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/features/tabs/tabs.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'src/features/tabs/tabs.ts') diff --git a/src/features/tabs/tabs.ts b/src/features/tabs/tabs.ts index a4db6f3..61ae58e 100644 --- a/src/features/tabs/tabs.ts +++ b/src/features/tabs/tabs.ts @@ -92,3 +92,22 @@ export function deriveTitle(message: string, max: number = DEFAULT_MAX_TITLE_LEN if (trimmed.length <= max) return trimmed; return `${trimmed.slice(0, max)}\u2026`; } + +/** Minimum length of a tab handle (git-style short id). */ +export const MIN_HANDLE_LENGTH = 4; + +/** + * The short "handle" shown on a tab: the shortest prefix of `conversationId` + * (at least `MIN_HANDLE_LENGTH` chars) that is unique among all open tabs — a + * git-style short id. Grows by a char only when another open tab shares the + * prefix, and shrinks back when that sibling closes. Pure: the id + every open + * id in, the handle string out. (`allIds` may include `conversationId` itself.) + */ +export function shortHandle(conversationId: string, allIds: readonly string[]): string { + const others = allIds.filter((id) => id !== conversationId); + for (let len = MIN_HANDLE_LENGTH; len < conversationId.length; len++) { + const candidate = conversationId.slice(0, len); + if (!others.some((id) => id.startsWith(candidate))) return candidate; + } + return conversationId; +} -- cgit v1.2.3