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() {
return (workspaceKey: string) =>
useWorkspaceLayoutStore.getState().openTab(workspaceKey, {
useWorkspaceLayoutStore.getState().openTabFocused(workspaceKey, {
kind: "draft",
draftId: generateDraftId(),
});
+1 -1
View File
@@ -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(),
}),
@@ -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,
@@ -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 {
@@ -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();
@@ -21,7 +21,8 @@ import {
insertSplit,
moveTabToPaneInLayout,
normalizeLayout,
openTabInLayout,
openTabInLayoutBackground,
openTabInLayoutFocused,
reconcileWorkspaceTabs,
removePaneFromTree,
removeTabFromTree,
@@ -66,7 +67,8 @@ interface WorkspaceLayoutStore {
splitSizesByWorkspace: Record<string, Record<string, number[]>>;
pinnedAgentIdsByWorkspace: 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;
focusTab: (workspaceKey: string, tabId: string) => void;
retargetTab: (workspaceKey: string, tabId: string, target: WorkspaceTabTarget) => string | null;
@@ -165,14 +167,44 @@ export const useWorkspaceLayoutStore = create<WorkspaceLayoutStore>()(
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(),
@@ -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);