From 4c1ef043781eb8866c980248a91631a5066ef637 Mon Sep 17 00:00:00 2001 From: Hare Date: Thu, 3 Sep 2026 02:49:58 +0900 Subject: [PATCH] fix: show workspace list throughout global sidebar --- .../src/lib/workspace/api/http.test.ts | 12 +++++++---- .../console/worker-console.ui.test.ts | 20 ++++++++---------- .../workspace/sidebar/GlobalSidebar.svelte | 12 +++++++++-- web/workspace/src/routes/+layout.svelte | 9 ++++++-- web/workspace/src/routes/+layout.ts | 21 +++++++++++++++---- web/workspace/src/routes/+page.svelte | 12 ----------- 6 files changed, 51 insertions(+), 35 deletions(-) diff --git a/web/workspace/src/lib/workspace/api/http.test.ts b/web/workspace/src/lib/workspace/api/http.test.ts index 93521aa6..eefa309f 100644 --- a/web/workspace/src/lib/workspace/api/http.test.ts +++ b/web/workspace/src/lib/workspace/api/http.test.ts @@ -43,17 +43,21 @@ Deno.test("root layout leaves Workspace selection explicit", async () => { new URL("./../../../routes/+layout.ts", import.meta.url), ); assert( - !layout.includes("/api/workspace") && + !layout.includes('"/api/workspace"') && !layout.includes("redirect(") && - layout.includes("Workspace selection is explicit"), - "root layout must not infer or redirect to a singleton Workspace", + layout.includes("listWorkspaces(fetch)") && + layout.includes("accessibleWorkspaces"), + "root layout may list accessible Workspaces but must not infer or redirect to a singleton Workspace", ); }); Deno.test("Workspace route changes dispose old multiplexed subscription state", async () => { const [layout, multiplexer] = await Promise.all([ Deno.readTextFile( - new URL("./../../../routes/w/[workspaceId]/+layout.svelte", import.meta.url), + new URL( + "./../../../routes/w/[workspaceId]/+layout.svelte", + import.meta.url, + ), ), Deno.readTextFile(new URL("./../multiplexer.ts", import.meta.url)), ]); 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 497361fd..dbe67448 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 @@ -358,9 +358,10 @@ Deno.test("root layout keeps Workspace selection explicit", async () => { assert( layoutLoad.includes("export const load") && - layoutLoad.includes("() => ({})") && + layoutLoad.includes("listWorkspaces(fetch)") && + layoutLoad.includes("accessibleWorkspaces") && !layoutLoad.includes("scopedCompatibilityRoute") && - !layoutLoad.includes("/api/workspace") && + !layoutLoad.includes('"/api/workspace"') && !layoutLoad.includes("workspaceRoute") && !layoutLoad.includes("redirect("), "root layout should not infer, bootstrap, or redirect through a singleton Workspace", @@ -815,9 +816,6 @@ Deno.test("Account UI owns browser passkey session state without workspace autho const globalNavSections = await Deno.readTextFile( new URL("../sidebar/GlobalNavSections.svelte", import.meta.url), ); - const workspaceCatalogPage = await Deno.readTextFile( - new URL("./../../../routes/+page.svelte", import.meta.url), - ); const sidebarFrame = await Deno.readTextFile( new URL("../sidebar/SidebarFrame.svelte", import.meta.url), ); @@ -891,13 +889,12 @@ Deno.test("Account UI owns browser passkey session state without workspace autho globalNavSections.includes("workspaces") && globalNavSections.includes("workspace.display_name") && globalNavSections.includes("workspaceHref(workspace.workspace_id)") && - workspaceCatalogPage.includes("SidebarOverride") && - workspaceCatalogPage.includes("sidebar={homeSidebar}") && - workspaceCatalogPage.includes("GlobalNavSections") && - workspaceCatalogPage.includes("{workspaces}") && + rootLayout.includes("workspaces={data.accessibleWorkspaces}") && + rootLayout.includes("workspaceError={data.workspaceCatalogError}") && + rootLayoutLoad.includes("listWorkspaces(fetch)") && !globalNavSections.includes("Tickets") && !globalNavSections.includes("Repositories"), - "Root page sidebar should replace the Workspaces button with a categorized accessible Workspace list below the remaining global navigation", + "Top-level sidebar should replace the Workspaces button with a categorized accessible Workspace list below the remaining global navigation", ); assert( workspaceLayout.includes("{#snippet workspaceSidebar()}") && @@ -935,7 +932,8 @@ Deno.test("Account UI owns browser passkey session state without workspace autho ); assert( rootLayoutLoad.includes("export const load") && - rootLayoutLoad.includes("() => ({})") && + rootLayoutLoad.includes("listWorkspaces(fetch)") && + rootLayoutLoad.includes("accessibleWorkspaces") && !rootLayoutLoad.includes("workspaceRoute") && !rootLayoutLoad.includes("redirect("), "Root layout should leave account and device-login routes public by avoiding Workspace redirects entirely", diff --git a/web/workspace/src/lib/workspace/sidebar/GlobalSidebar.svelte b/web/workspace/src/lib/workspace/sidebar/GlobalSidebar.svelte index 08430a63..fcf7fc02 100644 --- a/web/workspace/src/lib/workspace/sidebar/GlobalSidebar.svelte +++ b/web/workspace/src/lib/workspace/sidebar/GlobalSidebar.svelte @@ -1,4 +1,5 @@ {#if content} @@ -16,7 +24,7 @@ {:else} {/if} diff --git a/web/workspace/src/routes/+layout.svelte b/web/workspace/src/routes/+layout.svelte index 01499dea..bdd548d5 100644 --- a/web/workspace/src/routes/+layout.svelte +++ b/web/workspace/src/routes/+layout.svelte @@ -10,7 +10,7 @@ import '../app.css'; import type { LayoutProps } from './$types'; - let { children }: LayoutProps = $props(); + let { children, data }: LayoutProps = $props(); let sidebar = $state(null); const sidebarOverrides = createOverrideStack((activeSidebar) => { sidebar = activeSidebar; @@ -27,7 +27,12 @@
- +
diff --git a/web/workspace/src/routes/+layout.ts b/web/workspace/src/routes/+layout.ts index 3e4804dd..8dff6b82 100644 --- a/web/workspace/src/routes/+layout.ts +++ b/web/workspace/src/routes/+layout.ts @@ -1,5 +1,18 @@ -import type { LayoutLoad } from './$types'; +import { listWorkspaces } from "$lib/workspace/api/workspace-catalog"; +import type { LayoutLoad } from "./$types"; -// Workspace selection is explicit at `/`; the root layout must never infer a -// singleton Workspace or redirect based on an unscoped compatibility endpoint. -export const load: LayoutLoad = () => ({}); +export const load: LayoutLoad = async ({ fetch }) => { + try { + return { + accessibleWorkspaces: await listWorkspaces(fetch), + workspaceCatalogError: null, + }; + } catch (error) { + return { + accessibleWorkspaces: [], + workspaceCatalogError: error instanceof Error + ? error.message + : "Unable to load Workspaces", + }; + } +}; diff --git a/web/workspace/src/routes/+page.svelte b/web/workspace/src/routes/+page.svelte index 541624c6..75bf8187 100644 --- a/web/workspace/src/routes/+page.svelte +++ b/web/workspace/src/routes/+page.svelte @@ -8,8 +8,6 @@ type CreateWorkspaceRequest, type WorkspaceCatalogItem, } from "$lib/workspace/api/workspace-catalog"; - import GlobalNavSections from "$lib/workspace/sidebar/GlobalNavSections.svelte"; - import SidebarOverride from "$lib/workspace/sidebar/SidebarOverride.svelte"; import "$lib/workspace/styles/workspace-catalog.css"; let { data } = $props(); @@ -89,16 +87,6 @@ } -{#snippet homeSidebar()} - -{/snippet} - - - Workspaces ยท Yoi