From 13bfdd8cc18411bee4c2b51a406d6c7474343de5 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 17 Apr 2026 14:45:22 +0700 Subject: [PATCH] fix(app): land on agent tab after creating a workspace Workspace creation landed on the setup tab because openTab implicitly focused the tab it opened, and the setup auto-open effect ran after the setup dialog's navigation, stomping on the agent tab's focus. Split the store API into explicit openTabFocused and openTabInBackground so every caller declares intent. The setup auto-open effect now opens in background and no longer steals focus; user-intent call sites (sidebar, kebab menus, file explorer, terminal creation, prepareWorkspaceTab) use openTabFocused and drop the redundant paired focusTab call. --- .../app/src/hooks/use-open-project.test.ts | 2 +- packages/app/src/hooks/use-open-project.ts | 2 +- .../screens/workspace/workspace-screen.tsx | 65 ++---- .../src/stores/workspace-layout-actions.ts | 23 +- .../src/stores/workspace-layout-store.test.ts | 204 +++++++++++++----- .../app/src/stores/workspace-layout-store.ts | 40 +++- .../app/src/utils/workspace-navigation.ts | 6 +- 7 files changed, 232 insertions(+), 110 deletions(-) diff --git a/packages/app/src/hooks/use-open-project.test.ts b/packages/app/src/hooks/use-open-project.test.ts index b05f038d4..bc365efc5 100644 --- a/packages/app/src/hooks/use-open-project.test.ts +++ b/packages/app/src/hooks/use-open-project.test.ts @@ -39,7 +39,7 @@ const WORKSPACE_ID = "/repo/project"; function createOpenDraftTab() { return (workspaceKey: string) => - useWorkspaceLayoutStore.getState().openTab(workspaceKey, { + useWorkspaceLayoutStore.getState().openTabFocused(workspaceKey, { kind: "draft", draftId: generateDraftId(), }); diff --git a/packages/app/src/hooks/use-open-project.ts b/packages/app/src/hooks/use-open-project.ts index 2d5fa3603..c9879d6e0 100644 --- a/packages/app/src/hooks/use-open-project.ts +++ b/packages/app/src/hooks/use-open-project.ts @@ -71,7 +71,7 @@ export function useOpenProject(serverId: string | null): (path: string) => Promi mergeWorkspaces, setHasHydratedWorkspaces, openDraftTab: (workspaceKey: string) => - useWorkspaceLayoutStore.getState().openTab(workspaceKey, { + useWorkspaceLayoutStore.getState().openTabFocused(workspaceKey, { kind: "draft", draftId: generateDraftId(), }), diff --git a/packages/app/src/screens/workspace/workspace-screen.tsx b/packages/app/src/screens/workspace/workspace-screen.tsx index 8463686f4..302136e68 100644 --- a/packages/app/src/screens/workspace/workspace-screen.tsx +++ b/packages/app/src/screens/workspace/workspace-screen.tsx @@ -713,12 +713,9 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) if (input?.paneId) { focusWorkspacePane(workspaceKey, input.paneId); } - const tabId = useWorkspaceLayoutStore + useWorkspaceLayoutStore .getState() - .openTab(workspaceKey, { kind: "terminal", terminalId: createdTerminal.id }); - if (tabId) { - useWorkspaceLayoutStore.getState().focusTab(workspaceKey, tabId); - } + .openTabFocused(workspaceKey, { kind: "terminal", terminalId: createdTerminal.id }); } }, }); @@ -911,7 +908,10 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) () => (workspaceLayout ? collectAllTabs(workspaceLayout.root) : EMPTY_UI_TABS), [workspaceLayout], ); - const openWorkspaceTab = useWorkspaceLayoutStore((state) => state.openTab); + const openWorkspaceTabFocused = useWorkspaceLayoutStore((state) => state.openTabFocused); + const openWorkspaceTabInBackground = useWorkspaceLayoutStore( + (state) => state.openTabInBackground, + ); const focusWorkspaceTab = useWorkspaceLayoutStore((state) => state.focusTab); const closeWorkspaceTab = useWorkspaceLayoutStore((state) => state.closeTab); const unpinWorkspaceAgent = useWorkspaceLayoutStore((state) => state.unpinAgent); @@ -989,26 +989,9 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) if (!persistenceKey) { return null; } - - const normalizedTarget = normalizeWorkspaceTabTarget(target); - if (!normalizedTarget) { - return null; - } - - const existingTab = - uiTabs.find((tab) => workspaceTabTargetsEqual(tab.target, normalizedTarget)) ?? null; - if (existingTab) { - return existingTab.tabId; - } - - const previousFocusedTabId = focusedPaneTabState.activeTabId; - const tabId = openWorkspaceTab(persistenceKey, normalizedTarget); - if (tabId && previousFocusedTabId && previousFocusedTabId !== tabId) { - focusWorkspaceTab(persistenceKey, previousFocusedTabId); - } - return tabId; + return openWorkspaceTabInBackground(persistenceKey, target); }, - [focusWorkspaceTab, focusedPaneTabState, openWorkspaceTab, persistenceKey, uiTabs], + [openWorkspaceTabInBackground, persistenceKey], ); const openWorkspaceDraftTab = useCallback( @@ -1023,16 +1006,11 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) }); invariant(target?.kind === "draft", "Draft tab target must be valid"); if (input?.focus === false) { - return ensureWorkspaceTab(target); + return openWorkspaceTabInBackground(persistenceKey, target); } - - const tabId = openWorkspaceTab(persistenceKey, target); - if (tabId) { - focusWorkspaceTab(persistenceKey, tabId); - } - return tabId; + return openWorkspaceTabFocused(persistenceKey, target); }, - [ensureWorkspaceTab, focusWorkspaceTab, openWorkspaceTab, persistenceKey], + [openWorkspaceTabFocused, openWorkspaceTabInBackground, persistenceKey], ); useEffect(() => { @@ -1156,18 +1134,16 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) return; } - const tabId = openWorkspaceTab(persistenceKey, target); + const tabId = openWorkspaceTabInBackground(persistenceKey, target); if (!tabId) { return; } - focusWorkspaceTab(persistenceKey, tabId); autoOpenedSetupTabWorkspaceRef.current = persistenceKey; }, [ - focusWorkspaceTab, hasSetupTab, normalizedWorkspaceId, - openWorkspaceTab, + openWorkspaceTabInBackground, persistenceKey, workspaceSetupSnapshot, ]); @@ -1180,12 +1156,12 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) if (!persistenceKey) { return; } - const tabId = openWorkspaceTab(persistenceKey, { kind: "file", path: filePath }); + const tabId = openWorkspaceTabFocused(persistenceKey, { kind: "file", path: filePath }); if (tabId) { navigateToTabId(tabId); } }, - [closeToAgent, isMobile, navigateToTabId, openWorkspaceTab, persistenceKey], + [closeToAgent, isMobile, navigateToTabId, openWorkspaceTabFocused, persistenceKey], ); const handleOpenFileFromChat = useCallback( @@ -1520,11 +1496,8 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) if (!target) { return; } - const tabId = openWorkspaceTab(persistenceKey, target); - if (tabId) { - focusWorkspaceTab(persistenceKey, tabId); - } - }, [focusWorkspaceTab, normalizedWorkspaceId, openWorkspaceTab, persistenceKey]); + openWorkspaceTabFocused(persistenceKey, target); + }, [normalizedWorkspaceId, openWorkspaceTabFocused, persistenceKey]); const handleBulkCloseTabs = useCallback( async (input: { tabsToClose: WorkspaceTabDescriptor[]; title: string; logLabel: string }) => { @@ -1841,7 +1814,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) if (input.focusPaneBeforeOpen && input.paneId) { focusWorkspacePane(persistenceKey, input.paneId); } - const tabId = openWorkspaceTab(persistenceKey, target); + const tabId = openWorkspaceTabFocused(persistenceKey, target); if (tabId) { navigateToTabId(tabId); } @@ -1873,7 +1846,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) navigateToTabId, normalizedServerId, normalizedWorkspaceId, - openWorkspaceTab, + openWorkspaceTabFocused, persistenceKey, convertWorkspaceDraftToAgent, retargetWorkspaceTab, diff --git a/packages/app/src/stores/workspace-layout-actions.ts b/packages/app/src/stores/workspace-layout-actions.ts index a2a21f100..0ae040258 100644 --- a/packages/app/src/stores/workspace-layout-actions.ts +++ b/packages/app/src/stores/workspace-layout-actions.ts @@ -1019,6 +1019,7 @@ function insertNewTabIntoFocusedPane(input: { layout: WorkspaceLayout; target: WorkspaceTabTarget; now: number; + focus: boolean; }): OpenTabInLayoutResult { const layout = asInternalLayout(input.layout); const focusedPane = @@ -1034,20 +1035,22 @@ function insertNewTabIntoFocusedPane(input: { createdAt: input.now, }; + const preservedFocusTabId = focusedPane.focusedTabId ?? tabId; + return { tabId, layout: { root: insertTabIntoPane(layout.root, { paneId: focusedPane.id, tab: nextTab, - focusTabId: tabId, + focusTabId: input.focus ? tabId : preservedFocusTabId, }), - focusedPaneId: focusedPane.id, + focusedPaneId: input.focus ? focusedPane.id : layout.focusedPaneId, }, }; } -export function openTabInLayout(input: OpenTabInLayoutInput): OpenTabInLayoutResult { +export function openTabInLayoutFocused(input: OpenTabInLayoutInput): OpenTabInLayoutResult { const layout = asInternalLayout(input.layout); const existingTab = collectAllTabs(layout.root).find((tab) => workspaceTabTargetsEqual(tab.target, input.target), @@ -1063,7 +1066,19 @@ export function openTabInLayout(input: OpenTabInLayoutInput): OpenTabInLayoutRes }; } - return insertNewTabIntoFocusedPane(input); + return insertNewTabIntoFocusedPane({ ...input, focus: true }); +} + +export function openTabInLayoutBackground(input: OpenTabInLayoutInput): OpenTabInLayoutResult { + const layout = asInternalLayout(input.layout); + const existingTab = collectAllTabs(layout.root).find((tab) => + workspaceTabTargetsEqual(tab.target, input.target), + ); + if (existingTab) { + return { tabId: existingTab.tabId, layout: input.layout }; + } + + return insertNewTabIntoFocusedPane({ ...input, focus: false }); } export function closeTabInLayout(input: CloseTabInLayoutInput): WorkspaceLayout | null { diff --git a/packages/app/src/stores/workspace-layout-store.test.ts b/packages/app/src/stores/workspace-layout-store.test.ts index 32a6a4f69..02457e36a 100644 --- a/packages/app/src/stores/workspace-layout-store.test.ts +++ b/packages/app/src/stores/workspace-layout-store.test.ts @@ -236,8 +236,14 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - const firstTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); - const secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); + const firstTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/a.ts", + }); + const secondTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/b.ts", + }); const splitPaneId = store.splitPane(workspaceKey, { tabId: secondTabId!, targetPaneId: "main", @@ -247,7 +253,7 @@ describe("workspace-layout-store actions", () => { expect(splitPaneId).toBe("pane_aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); store.focusPane(workspaceKey, "main"); - const duplicateTabId = store.openTab(workspaceKey, { + const duplicateTabId = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts", }); @@ -263,12 +269,55 @@ describe("workspace-layout-store actions", () => { ]); }); + it("openTabInBackground inserts a tab without stealing focus", () => { + const workspaceKey = createWorkspaceKey(); + const store = useWorkspaceLayoutStore.getState(); + + const agentTabId = store.openTabFocused(workspaceKey, { kind: "agent", agentId: "agent-1" }); + const setupTabId = store.openTabInBackground(workspaceKey, { + kind: "setup", + workspaceId: "ws-main", + }); + const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!; + const pane = findPaneById(layout.root, "main")!; + + expect(agentTabId).toBe("agent_agent-1"); + expect(setupTabId).toBe("setup_ws-main"); + expect(pane.tabIds).toEqual([agentTabId, setupTabId]); + expect(pane.focusedTabId).toBe(agentTabId); + expect(layout.focusedPaneId).toBe("main"); + }); + + it("openTabInBackground on an existing target is a no-op", () => { + const workspaceKey = createWorkspaceKey(); + const store = useWorkspaceLayoutStore.getState(); + + const firstTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/a.ts", + }); + const secondTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/b.ts", + }); + const duplicateTabId = store.openTabInBackground(workspaceKey, { + kind: "file", + path: "/repo/worktree/a.ts", + }); + const layoutAfter = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!; + const pane = findPaneById(layoutAfter.root, "main")!; + + expect(duplicateTabId).toBe(firstTabId); + expect(pane.tabIds).toEqual([firstTabId, secondTabId]); + expect(pane.focusedTabId).toBe(secondTabId); + }); + it("openTab creates distinct draft tabs for repeated Cmd+T/new-tab opens", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - const firstTabId = store.openTab(workspaceKey, { kind: "draft", draftId: "draft-1" }); - const secondTabId = store.openTab(workspaceKey, { kind: "draft", draftId: "draft-2" }); + const firstTabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-1" }); + const secondTabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-2" }); const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!; expect(firstTabId).toBe("draft-1"); @@ -296,12 +345,15 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); + store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); const newPaneId = store.splitPaneEmpty(workspaceKey, { targetPaneId: "main", position: "right", }); - const draftTabId = store.openTab(workspaceKey, { kind: "draft", draftId: "draft-split" }); + const draftTabId = store.openTabFocused(workspaceKey, { + kind: "draft", + draftId: "draft-split", + }); const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!; expect(newPaneId).toBe("pane_77777777-7777-7777-7777-777777777777"); @@ -319,8 +371,14 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - const fileTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); - const terminalTabId = store.openTab(workspaceKey, { kind: "terminal", terminalId: "term-1" }); + const fileTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/a.ts", + }); + const terminalTabId = store.openTabFocused(workspaceKey, { + kind: "terminal", + terminalId: "term-1", + }); const splitPaneId = store.splitPane(workspaceKey, { tabId: terminalTabId!, targetPaneId: "main", @@ -345,8 +403,8 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); - const secondTabId = store.openTab(workspaceKey, { kind: "draft", draftId: "draft-2" }); + store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); + const secondTabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-2" }); const splitPaneId = store.splitPane(workspaceKey, { tabId: secondTabId!, targetPaneId: "main", @@ -373,7 +431,10 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - const draftTabId = store.openTab(workspaceKey, { kind: "draft", draftId: "draft-retarget" }); + const draftTabId = store.openTabFocused(workspaceKey, { + kind: "draft", + draftId: "draft-retarget", + }); const nextTabId = store.retargetTab(workspaceKey, draftTabId!, { kind: "file", path: "/repo/worktree/retargeted.ts", @@ -399,17 +460,20 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - const existingFileTabId = store.openTab(workspaceKey, { + const existingFileTabId = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/existing.ts", }); - const draftTabId = store.openTab(workspaceKey, { kind: "draft", draftId: "draft-dup" }); + const draftTabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-dup" }); const splitPaneId = store.splitPane(workspaceKey, { tabId: draftTabId!, targetPaneId: "main", position: "right", }); - const secondDraftTabId = store.openTab(workspaceKey, { kind: "draft", draftId: "draft-dup-2" }); + const secondDraftTabId = store.openTabFocused(workspaceKey, { + kind: "draft", + draftId: "draft-dup-2", + }); const nextTabId = store.retargetTab(workspaceKey, secondDraftTabId!, { kind: "file", @@ -433,7 +497,7 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - const firstDraftTabId = store.openTab(workspaceKey, { + const firstDraftTabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-agent-1", }); @@ -441,7 +505,7 @@ describe("workspace-layout-store actions", () => { kind: "agent", agentId: "agent-1", }); - const secondDraftTabId = store.openTab(workspaceKey, { + const secondDraftTabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-agent-2", }); @@ -468,9 +532,18 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - const firstTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); - const secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); - const thirdTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" }); + const firstTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/a.ts", + }); + const secondTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/b.ts", + }); + const thirdTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/c.ts", + }); store.reorderTabs(workspaceKey, [thirdTabId!, firstTabId!]); const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!; @@ -506,10 +579,19 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); - const secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); - const thirdTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" }); - const fourthTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/d.ts" }); + store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); + const secondTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/b.ts", + }); + const thirdTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/c.ts", + }); + const fourthTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/d.ts", + }); const splitPaneId = store.splitPane(workspaceKey, { tabId: thirdTabId!, targetPaneId: "main", @@ -549,8 +631,11 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); - const secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); + store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); + const secondTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/b.ts", + }); const splitPaneId = store.splitPane(workspaceKey, { tabId: secondTabId!, targetPaneId: "main", @@ -575,8 +660,11 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); - const secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); + store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); + const secondTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/b.ts", + }); const splitPaneId = store.splitPane(workspaceKey, { tabId: secondTabId!, targetPaneId: "main", @@ -604,11 +692,11 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - const a = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); - const b = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); - const c = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" }); - const d = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/d.ts" }); - const e = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/e.ts" }); + const a = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); + const b = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); + const c = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" }); + const d = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/d.ts" }); + const e = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/e.ts" }); expect(a).toBeTruthy(); const pane1 = store.splitPane(workspaceKey, { @@ -647,8 +735,14 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - const leftTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); - const rightTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); + const leftTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/a.ts", + }); + const rightTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/b.ts", + }); const splitPaneId = store.splitPane(workspaceKey, { tabId: rightTabId!, targetPaneId: "main", @@ -675,9 +769,15 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); - const secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); - const thirdTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" }); + store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); + const secondTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/b.ts", + }); + const thirdTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/c.ts", + }); const paneBId = store.splitPane(workspaceKey, { tabId: secondTabId!, targetPaneId: "main", @@ -732,8 +832,11 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); - const secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); + store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); + const secondTabId = store.openTabFocused(workspaceKey, { + kind: "file", + path: "/repo/worktree/b.ts", + }); const splitPaneId = store.splitPane(workspaceKey, { tabId: secondTabId!, targetPaneId: "main", @@ -741,7 +844,7 @@ describe("workspace-layout-store actions", () => { }); store.focusPane(workspaceKey, "main"); - const duplicateTabId = store.openTab(workspaceKey, { + const duplicateTabId = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts", }); @@ -765,9 +868,9 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - const a = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); - const b = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); - const c = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" }); + const a = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); + const b = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); + const c = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" }); expect(a).toBeTruthy(); const rightPaneId = store.splitPane(workspaceKey, { @@ -802,7 +905,7 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - const tabId = store.openTab(workspaceKey, { kind: "draft", draftId: "draft-1" }); + const tabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-1" }); store.closeTab(workspaceKey, tabId!); const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!; @@ -888,8 +991,11 @@ describe("workspace-layout-store actions", () => { const workspaceKey = createWorkspaceKey(); const store = useWorkspaceLayoutStore.getState(); - const draftTabId = store.openTab(workspaceKey, { kind: "draft", draftId: "draft-existing" }); - const agentTabId = store.openTab(workspaceKey, { kind: "agent", agentId: "agent-1" }); + const draftTabId = store.openTabFocused(workspaceKey, { + kind: "draft", + draftId: "draft-existing", + }); + const agentTabId = store.openTabFocused(workspaceKey, { kind: "agent", agentId: "agent-1" }); const splitPaneId = store.splitPane(workspaceKey, { tabId: agentTabId!, targetPaneId: "main", @@ -1006,7 +1112,7 @@ describe("workspace-layout-store actions", () => { const store = useWorkspaceLayoutStore.getState(); store.hideAgent(workspaceKey, "agent-1"); - store.openTab(workspaceKey, { kind: "agent", agentId: "agent-1" }); + store.openTabFocused(workspaceKey, { kind: "agent", agentId: "agent-1" }); const state = useWorkspaceLayoutStore.getState(); expect(state.hiddenAgentIdsByWorkspace[workspaceKey]).toBeUndefined(); @@ -1034,7 +1140,7 @@ describe("workspace-layout-store actions", () => { const store = useWorkspaceLayoutStore.getState(); store.hideAgent(workspaceKey, "agent-1"); - const tabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); + const tabId = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); store.retargetTab(workspaceKey, tabId!, { kind: "agent", agentId: "agent-1" }); const state = useWorkspaceLayoutStore.getState(); diff --git a/packages/app/src/stores/workspace-layout-store.ts b/packages/app/src/stores/workspace-layout-store.ts index 3f39b174e..bd633b9e4 100644 --- a/packages/app/src/stores/workspace-layout-store.ts +++ b/packages/app/src/stores/workspace-layout-store.ts @@ -21,7 +21,8 @@ import { insertSplit, moveTabToPaneInLayout, normalizeLayout, - openTabInLayout, + openTabInLayoutBackground, + openTabInLayoutFocused, reconcileWorkspaceTabs, removePaneFromTree, removeTabFromTree, @@ -66,7 +67,8 @@ interface WorkspaceLayoutStore { splitSizesByWorkspace: Record>; pinnedAgentIdsByWorkspace: Record>; hiddenAgentIdsByWorkspace: Record>; - openTab: (workspaceKey: string, target: WorkspaceTabTarget) => string | null; + openTabFocused: (workspaceKey: string, target: WorkspaceTabTarget) => string | null; + openTabInBackground: (workspaceKey: string, target: WorkspaceTabTarget) => string | null; closeTab: (workspaceKey: string, tabId: string) => void; focusTab: (workspaceKey: string, tabId: string) => void; retargetTab: (workspaceKey: string, tabId: string, target: WorkspaceTabTarget) => string | null; @@ -165,14 +167,44 @@ export const useWorkspaceLayoutStore = create()( splitSizesByWorkspace: {}, pinnedAgentIdsByWorkspace: {}, hiddenAgentIdsByWorkspace: {}, - openTab: (workspaceKey, target) => { + openTabFocused: (workspaceKey, target) => { const normalizedWorkspaceKey = trimNonEmpty(workspaceKey); const normalizedTarget = normalizeWorkspaceTabTarget(target); if (!normalizedWorkspaceKey || !normalizedTarget) { return null; } - const result = openTabInLayout({ + const result = openTabInLayoutFocused({ + layout: getWorkspaceLayout(get().layoutByWorkspace, normalizedWorkspaceKey), + target: normalizedTarget, + now: Date.now(), + }); + + set((state) => ({ + hiddenAgentIdsByWorkspace: + normalizedTarget.kind !== "agent" + ? state.hiddenAgentIdsByWorkspace + : removeAgentIdFromWorkspaceSet( + state.hiddenAgentIdsByWorkspace, + normalizedWorkspaceKey, + normalizedTarget.agentId, + ), + layoutByWorkspace: { + ...state.layoutByWorkspace, + [normalizedWorkspaceKey]: result.layout, + }, + })); + + return result.tabId; + }, + openTabInBackground: (workspaceKey, target) => { + const normalizedWorkspaceKey = trimNonEmpty(workspaceKey); + const normalizedTarget = normalizeWorkspaceTabTarget(target); + if (!normalizedWorkspaceKey || !normalizedTarget) { + return null; + } + + const result = openTabInLayoutBackground({ layout: getWorkspaceLayout(get().layoutByWorkspace, normalizedWorkspaceKey), target: normalizedTarget, now: Date.now(), diff --git a/packages/app/src/utils/workspace-navigation.ts b/packages/app/src/utils/workspace-navigation.ts index bad58ed5e..50abeb0de 100644 --- a/packages/app/src/utils/workspace-navigation.ts +++ b/packages/app/src/utils/workspace-navigation.ts @@ -33,11 +33,7 @@ export function prepareWorkspaceTab(input: PrepareWorkspaceTabInput) { workspaceId: input.workspaceId, }) ?? ""; - const tabId = useWorkspaceLayoutStore.getState().openTab(key, target); - - if (tabId) { - useWorkspaceLayoutStore.getState().focusTab(key, tabId); - } + useWorkspaceLayoutStore.getState().openTabFocused(key, target); if (input.pin && target.kind === "agent") { useWorkspaceLayoutStore.getState().pinAgent(key, target.agentId);