diff --git a/web/hwlab-cloud-web/scripts/workbench-navigation.test.ts b/web/hwlab-cloud-web/scripts/workbench-navigation.test.ts new file mode 100644 index 00000000..c6f7fc5e --- /dev/null +++ b/web/hwlab-cloud-web/scripts/workbench-navigation.test.ts @@ -0,0 +1,22 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { isWorkbenchNavigationContext, shouldReflectWorkbenchConversationUrl } from "../src/router/workbench-navigation.ts"; + +test("workbench navigation context recognizes workbench routes only while the component is active", () => { + assert.equal(isWorkbenchNavigationContext({ componentActive: true, routeSection: "workbench", routeName: "CodeWorkbenchSession", routePath: "/workbench/sessions/cnv_1" }), true); + assert.equal(isWorkbenchNavigationContext({ componentActive: true, routeName: "CodeWorkbench", routePath: "/workspace" }), true); + assert.equal(isWorkbenchNavigationContext({ componentActive: true, routeSection: "user", routeName: "Dashboard", routePath: "/dashboard" }), false); + assert.equal(isWorkbenchNavigationContext({ componentActive: false, routeSection: "workbench", routeName: "CodeWorkbenchSession", routePath: "/workbench/sessions/cnv_1" }), false); +}); + +test("workbench URL reflection never overrides a user navigation outside Workbench", () => { + assert.equal(shouldReflectWorkbenchConversationUrl({ componentActive: true, routeSection: "user", routeName: "Dashboard", routePath: "/dashboard", routeConversationId: null, conversationId: "cnv_async" }), false); + assert.equal(shouldReflectWorkbenchConversationUrl({ componentActive: false, routeSection: "workbench", routeName: "CodeWorkbench", routePath: "/workbench", routeConversationId: null, conversationId: "cnv_async" }), false); +}); + +test("workbench URL reflection still updates empty Workbench routes and respects route hydration", () => { + assert.equal(shouldReflectWorkbenchConversationUrl({ componentActive: true, routeSection: "workbench", routeName: "CodeWorkbench", routePath: "/workbench", routeConversationId: null, conversationId: "cnv_selected" }), true); + assert.equal(shouldReflectWorkbenchConversationUrl({ componentActive: true, routeSection: "workbench", routeName: "CodeWorkbenchSession", routePath: "/workbench/sessions/cnv_selected", routeConversationId: "cnv_selected", conversationId: "cnv_selected" }), false); + assert.equal(shouldReflectWorkbenchConversationUrl({ componentActive: true, routeSection: "workbench", routeName: "CodeWorkbenchSession", routePath: "/workbench/sessions/cnv_route", routeConversationId: "cnv_route", conversationId: "cnv_store", applyingRouteConversation: true }), false); +}); diff --git a/web/hwlab-cloud-web/src/router/workbench-navigation.ts b/web/hwlab-cloud-web/src/router/workbench-navigation.ts new file mode 100644 index 00000000..c6812fff --- /dev/null +++ b/web/hwlab-cloud-web/src/router/workbench-navigation.ts @@ -0,0 +1,27 @@ +export interface WorkbenchNavigationContext { + componentActive: boolean; + routeName?: unknown; + routePath?: unknown; + routeSection?: unknown; + routeConversationId?: string | null; + conversationId?: string | null; + applyingRouteConversation?: boolean; +} + +const WORKBENCH_ROUTE_NAMES = new Set(["CodeWorkbench", "CodeWorkbenchSession"]); + +export function isWorkbenchNavigationContext(input: WorkbenchNavigationContext): boolean { + if (!input.componentActive) return false; + if (input.routeSection === "workbench") return true; + if (typeof input.routeName === "string" && WORKBENCH_ROUTE_NAMES.has(input.routeName)) return true; + if (typeof input.routePath !== "string") return false; + return input.routePath === "/workbench" || input.routePath === "/workspace" || input.routePath.startsWith("/workbench/") || input.routePath.startsWith("/workspace/"); +} + +export function shouldReflectWorkbenchConversationUrl(input: WorkbenchNavigationContext): boolean { + if (!input.conversationId) return false; + if (!isWorkbenchNavigationContext(input)) return false; + if (input.routeConversationId === input.conversationId) return false; + if (input.applyingRouteConversation && input.routeConversationId && input.routeConversationId !== input.conversationId) return false; + return true; +} diff --git a/web/hwlab-cloud-web/src/views/workbench/CodeWorkbenchView.vue b/web/hwlab-cloud-web/src/views/workbench/CodeWorkbenchView.vue index 90ed190f..1b9cd600 100644 --- a/web/hwlab-cloud-web/src/views/workbench/CodeWorkbenchView.vue +++ b/web/hwlab-cloud-web/src/views/workbench/CodeWorkbenchView.vue @@ -1,11 +1,12 @@