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.
This commit is contained in:
Mohamed Boudra
2026-04-17 14:45:22 +07:00
parent 9a8b01a738
commit 13bfdd8cc1
7 changed files with 232 additions and 110 deletions
@@ -39,7 +39,7 @@ const WORKSPACE_ID = "/repo/project";
function createOpenDraftTab() { function createOpenDraftTab() {
return (workspaceKey: string) => return (workspaceKey: string) =>
useWorkspaceLayoutStore.getState().openTab(workspaceKey, { useWorkspaceLayoutStore.getState().openTabFocused(workspaceKey, {
kind: "draft", kind: "draft",
draftId: generateDraftId(), draftId: generateDraftId(),
}); });
+1 -1
View File
@@ -71,7 +71,7 @@ export function useOpenProject(serverId: string | null): (path: string) => Promi
mergeWorkspaces, mergeWorkspaces,
setHasHydratedWorkspaces, setHasHydratedWorkspaces,
openDraftTab: (workspaceKey: string) => openDraftTab: (workspaceKey: string) =>
useWorkspaceLayoutStore.getState().openTab(workspaceKey, { useWorkspaceLayoutStore.getState().openTabFocused(workspaceKey, {
kind: "draft", kind: "draft",
draftId: generateDraftId(), draftId: generateDraftId(),
}), }),
@@ -713,12 +713,9 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
if (input?.paneId) { if (input?.paneId) {
focusWorkspacePane(workspaceKey, input.paneId); focusWorkspacePane(workspaceKey, input.paneId);
} }
const tabId = useWorkspaceLayoutStore useWorkspaceLayoutStore
.getState() .getState()
.openTab(workspaceKey, { kind: "terminal", terminalId: createdTerminal.id }); .openTabFocused(workspaceKey, { kind: "terminal", terminalId: createdTerminal.id });
if (tabId) {
useWorkspaceLayoutStore.getState().focusTab(workspaceKey, tabId);
}
} }
}, },
}); });
@@ -911,7 +908,10 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
() => (workspaceLayout ? collectAllTabs(workspaceLayout.root) : EMPTY_UI_TABS), () => (workspaceLayout ? collectAllTabs(workspaceLayout.root) : EMPTY_UI_TABS),
[workspaceLayout], [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 focusWorkspaceTab = useWorkspaceLayoutStore((state) => state.focusTab);
const closeWorkspaceTab = useWorkspaceLayoutStore((state) => state.closeTab); const closeWorkspaceTab = useWorkspaceLayoutStore((state) => state.closeTab);
const unpinWorkspaceAgent = useWorkspaceLayoutStore((state) => state.unpinAgent); const unpinWorkspaceAgent = useWorkspaceLayoutStore((state) => state.unpinAgent);
@@ -989,26 +989,9 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
if (!persistenceKey) { if (!persistenceKey) {
return null; return null;
} }
return openWorkspaceTabInBackground(persistenceKey, target);
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;
}, },
[focusWorkspaceTab, focusedPaneTabState, openWorkspaceTab, persistenceKey, uiTabs], [openWorkspaceTabInBackground, persistenceKey],
); );
const openWorkspaceDraftTab = useCallback( const openWorkspaceDraftTab = useCallback(
@@ -1023,16 +1006,11 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
}); });
invariant(target?.kind === "draft", "Draft tab target must be valid"); invariant(target?.kind === "draft", "Draft tab target must be valid");
if (input?.focus === false) { if (input?.focus === false) {
return ensureWorkspaceTab(target); return openWorkspaceTabInBackground(persistenceKey, target);
} }
return openWorkspaceTabFocused(persistenceKey, target);
const tabId = openWorkspaceTab(persistenceKey, target);
if (tabId) {
focusWorkspaceTab(persistenceKey, tabId);
}
return tabId;
}, },
[ensureWorkspaceTab, focusWorkspaceTab, openWorkspaceTab, persistenceKey], [openWorkspaceTabFocused, openWorkspaceTabInBackground, persistenceKey],
); );
useEffect(() => { useEffect(() => {
@@ -1156,18 +1134,16 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
return; return;
} }
const tabId = openWorkspaceTab(persistenceKey, target); const tabId = openWorkspaceTabInBackground(persistenceKey, target);
if (!tabId) { if (!tabId) {
return; return;
} }
focusWorkspaceTab(persistenceKey, tabId);
autoOpenedSetupTabWorkspaceRef.current = persistenceKey; autoOpenedSetupTabWorkspaceRef.current = persistenceKey;
}, [ }, [
focusWorkspaceTab,
hasSetupTab, hasSetupTab,
normalizedWorkspaceId, normalizedWorkspaceId,
openWorkspaceTab, openWorkspaceTabInBackground,
persistenceKey, persistenceKey,
workspaceSetupSnapshot, workspaceSetupSnapshot,
]); ]);
@@ -1180,12 +1156,12 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
if (!persistenceKey) { if (!persistenceKey) {
return; return;
} }
const tabId = openWorkspaceTab(persistenceKey, { kind: "file", path: filePath }); const tabId = openWorkspaceTabFocused(persistenceKey, { kind: "file", path: filePath });
if (tabId) { if (tabId) {
navigateToTabId(tabId); navigateToTabId(tabId);
} }
}, },
[closeToAgent, isMobile, navigateToTabId, openWorkspaceTab, persistenceKey], [closeToAgent, isMobile, navigateToTabId, openWorkspaceTabFocused, persistenceKey],
); );
const handleOpenFileFromChat = useCallback( const handleOpenFileFromChat = useCallback(
@@ -1520,11 +1496,8 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
if (!target) { if (!target) {
return; return;
} }
const tabId = openWorkspaceTab(persistenceKey, target); openWorkspaceTabFocused(persistenceKey, target);
if (tabId) { }, [normalizedWorkspaceId, openWorkspaceTabFocused, persistenceKey]);
focusWorkspaceTab(persistenceKey, tabId);
}
}, [focusWorkspaceTab, normalizedWorkspaceId, openWorkspaceTab, persistenceKey]);
const handleBulkCloseTabs = useCallback( const handleBulkCloseTabs = useCallback(
async (input: { tabsToClose: WorkspaceTabDescriptor[]; title: string; logLabel: string }) => { async (input: { tabsToClose: WorkspaceTabDescriptor[]; title: string; logLabel: string }) => {
@@ -1841,7 +1814,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
if (input.focusPaneBeforeOpen && input.paneId) { if (input.focusPaneBeforeOpen && input.paneId) {
focusWorkspacePane(persistenceKey, input.paneId); focusWorkspacePane(persistenceKey, input.paneId);
} }
const tabId = openWorkspaceTab(persistenceKey, target); const tabId = openWorkspaceTabFocused(persistenceKey, target);
if (tabId) { if (tabId) {
navigateToTabId(tabId); navigateToTabId(tabId);
} }
@@ -1873,7 +1846,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
navigateToTabId, navigateToTabId,
normalizedServerId, normalizedServerId,
normalizedWorkspaceId, normalizedWorkspaceId,
openWorkspaceTab, openWorkspaceTabFocused,
persistenceKey, persistenceKey,
convertWorkspaceDraftToAgent, convertWorkspaceDraftToAgent,
retargetWorkspaceTab, retargetWorkspaceTab,
@@ -1019,6 +1019,7 @@ function insertNewTabIntoFocusedPane(input: {
layout: WorkspaceLayout; layout: WorkspaceLayout;
target: WorkspaceTabTarget; target: WorkspaceTabTarget;
now: number; now: number;
focus: boolean;
}): OpenTabInLayoutResult { }): OpenTabInLayoutResult {
const layout = asInternalLayout(input.layout); const layout = asInternalLayout(input.layout);
const focusedPane = const focusedPane =
@@ -1034,20 +1035,22 @@ function insertNewTabIntoFocusedPane(input: {
createdAt: input.now, createdAt: input.now,
}; };
const preservedFocusTabId = focusedPane.focusedTabId ?? tabId;
return { return {
tabId, tabId,
layout: { layout: {
root: insertTabIntoPane(layout.root, { root: insertTabIntoPane(layout.root, {
paneId: focusedPane.id, paneId: focusedPane.id,
tab: nextTab, 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 layout = asInternalLayout(input.layout);
const existingTab = collectAllTabs(layout.root).find((tab) => const existingTab = collectAllTabs(layout.root).find((tab) =>
workspaceTabTargetsEqual(tab.target, input.target), 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 { export function closeTabInLayout(input: CloseTabInLayoutInput): WorkspaceLayout | null {
@@ -236,8 +236,14 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); const store = useWorkspaceLayoutStore.getState();
const firstTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); const firstTabId = store.openTabFocused(workspaceKey, {
const secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); kind: "file",
path: "/repo/worktree/a.ts",
});
const secondTabId = store.openTabFocused(workspaceKey, {
kind: "file",
path: "/repo/worktree/b.ts",
});
const splitPaneId = store.splitPane(workspaceKey, { const splitPaneId = store.splitPane(workspaceKey, {
tabId: secondTabId!, tabId: secondTabId!,
targetPaneId: "main", targetPaneId: "main",
@@ -247,7 +253,7 @@ describe("workspace-layout-store actions", () => {
expect(splitPaneId).toBe("pane_aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); expect(splitPaneId).toBe("pane_aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
store.focusPane(workspaceKey, "main"); store.focusPane(workspaceKey, "main");
const duplicateTabId = store.openTab(workspaceKey, { const duplicateTabId = store.openTabFocused(workspaceKey, {
kind: "file", kind: "file",
path: "/repo/worktree/b.ts", 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", () => { it("openTab creates distinct draft tabs for repeated Cmd+T/new-tab opens", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); const store = useWorkspaceLayoutStore.getState();
const firstTabId = store.openTab(workspaceKey, { kind: "draft", draftId: "draft-1" }); const firstTabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-1" });
const secondTabId = store.openTab(workspaceKey, { kind: "draft", draftId: "draft-2" }); const secondTabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-2" });
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!; const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
expect(firstTabId).toBe("draft-1"); expect(firstTabId).toBe("draft-1");
@@ -296,12 +345,15 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); 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, { const newPaneId = store.splitPaneEmpty(workspaceKey, {
targetPaneId: "main", targetPaneId: "main",
position: "right", 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]!; const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
expect(newPaneId).toBe("pane_77777777-7777-7777-7777-777777777777"); expect(newPaneId).toBe("pane_77777777-7777-7777-7777-777777777777");
@@ -319,8 +371,14 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); const store = useWorkspaceLayoutStore.getState();
const fileTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); const fileTabId = store.openTabFocused(workspaceKey, {
const terminalTabId = store.openTab(workspaceKey, { kind: "terminal", terminalId: "term-1" }); kind: "file",
path: "/repo/worktree/a.ts",
});
const terminalTabId = store.openTabFocused(workspaceKey, {
kind: "terminal",
terminalId: "term-1",
});
const splitPaneId = store.splitPane(workspaceKey, { const splitPaneId = store.splitPane(workspaceKey, {
tabId: terminalTabId!, tabId: terminalTabId!,
targetPaneId: "main", targetPaneId: "main",
@@ -345,8 +403,8 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); 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 secondTabId = store.openTab(workspaceKey, { kind: "draft", draftId: "draft-2" }); const secondTabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-2" });
const splitPaneId = store.splitPane(workspaceKey, { const splitPaneId = store.splitPane(workspaceKey, {
tabId: secondTabId!, tabId: secondTabId!,
targetPaneId: "main", targetPaneId: "main",
@@ -373,7 +431,10 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); 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!, { const nextTabId = store.retargetTab(workspaceKey, draftTabId!, {
kind: "file", kind: "file",
path: "/repo/worktree/retargeted.ts", path: "/repo/worktree/retargeted.ts",
@@ -399,17 +460,20 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); const store = useWorkspaceLayoutStore.getState();
const existingFileTabId = store.openTab(workspaceKey, { const existingFileTabId = store.openTabFocused(workspaceKey, {
kind: "file", kind: "file",
path: "/repo/worktree/existing.ts", 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, { const splitPaneId = store.splitPane(workspaceKey, {
tabId: draftTabId!, tabId: draftTabId!,
targetPaneId: "main", targetPaneId: "main",
position: "right", 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!, { const nextTabId = store.retargetTab(workspaceKey, secondDraftTabId!, {
kind: "file", kind: "file",
@@ -433,7 +497,7 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); const store = useWorkspaceLayoutStore.getState();
const firstDraftTabId = store.openTab(workspaceKey, { const firstDraftTabId = store.openTabFocused(workspaceKey, {
kind: "draft", kind: "draft",
draftId: "draft-agent-1", draftId: "draft-agent-1",
}); });
@@ -441,7 +505,7 @@ describe("workspace-layout-store actions", () => {
kind: "agent", kind: "agent",
agentId: "agent-1", agentId: "agent-1",
}); });
const secondDraftTabId = store.openTab(workspaceKey, { const secondDraftTabId = store.openTabFocused(workspaceKey, {
kind: "draft", kind: "draft",
draftId: "draft-agent-2", draftId: "draft-agent-2",
}); });
@@ -468,9 +532,18 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); const store = useWorkspaceLayoutStore.getState();
const firstTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); const firstTabId = store.openTabFocused(workspaceKey, {
const secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); kind: "file",
const thirdTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" }); 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!]); store.reorderTabs(workspaceKey, [thirdTabId!, firstTabId!]);
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!; const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
@@ -506,10 +579,19 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); 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 secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); const secondTabId = store.openTabFocused(workspaceKey, {
const thirdTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" }); kind: "file",
const fourthTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/d.ts" }); 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, { const splitPaneId = store.splitPane(workspaceKey, {
tabId: thirdTabId!, tabId: thirdTabId!,
targetPaneId: "main", targetPaneId: "main",
@@ -549,8 +631,11 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); 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 secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); const secondTabId = store.openTabFocused(workspaceKey, {
kind: "file",
path: "/repo/worktree/b.ts",
});
const splitPaneId = store.splitPane(workspaceKey, { const splitPaneId = store.splitPane(workspaceKey, {
tabId: secondTabId!, tabId: secondTabId!,
targetPaneId: "main", targetPaneId: "main",
@@ -575,8 +660,11 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); 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 secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); const secondTabId = store.openTabFocused(workspaceKey, {
kind: "file",
path: "/repo/worktree/b.ts",
});
const splitPaneId = store.splitPane(workspaceKey, { const splitPaneId = store.splitPane(workspaceKey, {
tabId: secondTabId!, tabId: secondTabId!,
targetPaneId: "main", targetPaneId: "main",
@@ -604,11 +692,11 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); const store = useWorkspaceLayoutStore.getState();
const a = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); const a = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" });
const b = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); const b = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" });
const c = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" }); const c = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" });
const d = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/d.ts" }); const d = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/d.ts" });
const e = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/e.ts" }); const e = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/e.ts" });
expect(a).toBeTruthy(); expect(a).toBeTruthy();
const pane1 = store.splitPane(workspaceKey, { const pane1 = store.splitPane(workspaceKey, {
@@ -647,8 +735,14 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); const store = useWorkspaceLayoutStore.getState();
const leftTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); const leftTabId = store.openTabFocused(workspaceKey, {
const rightTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); kind: "file",
path: "/repo/worktree/a.ts",
});
const rightTabId = store.openTabFocused(workspaceKey, {
kind: "file",
path: "/repo/worktree/b.ts",
});
const splitPaneId = store.splitPane(workspaceKey, { const splitPaneId = store.splitPane(workspaceKey, {
tabId: rightTabId!, tabId: rightTabId!,
targetPaneId: "main", targetPaneId: "main",
@@ -675,9 +769,15 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); 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 secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); const secondTabId = store.openTabFocused(workspaceKey, {
const thirdTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" }); kind: "file",
path: "/repo/worktree/b.ts",
});
const thirdTabId = store.openTabFocused(workspaceKey, {
kind: "file",
path: "/repo/worktree/c.ts",
});
const paneBId = store.splitPane(workspaceKey, { const paneBId = store.splitPane(workspaceKey, {
tabId: secondTabId!, tabId: secondTabId!,
targetPaneId: "main", targetPaneId: "main",
@@ -732,8 +832,11 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); 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 secondTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); const secondTabId = store.openTabFocused(workspaceKey, {
kind: "file",
path: "/repo/worktree/b.ts",
});
const splitPaneId = store.splitPane(workspaceKey, { const splitPaneId = store.splitPane(workspaceKey, {
tabId: secondTabId!, tabId: secondTabId!,
targetPaneId: "main", targetPaneId: "main",
@@ -741,7 +844,7 @@ describe("workspace-layout-store actions", () => {
}); });
store.focusPane(workspaceKey, "main"); store.focusPane(workspaceKey, "main");
const duplicateTabId = store.openTab(workspaceKey, { const duplicateTabId = store.openTabFocused(workspaceKey, {
kind: "file", kind: "file",
path: "/repo/worktree/b.ts", path: "/repo/worktree/b.ts",
}); });
@@ -765,9 +868,9 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); const store = useWorkspaceLayoutStore.getState();
const a = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" }); const a = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" });
const b = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" }); const b = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" });
const c = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" }); const c = store.openTabFocused(workspaceKey, { kind: "file", path: "/repo/worktree/c.ts" });
expect(a).toBeTruthy(); expect(a).toBeTruthy();
const rightPaneId = store.splitPane(workspaceKey, { const rightPaneId = store.splitPane(workspaceKey, {
@@ -802,7 +905,7 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); 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!); store.closeTab(workspaceKey, tabId!);
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!; const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
@@ -888,8 +991,11 @@ describe("workspace-layout-store actions", () => {
const workspaceKey = createWorkspaceKey(); const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState(); const store = useWorkspaceLayoutStore.getState();
const draftTabId = store.openTab(workspaceKey, { kind: "draft", draftId: "draft-existing" }); const draftTabId = store.openTabFocused(workspaceKey, {
const agentTabId = store.openTab(workspaceKey, { kind: "agent", agentId: "agent-1" }); kind: "draft",
draftId: "draft-existing",
});
const agentTabId = store.openTabFocused(workspaceKey, { kind: "agent", agentId: "agent-1" });
const splitPaneId = store.splitPane(workspaceKey, { const splitPaneId = store.splitPane(workspaceKey, {
tabId: agentTabId!, tabId: agentTabId!,
targetPaneId: "main", targetPaneId: "main",
@@ -1006,7 +1112,7 @@ describe("workspace-layout-store actions", () => {
const store = useWorkspaceLayoutStore.getState(); const store = useWorkspaceLayoutStore.getState();
store.hideAgent(workspaceKey, "agent-1"); 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(); const state = useWorkspaceLayoutStore.getState();
expect(state.hiddenAgentIdsByWorkspace[workspaceKey]).toBeUndefined(); expect(state.hiddenAgentIdsByWorkspace[workspaceKey]).toBeUndefined();
@@ -1034,7 +1140,7 @@ describe("workspace-layout-store actions", () => {
const store = useWorkspaceLayoutStore.getState(); const store = useWorkspaceLayoutStore.getState();
store.hideAgent(workspaceKey, "agent-1"); 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" }); store.retargetTab(workspaceKey, tabId!, { kind: "agent", agentId: "agent-1" });
const state = useWorkspaceLayoutStore.getState(); const state = useWorkspaceLayoutStore.getState();
@@ -21,7 +21,8 @@ import {
insertSplit, insertSplit,
moveTabToPaneInLayout, moveTabToPaneInLayout,
normalizeLayout, normalizeLayout,
openTabInLayout, openTabInLayoutBackground,
openTabInLayoutFocused,
reconcileWorkspaceTabs, reconcileWorkspaceTabs,
removePaneFromTree, removePaneFromTree,
removeTabFromTree, removeTabFromTree,
@@ -66,7 +67,8 @@ interface WorkspaceLayoutStore {
splitSizesByWorkspace: Record<string, Record<string, number[]>>; splitSizesByWorkspace: Record<string, Record<string, number[]>>;
pinnedAgentIdsByWorkspace: Record<string, Set<string>>; pinnedAgentIdsByWorkspace: Record<string, Set<string>>;
hiddenAgentIdsByWorkspace: Record<string, Set<string>>; hiddenAgentIdsByWorkspace: Record<string, Set<string>>;
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; closeTab: (workspaceKey: string, tabId: string) => void;
focusTab: (workspaceKey: string, tabId: string) => void; focusTab: (workspaceKey: string, tabId: string) => void;
retargetTab: (workspaceKey: string, tabId: string, target: WorkspaceTabTarget) => string | null; retargetTab: (workspaceKey: string, tabId: string, target: WorkspaceTabTarget) => string | null;
@@ -165,14 +167,44 @@ export const useWorkspaceLayoutStore = create<WorkspaceLayoutStore>()(
splitSizesByWorkspace: {}, splitSizesByWorkspace: {},
pinnedAgentIdsByWorkspace: {}, pinnedAgentIdsByWorkspace: {},
hiddenAgentIdsByWorkspace: {}, hiddenAgentIdsByWorkspace: {},
openTab: (workspaceKey, target) => { openTabFocused: (workspaceKey, target) => {
const normalizedWorkspaceKey = trimNonEmpty(workspaceKey); const normalizedWorkspaceKey = trimNonEmpty(workspaceKey);
const normalizedTarget = normalizeWorkspaceTabTarget(target); const normalizedTarget = normalizeWorkspaceTabTarget(target);
if (!normalizedWorkspaceKey || !normalizedTarget) { if (!normalizedWorkspaceKey || !normalizedTarget) {
return null; 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), layout: getWorkspaceLayout(get().layoutByWorkspace, normalizedWorkspaceKey),
target: normalizedTarget, target: normalizedTarget,
now: Date.now(), now: Date.now(),
@@ -33,11 +33,7 @@ export function prepareWorkspaceTab(input: PrepareWorkspaceTabInput) {
workspaceId: input.workspaceId, workspaceId: input.workspaceId,
}) ?? ""; }) ?? "";
const tabId = useWorkspaceLayoutStore.getState().openTab(key, target); useWorkspaceLayoutStore.getState().openTabFocused(key, target);
if (tabId) {
useWorkspaceLayoutStore.getState().focusTab(key, tabId);
}
if (input.pin && target.kind === "agent") { if (input.pin && target.kind === "agent") {
useWorkspaceLayoutStore.getState().pinAgent(key, target.agentId); useWorkspaceLayoutStore.getState().pinAgent(key, target.agentId);