diff options
| author | Adam Malczewski <[email protected]> | 2026-06-27 16:08:10 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-27 16:08:10 +0900 |
| commit | 6bd8f4718b44d06d116d02d9538214c8450b1c8d (patch) | |
| tree | d709dc077be66cbf22108e492472e27cf731440d /src | |
| parent | 0e0601817712033b3247695646acd22d6496330a (diff) | |
| download | dispatch-web-6bd8f4718b44d06d116d02d9538214c8450b1c8d.tar.gz dispatch-web-6bd8f4718b44d06d116d02d9538214c8450b1c8d.zip | |
feat(sidebar-tabs): add active tab title to top bar (New Tab for unstarted chats)
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/App.svelte | 27 | ||||
| -rw-r--r-- | src/app/App.test.ts | 40 |
2 files changed, 64 insertions, 3 deletions
diff --git a/src/app/App.svelte b/src/app/App.svelte index 9a17999..1bb5689 100644 --- a/src/app/App.svelte +++ b/src/app/App.svelte @@ -216,6 +216,18 @@ return parseTodoPayload(field.payload); }); + // Top-bar title: the active tab's title, or "New Tab" when no tab is active + // (a fresh, unstarted draft — the conversation hasn't been sent yet, so no + // tab exists). Pure-derived from the (workspace-filtered) tab set + the + // active id; reflects whichever tab is selected in the sidebar's Tabs view. + const NEW_TAB_TITLE = "New Tab"; + const topBarTitle = $derived.by(() => { + const id = store.activeConversationId; + if (id === null) return NEW_TAB_TITLE; + const tab = store.tabs.find((t) => t.conversationId === id); + return tab?.title ?? NEW_TAB_TITLE; + }); + // Conversation/tab switch → snap to the bottom of the new transcript. $effect(() => { void store.activeConversationId; @@ -421,9 +433,18 @@ (below), so opening it shrinks this ENTIRE column. --> <div class="flex min-w-0 flex-1 flex-col overflow-hidden pt-[5px]"> <!-- Slim header: the tab bar moved into the sidebar (the "Tabs" view), so - the top row now holds only the build version + the sidebar toggle, - right-aligned. --> - <div class="flex items-center justify-end px-1"> + the top row now shows the active tab's title on the left (or "New Tab" + for an unstarted draft), with the build version + sidebar toggle on + the right. --> + <div class="flex items-center justify-between gap-2 px-2"> + <span + class="min-w-0 flex-1 shrink truncate text-sm font-medium opacity-70" + data-testid="top-bar-title" + title={topBarTitle} + aria-label="Active conversation title" + > + {topBarTitle} + </span> <span class="shrink-0 select-none px-1 font-mono text-[10px] leading-none text-base-content/30" title="Build version (git short hash)" diff --git a/src/app/App.test.ts b/src/app/App.test.ts index 7c0a851..3d6e70c 100644 --- a/src/app/App.test.ts +++ b/src/app/App.test.ts @@ -136,6 +136,46 @@ describe("App component interaction tests", () => { store.dispose(); }); + it("shows 'New Tab' in the top bar for an unstarted draft (no active tab)", () => { + const ws = fakeSocket(); + const store = createAppStore({ + socketFactory: () => ws, + fetchImpl: fakeFetchImpl(), + localStorage: createFakeStorage(), + }); + ws.resolveOpen(); + + render(App, { props: { store } }); + + expect(store.activeConversationId).toBeNull(); + expect(screen.getByTestId("top-bar-title")).toHaveTextContent("New Tab"); + + store.dispose(); + }); + + it("shows the active tab's title in the top bar once a tab is started", async () => { + const ws = fakeSocket(); + const store = createAppStore({ + socketFactory: () => ws, + fetchImpl: fakeFetchImpl(), + localStorage: createFakeStorage(), + }); + ws.resolveOpen(); + + render(App, { props: { store } }); + + // Promote draft → tab: the tab's title is derived from the first message. + store.send("Refactor the auth module"); + expect(store.activeConversationId).not.toBeNull(); + + // The top-bar title updates reactively; findByTestId awaits the flush. + expect(await screen.findByTestId("top-bar-title")).toHaveTextContent( + "Refactor the auth module", + ); + + store.dispose(); + }); + it("auto-subscribes to every catalog entry on render (no buttons to click)", () => { const ws = fakeSocket(); const store = createAppStore({ |
