From 0e0601817712033b3247695646acd22d6496330a Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sat, 27 Jun 2026 02:17:24 +0900 Subject: feat(sidebar-tabs): move tab bar from top into sidebar as vertical list --- src/app/App.svelte | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) (limited to 'src/app') diff --git a/src/app/App.svelte b/src/app/App.svelte index 09be947..9a17999 100644 --- a/src/app/App.svelte +++ b/src/app/App.svelte @@ -40,7 +40,7 @@ import { parseMessageQueuePayload } from "../features/surface-host/logic/message-queue"; import { parseTodoPayload } from "../features/surface-host/logic/todo"; import TodoList from "../features/surface-host/ui/TodoList.svelte"; - import { manifest as tabsManifest, TabBar } from "../features/tabs"; + import { manifest as tabsManifest, TabList } from "../features/tabs"; import { manifest as viewsManifest, ViewSidebar } from "../features/views"; import { CwdField, @@ -99,6 +99,7 @@ // The view kinds offered in the sidebar's dropdown. Generic data — the // `viewContent` snippet below maps each kind id to its renderer. const viewKinds = [ + { id: "tabs", label: "Tabs" }, { id: "model", label: "Model" }, { id: "lsp", label: "Language Servers" }, { id: "mcp", label: "MCP Servers" }, @@ -111,8 +112,9 @@ { id: "settings", label: "Settings" }, ] as const; - // Default sidebar layout: just the Model view. - const DEFAULT_VIEWS: readonly string[] = ["model"]; + // Default sidebar layout: the Tabs list (the moved-from-top tab bar) plus the + // Model view, so a fresh user sees their conversations + model controls. + const DEFAULT_VIEWS: readonly string[] = ["tabs", "model"]; const sidebarStore = createLocalStore("dispatch.sidebar.views", { storage: untrack(() => store.storage), }); @@ -416,21 +418,12 @@
+ (below), so opening it shrinks this ENTIRE column. -->
- -
- store.conversationStatus(id)} - onSelect={(id) => store.selectTab(id)} - onClose={(id) => store.closeTab(id)} - onNewDraft={() => store.newDraft()} - onRename={(id, title) => store.renameTab(id, title)} - /> + +
+ {#key store.activeWorkspaceId} + store.conversationStatus(id)} + onSelect={(id) => store.selectTab(id)} + onClose={(id) => store.closeTab(id)} + onNewDraft={() => store.newDraft()} + onRename={(id, title) => store.renameTab(id, title)} + /> + {/key} + {:else if kind === "model"}
-
+ 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. --> +
+ + {topBarTitle} + { 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({ -- cgit v1.2.3 From 07bb2ca4a5c17101f6f0e469564963e5e922399e Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sat, 27 Jun 2026 16:13:34 +0900 Subject: feat(sidebar-tabs): add 2px horizontal spacing to top-bar title --- src/app/App.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/app') diff --git a/src/app/App.svelte b/src/app/App.svelte index 1bb5689..04be746 100644 --- a/src/app/App.svelte +++ b/src/app/App.svelte @@ -438,7 +438,7 @@ the right. -->
Date: Sat, 27 Jun 2026 16:15:09 +0900 Subject: fix(sidebar-tabs): inset top-bar title 8px left to match parent px-2 spacing --- src/app/App.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/app') diff --git a/src/app/App.svelte b/src/app/App.svelte index 04be746..b52c414 100644 --- a/src/app/App.svelte +++ b/src/app/App.svelte @@ -438,7 +438,7 @@ the right. -->
Date: Sat, 27 Jun 2026 16:23:33 +0900 Subject: feat(sidebar-tabs): replace hamburger with directional double-chevron toggle --- src/app/App.svelte | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'src/app') diff --git a/src/app/App.svelte b/src/app/App.svelte index b52c414..290f27b 100644 --- a/src/app/App.svelte +++ b/src/app/App.svelte @@ -453,7 +453,7 @@
-- cgit v1.2.3 From d3c1f96cbcaea4cadb2432a93e7ba305e938735e Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sat, 27 Jun 2026 16:26:28 +0900 Subject: feat(sidebar-tabs): remove sidebar left border --- src/app/App.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/app') diff --git a/src/app/App.svelte b/src/app/App.svelte index 290f27b..31d0155 100644 --- a/src/app/App.svelte +++ b/src/app/App.svelte @@ -557,7 +557,7 @@ class:w-0={!sidebarOpen} >
-- cgit v1.2.3 From 814544e1924e736c924de6d4d5b3c404ca036999 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sat, 27 Jun 2026 16:29:48 +0900 Subject: fix(sidebar-tabs): drop sidebar left padding so it shares the chat gutter --- src/app/App.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/app') diff --git a/src/app/App.svelte b/src/app/App.svelte index 31d0155..9926824 100644 --- a/src/app/App.svelte +++ b/src/app/App.svelte @@ -557,7 +557,7 @@ class:w-0={!sidebarOpen} >
-- cgit v1.2.3 From 47248676ff6db70c4db00c866e13840c3299de36 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sat, 27 Jun 2026 17:13:45 +0900 Subject: fix(sidebar-tabs): align chat scrollbar with composer instead of flush against sidebar --- src/app/App.svelte | 2 +- src/features/chat/ui/ChatView.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/app') diff --git a/src/app/App.svelte b/src/app/App.svelte index 9926824..2bcf346 100644 --- a/src/app/App.svelte +++ b/src/app/App.svelte @@ -499,7 +499,7 @@
{/if} -
+
{#key store.activeConversationId} diff --git a/src/features/chat/ui/ChatView.svelte b/src/features/chat/ui/ChatView.svelte index e72f639..b49f7b4 100644 --- a/src/features/chat/ui/ChatView.svelte +++ b/src/features/chat/ui/ChatView.svelte @@ -210,7 +210,7 @@ {/if} {/snippet} -
+
{#if hasEarlier && onShowEarlier}
-- cgit v1.2.3 From e1f6986c32767fea445eedffece488f10a73b3d2 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sat, 27 Jun 2026 17:15:02 +0900 Subject: feat(sidebar-tabs): prefix build hash with "build: " --- src/app/App.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/app') diff --git a/src/app/App.svelte b/src/app/App.svelte index 2bcf346..f8e6e5f 100644 --- a/src/app/App.svelte +++ b/src/app/App.svelte @@ -449,7 +449,7 @@ class="shrink-0 select-none px-1 font-mono text-[10px] leading-none text-base-content/30" title="Build version (git short hash)" > - {__APP_VERSION__} + build: {__APP_VERSION__}
{#if cwdError} diff --git a/src/features/workspaces/ui/WorkspaceCard.test.ts b/src/features/workspaces/ui/WorkspaceCard.test.ts index 0736efb..0d03b8e 100644 --- a/src/features/workspaces/ui/WorkspaceCard.test.ts +++ b/src/features/workspaces/ui/WorkspaceCard.test.ts @@ -119,16 +119,20 @@ describe("WorkspaceCard", () => { expect(store.setDefaultCwd).toHaveBeenCalledWith("my-ws", null); }); - it("the Open link opens the workspace in a new browser tab (no same-tab navigation)", () => { + it("the Open link navigates to the workspace in the same tab (SPA navigation, no new tab)", async () => { + const user = userEvent.setup(); const store = fakeStore() as unknown as WorkspaceStore; const onNavigate = vi.fn(); render(WorkspaceCard, { props: { ws: fakeEntry(), store, onNavigate, computers: [] } }); const open = screen.getByRole("link", { name: "Open" }); - // Native new-tab link: href points at the workspace, and target="_blank" - // opens it in a new browser tab rather than client-side navigating. + // Still a real link (progressive enhancement): href points at the workspace. expect(open).toHaveAttribute("href", "/my-ws"); - expect(open).toHaveAttribute("target", "_blank"); - expect(open.getAttribute("rel") ?? "").toMatch(/noopener/); + // But it no longer opens a new browser tab. + expect(open).not.toHaveAttribute("target", "_blank"); + // Clicking navigates in-place via the SPA callback. + await user.click(open); + expect(onNavigate).toHaveBeenCalledTimes(1); + expect(onNavigate).toHaveBeenCalledWith("/my-ws"); }); }); -- cgit v1.2.3