From 7b345f132763fa6405ae858b74e46229629c19d9 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Wed, 10 Jun 2026 11:40:16 +0900 Subject: feat(tabs,app): tab id handles, fixed-width tabs-lift, slim shell + full-height sidebar Tabs: - short-handle ID badge per tab (shortest unique conversationId prefix, min 4) - fixed-width (w-48) tabs with tabs-lift folder borders Shell (composition root): - drop the Dispatch title bar; tabs sit at the very top with a 5px gap - big faded "Dispatch" watermark centered on an empty chat - collapsible right sidebar (empty shell) spanning full window height: a permanently right-pinned hamburger in the tab row toggles it; in-flow push that shrinks the whole left column (tabs included) at >=lg, overlay + backdrop below lg; open-by-default on wide / closed on narrow - main is overflow-hidden with a min-w-0 shrink chain; app.css pins html/body/#app height + body overflow hidden so the page never overflows --- src/app.css | 12 +++ src/app/App.svelte | 164 ++++++++++++++++++++++++++----------- src/features/tabs/index.ts | 2 + src/features/tabs/tabs.test.ts | 33 ++++++++ src/features/tabs/tabs.ts | 19 +++++ src/features/tabs/ui.test.ts | 35 ++++++++ src/features/tabs/ui/TabBar.svelte | 27 ++++-- 7 files changed, 239 insertions(+), 53 deletions(-) diff --git a/src/app.css b/src/app.css index 37131d3..5db1f25 100644 --- a/src/app.css +++ b/src/app.css @@ -6,3 +6,15 @@ @plugin "daisyui" { themes: dracula --default; } + +/* App shell fills the viewport and never scrolls/overflows at the page level — + the inner regions (tab strip, chat transcript) own their own scrolling. */ +html, +body, +#app { + height: 100%; +} + +body { + overflow: hidden; +} diff --git a/src/app/App.svelte b/src/app/App.svelte index 857a1e5..4ee071d 100644 --- a/src/app/App.svelte +++ b/src/app/App.svelte @@ -7,6 +7,12 @@ let { store }: { store: AppStore } = $props(); + // Right sidebar: open by default on wide screens (pushes the chat aside), + // closed by default on narrow screens (overlays the chat). Initial state is + // derived from the viewport width once; the hamburger toggles it thereafter. + const WIDE_BREAKPOINT = 1024; // Tailwind `lg` + let sidebarOpen = $state(typeof window !== "undefined" ? window.innerWidth >= WIDE_BREAKPOINT : true); + function handleSelect(surfaceId: string) { store.select(surfaceId); } @@ -24,34 +30,59 @@ } -
-
-

Dispatch

-
- - {#if store.lastError} -