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);