diff --git a/web/workspace/src/app.css b/web/workspace/src/app.css
index 526dc9c7..9c529e28 100644
--- a/web/workspace/src/app.css
+++ b/web/workspace/src/app.css
@@ -122,35 +122,12 @@
}
}
@layer layout {
- .app-layout {
- display: grid;
- grid-template-rows: auto minmax(0, 1fr);
- width: 100vw;
- height: 100dvh;
- margin: 0;
- padding: 0;
- overflow: hidden;
- min-width: 0;
- }
- .app-shell {
- display: grid;
- min-width: 0;
- min-height: 0;
- overflow: hidden;
- }
- .app-shell.global-sidebar-layout {
- grid-template-columns: minmax(180px, 240px) minmax(0, 1fr);
- }
- .app-content {
- min-width: 0;
- min-height: 0;
- overflow: auto;
- }
.workspace-layout {
display: grid;
grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
- width: 100%;
- height: 100%;
+ grid-template-rows: auto minmax(0, 1fr);
+ width: 100vw;
+ height: 100dvh;
margin: 0;
padding: 0;
overflow: hidden;
@@ -160,6 +137,8 @@
grid-template-columns: max-content minmax(0, 1fr);
}
.workspace-topbar {
+ grid-column: 2;
+ grid-row: 1;
display: flex;
align-items: center;
justify-content: flex-end;
@@ -201,6 +180,7 @@
}
.shell {
grid-column: 2;
+ grid-row: 2;
display: flex;
flex-direction: column;
gap: var(--space-6);
@@ -223,26 +203,15 @@
gap: var(--space-4);
}
@media (max-width: 760px) {
- .app-layout {
- height: auto;
- min-height: 100dvh;
- overflow: visible;
- }
- .app-shell,
- .app-shell.global-sidebar-layout {
- grid-template-columns: 1fr;
- overflow: visible;
- }
- .app-content {
- overflow: visible;
- }
.global-sidebar {
+ grid-column: 1;
+ grid-row: 1;
border-right: 0;
border-bottom: 1px solid var(--line);
}
.workspace-layout {
grid-template-columns: 1fr;
- grid-template-rows: auto 1fr;
+ grid-template-rows: auto auto 1fr;
width: 100%;
height: auto;
min-height: 0;
@@ -258,11 +227,13 @@
border-bottom: 1px solid var(--line);
}
.workspace-topbar {
+ grid-column: 1;
+ grid-row: 2;
padding: 0 var(--space-4);
}
.shell {
grid-column: 1;
- grid-row: 2;
+ grid-row: 3;
overflow: visible;
padding: var(--space-5) var(--space-4);
}
@@ -270,6 +241,8 @@
}
@layer components {
.global-sidebar {
+ grid-column: 1;
+ grid-row: 1 / 3;
min-width: 0;
min-height: 0;
overflow-y: auto;
@@ -282,7 +255,7 @@
}
.workspace-sidebar {
grid-column: 1;
- grid-row: 1;
+ grid-row: 1 / 3;
align-self: stretch;
min-width: 0;
min-height: 0;
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 c8949c28..4ebd0d24 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
@@ -393,12 +393,6 @@ Deno.test("Account UI owns browser passkey session state without workspace autho
const globalSidebar = await Deno.readTextFile(
new URL("../sidebar/GlobalSidebar.svelte", import.meta.url),
);
- const workspaceLayout = await Deno.readTextFile(
- new URL("./../../../routes/w/[workspaceId]/+layout.svelte", import.meta.url),
- );
- const workspaceLayoutLoad = await Deno.readTextFile(
- new URL("./../../../routes/w/[workspaceId]/+layout.ts", import.meta.url),
- );
const sidebar = await Deno.readTextFile(
new URL("../sidebar/WorkspaceSidebar.svelte", import.meta.url),
);
@@ -428,15 +422,15 @@ Deno.test("Account UI owns browser passkey session state without workspace autho
);
assert(
rootLayout.includes("GlobalSidebar") &&
- rootLayout.includes("global-sidebar-layout") &&
+ rootLayout.includes("WorkspaceSidebar") &&
+ rootLayout.includes("data.workspaceScoped") &&
rootLayout.includes("workspace-topbar") &&
rootLayout.includes("topbar-icon-button") &&
rootLayout.includes('href="/account"') &&
rootLayout.includes("Open Account") &&
- !rootLayout.includes("WorkspaceSidebar") &&
!sidebar.includes("accountHref") &&
!sidebar.includes("Open Account"),
- "Account navigation should live in the global layout header, not the workspace sidebar",
+ "Root layout chrome should choose GlobalSidebar or WorkspaceSidebar while account navigation stays in the header",
);
assert(
globalSidebar.includes("Global") &&
@@ -447,10 +441,10 @@ Deno.test("Account UI owns browser passkey session state without workspace autho
"Root default sidebar should contain only global navigation, not workspace-scoped sections",
);
assert(
- workspaceLayout.includes("WorkspaceSidebar") &&
- workspaceLayout.includes("workspace={data.workspace}") &&
- workspaceLayoutLoad.includes("workspaceApiPath(params.workspaceId"),
- "Workspace sidebar and workspace-scoped data loading should live under /w/[workspaceId] layout",
+ rootLayoutLoad.includes("params.workspaceId") &&
+ rootLayoutLoad.includes("workspaceApiPath(workspaceId") &&
+ rootLayoutLoad.includes("workspaceScoped: true"),
+ "Root layout load should provide workspace-scoped sidebar data when a workspace route is active",
);
assert(
rootLayoutLoad.includes('"/account"') && rootLayoutLoad.includes('"/login/device"'),
diff --git a/web/workspace/src/routes/+layout.svelte b/web/workspace/src/routes/+layout.svelte
index 4de53e22..68a95623 100644
--- a/web/workspace/src/routes/+layout.svelte
+++ b/web/workspace/src/routes/+layout.svelte
@@ -2,15 +2,30 @@
import { page } from '$app/state';
import WorkspaceAlerts from '$lib/workspace/alerts/WorkspaceAlerts.svelte';
import GlobalSidebar from '$lib/workspace/sidebar/GlobalSidebar.svelte';
+ import WorkspaceSidebar from '$lib/workspace/sidebar/WorkspaceSidebar.svelte';
import '../app.css';
import type { LayoutProps } from './$types';
let { data, children }: LayoutProps = $props();
+ let sidebarCollapsed = $state(false);