diff --git a/web/workspace/src/app.css b/web/workspace/src/app.css index 8d047123..fffc5f5b 100644 --- a/web/workspace/src/app.css +++ b/web/workspace/src/app.css @@ -125,6 +125,7 @@ .workspace-layout { display: grid; grid-template-columns: minmax(220px, 280px) minmax(0, 1fr); + grid-template-rows: auto minmax(0, 1fr); width: 100vw; height: 100dvh; margin: 0; @@ -135,7 +136,69 @@ .workspace-layout.sidebar-collapsed { grid-template-columns: max-content minmax(0, 1fr); } + .workspace-topbar { + grid-column: 2; + grid-row: 1; + display: flex; + align-items: center; + justify-content: space-between; + min-width: 0; + min-height: 3.25rem; + padding: 0 var(--space-5); + border-bottom: 1px solid var(--line); + background: color-mix(in srgb, var(--bg-raised) 88%, transparent); + backdrop-filter: blur(14px); + } + .workspace-topbar-title { + display: inline-flex; + align-items: center; + gap: var(--space-2); + color: var(--text-strong); + font-weight: 700; + } + .workspace-topbar-logo { + display: inline-grid; + width: 1.8rem; + height: 1.8rem; + place-items: center; + border-radius: var(--radius-soft); + background: var(--interactive-hover); + color: var(--text-strong); + font-size: 0.9rem; + font-weight: 800; + } + .workspace-topbar-actions { + display: inline-flex; + align-items: center; + gap: var(--space-2); + } + .topbar-icon-button { + display: inline-flex; + width: 2.35rem; + height: 2.35rem; + align-items: center; + justify-content: center; + border-radius: 999px; + color: var(--text-muted); + text-decoration: none; + } + .topbar-icon-button:hover, + .topbar-icon-button:focus-visible { + background: var(--interactive-hover); + color: var(--text-muted); + } + .topbar-icon { + width: 1.1rem; + height: 1.1rem; + fill: none; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + } .shell { + grid-column: 2; + grid-row: 2; display: flex; flex-direction: column; gap: var(--space-6); @@ -160,6 +223,7 @@ @media (max-width: 760px) { .workspace-layout { grid-template-columns: 1fr; + grid-template-rows: auto auto 1fr; width: 100vw; height: auto; min-height: 100dvh; @@ -168,7 +232,18 @@ .workspace-layout.sidebar-collapsed { grid-template-columns: 1fr; } + .workspace-sidebar { + grid-column: 1; + grid-row: 1; + } + .workspace-topbar { + grid-column: 1; + grid-row: 2; + padding: 0 var(--space-4); + } .shell { + grid-column: 1; + grid-row: 3; overflow: visible; padding: var(--space-5) var(--space-4); } @@ -176,6 +251,8 @@ } @layer components { .workspace-sidebar { + grid-column: 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 0b94aebf..3acf3cf7 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 @@ -385,8 +385,14 @@ Deno.test("Account UI owns browser passkey session state without workspace autho new URL("../auth/api.ts", import.meta.url), ); const rootLayout = await Deno.readTextFile( + new URL("./../../../routes/+layout.svelte", import.meta.url), + ); + const rootLayoutLoad = await Deno.readTextFile( new URL("./../../../routes/+layout.ts", import.meta.url), ); + const sidebar = await Deno.readTextFile( + new URL("../sidebar/WorkspaceSidebar.svelte", import.meta.url), + ); assert( accountPage.includes("registerPasskey") && @@ -412,7 +418,16 @@ Deno.test("Account UI owns browser passkey session state without workspace autho "Auth model should stay on Backend auth APIs rather than workspace authorization APIs", ); assert( - rootLayout.includes('"/account"') && rootLayout.includes('"/login/device"'), + rootLayout.includes("workspace-topbar") && + rootLayout.includes("topbar-icon-button") && + rootLayout.includes('href="/account"') && + rootLayout.includes("Open Account") && + !sidebar.includes("accountHref") && + !sidebar.includes("Open Account"), + "Account navigation should live in the global layout header, not the workspace sidebar", + ); + assert( + rootLayoutLoad.includes('"/account"') && rootLayoutLoad.includes('"/login/device"'), "Root layout should not redirect account and device-login public routes to a workspace", ); }); diff --git a/web/workspace/src/lib/workspace/sidebar/WorkspaceSidebar.svelte b/web/workspace/src/lib/workspace/sidebar/WorkspaceSidebar.svelte index a1151f8a..54326508 100644 --- a/web/workspace/src/lib/workspace/sidebar/WorkspaceSidebar.svelte +++ b/web/workspace/src/lib/workspace/sidebar/WorkspaceSidebar.svelte @@ -28,7 +28,6 @@ let workspaceId = $derived(workspace?.workspace_id ?? ''); let homeHref = $derived(workspaceId ? workspaceRoute(workspaceId) : '/'); - let accountHref = '/account'; let settingsHref = $derived(workspaceId ? workspaceRoute(workspaceId, '/settings') : '/settings'); @@ -88,17 +87,6 @@ /> - - - (sidebarCollapsed = !sidebarCollapsed)} /> +
+
+ + Yoi +
+
+
{@render children()}