From bd3cc65e1953a441e96afb7cf8486fded27c675d Mon Sep 17 00:00:00 2001 From: Hare Date: Wed, 23 Sep 2026 01:29:21 +0900 Subject: [PATCH] fix: fence shared chrome overrides by route --- web/workspace/deno.json | 2 +- .../console/worker-console.ui.test.ts | 29 ++++++- .../workspace/header/HeaderOverride.svelte | 14 ++-- .../src/lib/workspace/header/context.ts | 4 +- .../workspace/sidebar/SidebarOverride.svelte | 12 ++- .../workspace/sidebar/WorkspaceSidebar.svelte | 5 +- .../sidebar/chrome-overrides.browser.spec.ts | 78 +++++++++++++++++++ .../workspace/sidebar/route-ownership.test.ts | 48 ++++++++++++ .../lib/workspace/sidebar/route-ownership.ts | 4 + web/workspace/src/routes/+layout.svelte | 17 +++- .../workspace-web-ux/+layout.svelte | 16 +++- .../WorkspaceSidebarFixture.svelte | 3 +- .../workspace-web-ux/settings/+layout.svelte | 12 ++- .../src/routes/w/[workspaceId]/+layout.svelte | 15 +++- .../w/[workspaceId]/settings/+layout.svelte | 16 +++- 15 files changed, 248 insertions(+), 27 deletions(-) create mode 100644 web/workspace/src/lib/workspace/sidebar/chrome-overrides.browser.spec.ts create mode 100644 web/workspace/src/lib/workspace/sidebar/route-ownership.test.ts create mode 100644 web/workspace/src/lib/workspace/sidebar/route-ownership.ts diff --git a/web/workspace/deno.json b/web/workspace/deno.json index 3b9e7853b..7ecc4b5bd 100644 --- a/web/workspace/deno.json +++ b/web/workspace/deno.json @@ -6,7 +6,7 @@ "dev": "deno run -A npm:vite@7.2.7 dev", "dev:backend": "cd ../.. && cargo run -p yoi-workspace-server --bin yoi-server -- serve --listen 127.0.0.1:8787", "check": "deno run -A npm:@sveltejs/kit@2.49.4 sync && deno run -A npm:svelte-check@4.3.4 --tsconfig ./tsconfig.json", - "test": "deno test --allow-read=src,test,tests,../../docs --allow-env=LOG,VSCODE_TEXTMATE_DEBUG,NODE_ENV tests/workspace-model.test.ts tests/repository-api.test.ts tests/workspace-catalog.test.ts tests/profile-api.test.ts tests/skill-api.test.ts tests/tooltip-contract.test.ts tests/bevel-contract.test.ts tests/bevel-line-contract.test.ts tests/typography-contract.test.ts src/lib/workspace/auth/model.test.ts tests/auth-api.test.ts src/lib/workspace/api/http.test.ts src/lib/workspace/api/workers.test.ts src/lib/workspace/header/breadcrumb-model.test.ts src/lib/workspace/console/chat-submit.test.ts src/lib/workspace/console/composer-delivery.test.ts test/composer-history.test.ts tests/composer-paste.test.ts src/lib/workspace/console/composer-command.test.ts src/lib/workspace/console/composer-draft.test.ts src/lib/workspace/console/composer-completion.test.ts test/console/ansi.test.ts src/lib/workspace/console/model.test.ts src/lib/workspace/console/run-status.test.ts src/lib/workspace/companion/api.test.ts tests/workdir-api.test.ts src/lib/workspace/console/tasks.test.ts test/ticket-detail-route-reuse.test.ts test/objective-pages.test.ts test/repositories/ui.test.ts src/lib/workspace/console/worker-console.ui.test.ts src/lib/workspace/settings/model.test.ts tests/runtime-connection.test.ts tests/runtime-management.test.ts tests/runtime-management-source.test.ts src/lib/workspace/sidebar/override-stack.test.ts src/lib/workspace/sidebar/workers.test.ts src/lib/workspace/sidebar/workspace-switcher.test.ts src/lib/workspace/sidebar/worker-subscription.test.ts src/lib/workspace/sidebar/worker-workdir-meta.test.ts src/lib/workspace/sidebar/worker-launch.test.ts test/sidebar/worker-actions.test.ts src/lib/workspace/tickets/merge-request-resources.test.ts src/lib/workspace/tickets/ticket-panel.test.ts test/merge-request-status.test.ts test/config-source/decodal-grammar.test.ts test/config-source/editor-state.test.ts test/config-source/fixed-schema-wrapper.test.ts test/config-source/toolchain.test.ts test/config-source/wasm-parity.test.ts test/repository-access/api.test.ts test/repository-access/loader.test.ts test/repository-access/ui.test.ts test/repository-ssh-connection-ui.test.ts", + "test": "deno test --allow-read=src,test,tests,../../docs --allow-env=LOG,VSCODE_TEXTMATE_DEBUG,NODE_ENV tests/workspace-model.test.ts tests/repository-api.test.ts tests/workspace-catalog.test.ts tests/profile-api.test.ts tests/skill-api.test.ts tests/tooltip-contract.test.ts tests/bevel-contract.test.ts tests/bevel-line-contract.test.ts tests/typography-contract.test.ts src/lib/workspace/auth/model.test.ts tests/auth-api.test.ts src/lib/workspace/api/http.test.ts src/lib/workspace/api/workers.test.ts src/lib/workspace/header/breadcrumb-model.test.ts src/lib/workspace/console/chat-submit.test.ts src/lib/workspace/console/composer-delivery.test.ts test/composer-history.test.ts tests/composer-paste.test.ts src/lib/workspace/console/composer-command.test.ts src/lib/workspace/console/composer-draft.test.ts src/lib/workspace/console/composer-completion.test.ts test/console/ansi.test.ts src/lib/workspace/console/model.test.ts src/lib/workspace/console/run-status.test.ts src/lib/workspace/companion/api.test.ts tests/workdir-api.test.ts src/lib/workspace/console/tasks.test.ts test/ticket-detail-route-reuse.test.ts test/objective-pages.test.ts test/repositories/ui.test.ts src/lib/workspace/console/worker-console.ui.test.ts src/lib/workspace/settings/model.test.ts tests/runtime-connection.test.ts tests/runtime-management.test.ts tests/runtime-management-source.test.ts src/lib/workspace/sidebar/override-stack.test.ts src/lib/workspace/sidebar/route-ownership.test.ts src/lib/workspace/sidebar/workers.test.ts src/lib/workspace/sidebar/workspace-switcher.test.ts src/lib/workspace/sidebar/worker-subscription.test.ts src/lib/workspace/sidebar/worker-workdir-meta.test.ts src/lib/workspace/sidebar/worker-launch.test.ts test/sidebar/worker-actions.test.ts src/lib/workspace/tickets/merge-request-resources.test.ts src/lib/workspace/tickets/ticket-panel.test.ts test/merge-request-status.test.ts test/config-source/decodal-grammar.test.ts test/config-source/editor-state.test.ts test/config-source/fixed-schema-wrapper.test.ts test/config-source/toolchain.test.ts test/config-source/wasm-parity.test.ts test/repository-access/api.test.ts test/repository-access/loader.test.ts test/repository-access/ui.test.ts test/repository-ssh-connection-ui.test.ts", "test:component": "deno run -A npm:vitest@3.2.4 run --config vitest.config.ts", "build": "deno run -A npm:vite@7.2.7 build", "preview": "deno run -A npm:vite@7.2.7 preview", diff --git a/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts b/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts index 427bb744f..98ebf4397 100644 --- a/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts +++ b/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts @@ -179,6 +179,13 @@ Deno.test("design lab demonstrates nested sidebar slots without flattening level workspaceLayout.includes("content={sidebarContent}"), "Workspace design-lab layout must expose its child override slot", ); + assert( + workspaceLayout.includes( + "ownsRoutePath(designLabBasePath, page.url.pathname)", + ) && + workspaceLayout.includes("active={ownsCurrentRoute}"), + "Workspace design-lab chrome must unregister outside its owned route", + ); assert( !workspaceLayout.includes("") && rootLayout.includes("registerSidebar: sidebarOverrides.register"), - "SidebarOverride should register a nested sidebar whose cleanup restores the parent override", + "Sidebar overrides should register only while their route owns the shared chrome slot", + ); + assert( + headerOverride.includes("if (!active) return") && + headerOverride.includes("controller.registerContent(content)") && + rootLayout.includes("createOverrideStack") && + rootLayout.includes("registerContent: headerOverrides.register"), + "Header overrides should share the route-scoped LIFO registration contract", ); assert( rootLayoutLoad.includes("export const load") && diff --git a/web/workspace/src/lib/workspace/header/HeaderOverride.svelte b/web/workspace/src/lib/workspace/header/HeaderOverride.svelte index c33451dac..4bb16fe92 100644 --- a/web/workspace/src/lib/workspace/header/HeaderOverride.svelte +++ b/web/workspace/src/lib/workspace/header/HeaderOverride.svelte @@ -2,13 +2,17 @@ import type { Snippet } from 'svelte'; import { getHeaderController } from './context'; - let { content }: { content: Snippet<[]> } = $props(); + let { + content, + active = true, + }: { + content: Snippet<[]>; + active?: boolean; + } = $props(); const controller = getHeaderController(); $effect(() => { - controller.content = content; - return () => { - if (controller.content === content) controller.content = null; - }; + if (!active) return; + return controller.registerContent(content); }); diff --git a/web/workspace/src/lib/workspace/header/context.ts b/web/workspace/src/lib/workspace/header/context.ts index a5fbe8e7f..5176ae3ff 100644 --- a/web/workspace/src/lib/workspace/header/context.ts +++ b/web/workspace/src/lib/workspace/header/context.ts @@ -3,9 +3,9 @@ import type { Snippet } from "svelte"; const HEADER_CONTEXT_KEY = Symbol("workspace-header"); -export type HeaderContent = Snippet<[]> | null; +export type HeaderSnippet = Snippet<[]>; export type HeaderController = { - content: HeaderContent; + registerContent(content: HeaderSnippet): () => void; }; export function provideHeaderController(controller: HeaderController): void { diff --git a/web/workspace/src/lib/workspace/sidebar/SidebarOverride.svelte b/web/workspace/src/lib/workspace/sidebar/SidebarOverride.svelte index 3f89acb22..9355d7c73 100644 --- a/web/workspace/src/lib/workspace/sidebar/SidebarOverride.svelte +++ b/web/workspace/src/lib/workspace/sidebar/SidebarOverride.svelte @@ -8,10 +8,18 @@ type Props = { sidebar: SidebarSnippet; controller?: SidebarController; + active?: boolean; }; const inheritedController = getSidebarController(); - const { sidebar, controller = inheritedController }: Props = $props(); + const { + sidebar, + controller = inheritedController, + active = true, + }: Props = $props(); - $effect(() => controller.registerSidebar(sidebar)); + $effect(() => { + if (!active) return; + return controller.registerSidebar(sidebar); + }); diff --git a/web/workspace/src/lib/workspace/sidebar/WorkspaceSidebar.svelte b/web/workspace/src/lib/workspace/sidebar/WorkspaceSidebar.svelte index 4752d56c8..fb1361fa7 100644 --- a/web/workspace/src/lib/workspace/sidebar/WorkspaceSidebar.svelte +++ b/web/workspace/src/lib/workspace/sidebar/WorkspaceSidebar.svelte @@ -2,6 +2,7 @@ import type { Snippet } from 'svelte'; import './sidebar.css'; import { workspaceRoute } from '$lib/workspace/api/http'; + import { ownsRoutePath } from './route-ownership'; import ObjectivesNavSection from './ObjectivesNavSection.svelte'; import MemoryNavSection from './MemoryNavSection.svelte'; import MergeRequestsNavSection from './MergeRequestsNavSection.svelte'; @@ -50,11 +51,11 @@
- {#if headerController.content}{@render headerController.content()}{/if} + {#if header}{@render header()}{/if}