From b55aa042d448d105a8d14689329329f78567269e Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 17 May 2026 23:06:39 +0700 Subject: [PATCH] Return to parent tabs on close --- .../screens/workspace/workspace-screen.tsx | 42 +++- .../src/stores/workspace-layout-actions.ts | 206 ++++++++++++++---- .../src/stores/workspace-layout-store.test.ts | 84 +++++++ .../app/src/stores/workspace-layout-store.ts | 69 ++++++ 4 files changed, 347 insertions(+), 54 deletions(-) diff --git a/packages/app/src/screens/workspace/workspace-screen.tsx b/packages/app/src/screens/workspace/workspace-screen.tsx index dff7355ff..25a61f089 100644 --- a/packages/app/src/screens/workspace/workspace-screen.tsx +++ b/packages/app/src/screens/workspace/workspace-screen.tsx @@ -1502,6 +1502,9 @@ function WorkspaceScreenContent({ [normalizedServerId, normalizedWorkspaceId], ); const openWorkspaceTabFocused = useWorkspaceLayoutStore((state) => state.openTabFocused); + const openWorkspaceChildTabFocused = useWorkspaceLayoutStore( + (state) => state.openChildTabFocused, + ); const focusWorkspacePane = useWorkspaceLayoutStore((state) => state.focusPane); const hasHydratedWorkspaces = useSessionStore( (state) => state.sessions[normalizedServerId]?.hasHydratedWorkspaces ?? false, @@ -2039,7 +2042,7 @@ function WorkspaceScreenContent({ ); const handleOpenFileFromChat = useCallback( - (location: WorkspaceFileLocation) => { + (location: WorkspaceFileLocation, options?: { parentTabId?: string | null }) => { const normalizedLocation = normalizeWorkspaceFileLocation(location); if (!normalizedLocation) { return; @@ -2050,25 +2053,36 @@ function WorkspaceScreenContent({ if (!persistenceKey) { return; } - const tabId = openWorkspaceTabFocused( - persistenceKey, - createWorkspaceFileTabTarget(normalizedLocation), - ); + const target = createWorkspaceFileTabTarget(normalizedLocation); + const tabId = options?.parentTabId + ? openWorkspaceChildTabFocused(persistenceKey, target, options.parentTabId) + : openWorkspaceTabFocused(persistenceKey, target); if (tabId) { navigateToTabId(tabId); } }, - [isMobile, navigateToTabId, openWorkspaceTabFocused, persistenceKey, showMobileAgent], + [ + isMobile, + navigateToTabId, + openWorkspaceChildTabFocused, + openWorkspaceTabFocused, + persistenceKey, + showMobileAgent, + ], ); const handleOpenFileFromChatInSidePane = useCallback( - (input: { location: WorkspaceFileLocation; sourcePaneId?: string }) => { + (input: { + location: WorkspaceFileLocation; + sourcePaneId?: string; + parentTabId?: string | null; + }) => { const location = normalizeWorkspaceFileLocation(input.location); if (!location) { return; } if (!persistenceKey || isMobile || !input.sourcePaneId) { - handleOpenFileFromChat(location); + handleOpenFileFromChat(location, { parentTabId: input.parentTabId }); return; } @@ -2088,7 +2102,9 @@ function WorkspaceScreenContent({ }); } - const tabId = openWorkspaceTabFocused(persistenceKey, target); + const tabId = input.parentTabId + ? openWorkspaceChildTabFocused(persistenceKey, target, input.parentTabId) + : openWorkspaceTabFocused(persistenceKey, target); if (tabId) { navigateToTabId(tabId); } @@ -2098,6 +2114,7 @@ function WorkspaceScreenContent({ isMobile, focusWorkspacePane, navigateToTabId, + openWorkspaceChildTabFocused, openWorkspaceTabFocused, persistenceKey, splitWorkspacePaneEmpty, @@ -2756,7 +2773,7 @@ function WorkspaceScreenContent({ if (input.focusPaneBeforeOpen && input.paneId) { focusWorkspacePane(persistenceKey, input.paneId); } - const tabId = openWorkspaceTabFocused(persistenceKey, target); + const tabId = openWorkspaceChildTabFocused(persistenceKey, target, input.tab.tabId); if (tabId) { navigateToTabId(tabId); } @@ -2782,10 +2799,11 @@ function WorkspaceScreenContent({ handleOpenFileFromChatInSidePane({ location: request.location, sourcePaneId: input.paneId ?? undefined, + parentTabId: input.tab.tabId, }); return; } - handleOpenFileFromChat(request.location); + handleOpenFileFromChat(request.location, { parentTabId: input.tab.tabId }); }, onOpenImportSheet: openImportSheet, }), @@ -2798,7 +2816,7 @@ function WorkspaceScreenContent({ normalizedServerId, normalizedWorkspaceId, openImportSheet, - openWorkspaceTabFocused, + openWorkspaceChildTabFocused, persistenceKey, convertWorkspaceDraftToAgent, retargetWorkspaceTab, diff --git a/packages/app/src/stores/workspace-layout-actions.ts b/packages/app/src/stores/workspace-layout-actions.ts index 3e4483057..f62301319 100644 --- a/packages/app/src/stores/workspace-layout-actions.ts +++ b/packages/app/src/stores/workspace-layout-actions.ts @@ -26,6 +26,7 @@ export type SplitNode = { kind: "pane"; pane: SplitPane } | { kind: "group"; gro export interface WorkspaceLayout { root: SplitNode; focusedPaneId: string | null; + parentTabIdByTabId?: Record; } interface SplitPaneInternal extends SplitPane { @@ -903,10 +904,16 @@ export function normalizeLayout(layout: unknown): WorkspaceLayout { collectAllPanes(root)[0]?.id ?? DEFAULT_PANE_ID); - return { + const normalizedLayout = { root, focusedPaneId: resolvedFocusedPaneId, }; + const parentTabIdByTabId = normalizeParentTabMap({ + raw: rawLayout.parentTabIdByTabId, + openTabIds: new Set(collectAllTabs(root).map((tab) => tab.tabId)), + }); + + return parentTabIdByTabId ? { ...normalizedLayout, parentTabIdByTabId } : normalizedLayout; } export function findPaneById(root: SplitNode, paneId: string | null | undefined): SplitPane | null { @@ -1040,14 +1047,15 @@ function insertNewTabIntoFocusedPane(input: { return { tabId, - layout: { + layout: withNormalizedParentTabMap({ root: insertTabIntoPane(layout.root, { paneId: focusedPane.id, tab: nextTab, focusTabId: input.focus ? tabId : preservedFocusTabId, }), focusedPaneId: input.focus ? focusedPane.id : layout.focusedPaneId, - }, + parentTabIdByTabId: input.layout.parentTabIdByTabId, + }), }; } @@ -1061,28 +1069,28 @@ function findExistingTabForTarget(root: SplitNodeInternal, target: WorkspaceTabT } function updateExistingTabTarget( - layout: { root: SplitNodeInternal; focusedPaneId: string | null }, + layout: WorkspaceLayout, tab: WorkspaceTab, target: WorkspaceTabTarget, -): { root: SplitNodeInternal; focusedPaneId: string | null } { +): WorkspaceLayout { if (workspaceTabTargetsEqual(tab.target, target)) { return layout; } - return { + return withNormalizedParentTabMap({ ...layout, - root: replaceTabInTree(layout.root, { + root: replaceTabInTree(asInternalNode(layout.root), { tabId: tab.tabId, nextTabId: tab.tabId, target, }), - }; + }); } export function openTabInLayoutFocused(input: OpenTabInLayoutInput): OpenTabInLayoutResult { const layout = asInternalLayout(input.layout); const existingTab = findExistingTabForTarget(layout.root, input.target); if (existingTab) { - const nextLayout = updateExistingTabTarget(layout, existingTab, input.target); + const nextLayout = updateExistingTabTarget(input.layout, existingTab, input.target); return { tabId: existingTab.tabId, layout: @@ -1102,7 +1110,7 @@ export function openTabInLayoutBackground(input: OpenTabInLayoutInput): OpenTabI if (existingTab) { return { tabId: existingTab.tabId, - layout: updateExistingTabTarget(layout, existingTab, input.target), + layout: updateExistingTabTarget(input.layout, existingTab, input.target), }; } @@ -1116,20 +1124,42 @@ export function closeTabInLayout(input: CloseTabInLayoutInput): WorkspaceLayout return null; } + const closeSuccessorTabId = getCloseSuccessorTabId({ + pane, + tabId: input.tabId, + openTabIds: new Set(collectAllTabs(internalLayout.root).map((tab) => tab.tabId)), + parentTabIdByTabId: input.layout.parentTabIdByTabId, + }); const fallbackPaneId = findNearestSiblingPaneId(internalLayout.root, pane.id); const nextRoot = removeTabFromTree(internalLayout.root, input.tabId) as SplitNodeInternal; - const nextFocusedPaneId = - internalLayout.focusedPaneId === null - ? null - : (findPaneById(nextRoot, internalLayout.focusedPaneId)?.id ?? - (fallbackPaneId && findPaneById(nextRoot, fallbackPaneId)?.id) ?? - collectAllPanes(nextRoot)[0]?.id ?? - DEFAULT_PANE_ID); + const parentTabIdByTabId = normalizeParentTabMap({ + raw: input.layout.parentTabIdByTabId, + openTabIds: new Set(collectAllTabs(nextRoot).map((tab) => tab.tabId)), + }); + const nextFocusedPaneId = getFocusedPaneIdAfterTabClose({ + root: nextRoot, + focusedPaneId: internalLayout.focusedPaneId, + fallbackPaneId, + }); - return { + const nextLayout = { root: nextRoot, focusedPaneId: nextFocusedPaneId, }; + const nextLayoutWithParentMap = parentTabIdByTabId + ? { ...nextLayout, parentTabIdByTabId } + : nextLayout; + + if (closeSuccessorTabId && findPaneContainingTab(nextRoot, closeSuccessorTabId)) { + const focusedLayout = + focusTabInLayout({ + layout: nextLayoutWithParentMap, + tabId: closeSuccessorTabId, + }) ?? nextLayoutWithParentMap; + return parentTabIdByTabId ? { ...focusedLayout, parentTabIdByTabId } : focusedLayout; + } + + return nextLayoutWithParentMap; } export function focusTabInLayout(input: FocusTabInLayoutInput): WorkspaceLayout | null { @@ -1143,10 +1173,11 @@ export function focusTabInLayout(input: FocusTabInLayoutInput): WorkspaceLayout return null; } - return { + return withNormalizedParentTabMap({ root: focusTabInPane(layout.root, pane.id, input.tabId), focusedPaneId: pane.id, - }; + parentTabIdByTabId: input.layout.parentTabIdByTabId, + }); } export function retargetTabInLayout( @@ -1197,14 +1228,15 @@ export function retargetTabInLayout( // target identity immediately so local tab state cannot masquerade as the // previous agent/terminal/file. tabId: nextTabId, - layout: { + layout: withNormalizedParentTabMap({ root: replaceTabInTree(layout.root, { tabId: input.tabId, nextTabId, target: input.target, }), focusedPaneId: layout.focusedPaneId, - }, + parentTabIdByTabId: input.layout.parentTabIdByTabId, + }), }; } @@ -1243,14 +1275,15 @@ export function convertDraftToAgentInLayout( return { tabId: canonicalTabId, - layout: { + layout: withNormalizedParentTabMap({ root: replaceTabInTree(layout.root, { tabId: input.tabId, nextTabId: canonicalTabId, target, }), focusedPaneId: layout.focusedPaneId, - }, + parentTabIdByTabId: input.layout.parentTabIdByTabId, + }), }; } @@ -1262,13 +1295,14 @@ export function reorderFocusedPaneTabsInLayout( return null; } - return { + return withNormalizedParentTabMap({ root: updatePaneInTree(layout.root, { paneId: layout.focusedPaneId, updater: (pane) => reorderTabsForPane({ pane, tabIds: input.tabIds }), }), focusedPaneId: layout.focusedPaneId, - }; + parentTabIdByTabId: input.layout.parentTabIdByTabId, + }); } export function splitPaneInLayout(input: SplitPaneInLayoutInput): SplitPaneInLayoutResult | null { @@ -1293,10 +1327,11 @@ export function splitPaneInLayout(input: SplitPaneInLayoutInput): SplitPaneInLay return { paneId: result.newPaneId, - layout: { + layout: withNormalizedParentTabMap({ root: result.root, focusedPaneId: result.newPaneId, - }, + parentTabIdByTabId: input.layout.parentTabIdByTabId, + }), }; } @@ -1350,10 +1385,11 @@ export function splitPaneEmptyInLayout( return { paneId: newPaneId, - layout: { + layout: withNormalizedParentTabMap({ root: nextRoot, focusedPaneId: newPaneId, - }, + parentTabIdByTabId: input.layout.parentTabIdByTabId, + }), }; } @@ -1372,14 +1408,15 @@ export function moveTabToPaneInLayout(input: MoveTabToPaneInLayoutInput): Worksp return null; } - return { + return withNormalizedParentTabMap({ root: insertTabIntoPane(detached.root, { paneId: input.toPaneId, tab: detached.tab, focusTabId: input.tabId, }), focusedPaneId: input.toPaneId, - }; + parentTabIdByTabId: input.layout.parentTabIdByTabId, + }); } export function focusPaneInLayout(input: FocusPaneInLayoutInput): WorkspaceLayout | null { @@ -1389,21 +1426,23 @@ export function focusPaneInLayout(input: FocusPaneInLayoutInput): WorkspaceLayou if (input.layout.focusedPaneId === input.paneId) { return null; } - return { + return withNormalizedParentTabMap({ root: input.layout.root, focusedPaneId: input.paneId, - }; + parentTabIdByTabId: input.layout.parentTabIdByTabId, + }); } export function resizeSplitInLayout(input: ResizeSplitInLayoutInput): WorkspaceLayout { const layout = asInternalLayout(input.layout); - return { + return withNormalizedParentTabMap({ root: updateGroupSizesInTree(layout.root, { groupId: input.groupId, sizes: input.sizes, }), focusedPaneId: layout.focusedPaneId, - }; + parentTabIdByTabId: input.layout.parentTabIdByTabId, + }); } export function reorderPaneTabsInLayout( @@ -1414,13 +1453,14 @@ export function reorderPaneTabsInLayout( return null; } - return { + return withNormalizedParentTabMap({ root: updatePaneInTree(layout.root, { paneId: input.paneId, updater: (pane) => reorderTabsForPane({ pane, tabIds: input.tabIds }), }), focusedPaneId: layout.focusedPaneId, - }; + parentTabIdByTabId: input.layout.parentTabIdByTabId, + }); } function normalizeStringSet(values: Iterable): Set { @@ -1434,6 +1474,86 @@ function normalizeStringSet(values: Iterable): Set { return next; } +function normalizeParentTabMap(input: { + raw: unknown; + openTabIds: ReadonlySet; +}): Record | undefined { + if (!input.raw || typeof input.raw !== "object" || Array.isArray(input.raw)) { + return undefined; + } + + const next: Record = {}; + for (const [rawChildId, rawParentId] of Object.entries(input.raw)) { + const childId = trimNonEmpty(rawChildId); + const parentId = trimNonEmpty(typeof rawParentId === "string" ? rawParentId : null); + if ( + !childId || + !parentId || + childId === parentId || + !input.openTabIds.has(childId) || + !input.openTabIds.has(parentId) + ) { + continue; + } + next[childId] = parentId; + } + + return Object.keys(next).length > 0 ? next : undefined; +} + +function normalizeLayoutParentTabMap(layout: WorkspaceLayout): Record | undefined { + return normalizeParentTabMap({ + raw: layout.parentTabIdByTabId, + openTabIds: new Set(collectAllTabs(layout.root).map((tab) => tab.tabId)), + }); +} + +function withNormalizedParentTabMap(layout: WorkspaceLayout): WorkspaceLayout { + const parentTabIdByTabId = normalizeLayoutParentTabMap(layout); + return parentTabIdByTabId + ? { ...layout, parentTabIdByTabId } + : { root: layout.root, focusedPaneId: layout.focusedPaneId }; +} + +function getCloseSuccessorTabId(input: { + pane: SplitPane; + tabId: string; + openTabIds: ReadonlySet; + parentTabIdByTabId?: Record; +}): string | null { + if (input.pane.focusedTabId !== input.tabId) { + return null; + } + + const tabIndex = input.pane.tabIds.indexOf(input.tabId); + const parentTabId = input.parentTabIdByTabId?.[input.tabId] ?? null; + if (parentTabId && input.openTabIds.has(parentTabId)) { + return parentTabId; + } + + return ( + input.pane.tabIds[tabIndex + 1] ?? + (tabIndex > 0 ? input.pane.tabIds[tabIndex - 1] : null) ?? + null + ); +} + +function getFocusedPaneIdAfterTabClose(input: { + root: SplitNode; + focusedPaneId: string | null; + fallbackPaneId: string | null; +}): string | null { + if (input.focusedPaneId === null) { + return null; + } + return ( + findPaneById(input.root, input.focusedPaneId)?.id ?? + (input.fallbackPaneId && findPaneById(input.root, input.fallbackPaneId)?.id) ?? + collectAllPanes(input.root)[0]?.id ?? + DEFAULT_PANE_ID + ); +} + function isEntityTarget( target: WorkspaceTabTarget, ): target is Extract { @@ -1464,7 +1584,7 @@ function openEntityTabWithoutFocusing( invariant(focusedPane, "Workspace layout must always have a pane"); const tabId = buildDeterministicWorkspaceTabId(target); - return { + return withNormalizedParentTabMap({ root: insertTabIntoPane(internalLayout.root, { paneId: focusedPane.id, tab: { @@ -1475,7 +1595,8 @@ function openEntityTabWithoutFocusing( focusTabId: focusedPane.focusedTabId ?? tabId, }), focusedPaneId: internalLayout.focusedPaneId, - }; + parentTabIdByTabId: layout.parentTabIdByTabId, + }); } interface EntityTabGroup { @@ -1654,14 +1775,15 @@ export function reconcileWorkspaceTabs( reconciledFocusedTabId = canonicalTabId; } if (keeper.tabId !== canonicalTabId || !workspaceTabTargetsEqual(keeper.target, group.target)) { - nextLayout = { + nextLayout = withNormalizedParentTabMap({ root: replaceTabInTree(asInternalLayout(nextLayout).root, { tabId: keeper.tabId, nextTabId: canonicalTabId, target: group.target, }), focusedPaneId: nextLayout.focusedPaneId, - }; + parentTabIdByTabId: nextLayout.parentTabIdByTabId, + }); } for (const tab of group.tabs) { if (tab.tabId === keeper.tabId) { diff --git a/packages/app/src/stores/workspace-layout-store.test.ts b/packages/app/src/stores/workspace-layout-store.test.ts index 318e786b0..ed5db7afc 100644 --- a/packages/app/src/stores/workspace-layout-store.test.ts +++ b/packages/app/src/stores/workspace-layout-store.test.ts @@ -429,6 +429,90 @@ describe("workspace-layout-store actions", () => { expect(pane.focusedTabId).toBe(secondTabId); }); + it("closing a focused middle tab selects the tab to its right", () => { + const workspaceKey = createWorkspaceKey(); + const firstTabId = "draft-1"; + const closedTabId = "draft-2"; + const rightTabId = "draft-3"; + + workspaceLayoutStore.setState((state) => ({ + ...state, + layoutByWorkspace: { + ...state.layoutByWorkspace, + [workspaceKey]: { + root: createPane({ + id: "main", + tabIds: [firstTabId, closedTabId, rightTabId], + focusedTabId: closedTabId, + }), + focusedPaneId: "main", + }, + }, + })); + + workspaceLayoutStore.getState().closeTab(workspaceKey, closedTabId); + const layout = workspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]; + const pane = findPaneById(layout.root, "main")!; + + expect(pane.tabIds).toEqual([firstTabId, rightTabId]); + expect(pane.focusedTabId).toBe(rightTabId); + }); + + it("closing a focused child tab returns to its parent before using tab-strip order", () => { + const workspaceKey = createWorkspaceKey(); + const store = workspaceLayoutStore.getState(); + + const parentTabId = store.openTabFocused(workspaceKey, { + kind: "draft", + draftId: "draft-parent", + }); + const childTabId = store.openChildTabFocused( + workspaceKey, + { kind: "draft", draftId: "draft-child" }, + parentTabId!, + ); + const rightTabId = store.openTabFocused(workspaceKey, { + kind: "draft", + draftId: "draft-right", + }); + store.focusTab(workspaceKey, childTabId!); + + workspaceLayoutStore.getState().closeTab(workspaceKey, childTabId!); + const layout = workspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]; + const pane = findPaneById(layout.root, "main")!; + + expect(pane.tabIds).toEqual([parentTabId, rightTabId]); + expect(pane.focusedTabId).toBe(parentTabId); + }); + + it("closing a focused last tab selects the tab to its left", () => { + const workspaceKey = createWorkspaceKey(); + const leftTabId = "draft-1"; + const closedTabId = "draft-2"; + + workspaceLayoutStore.setState((state) => ({ + ...state, + layoutByWorkspace: { + ...state.layoutByWorkspace, + [workspaceKey]: { + root: createPane({ + id: "main", + tabIds: [leftTabId, closedTabId], + focusedTabId: closedTabId, + }), + focusedPaneId: "main", + }, + }, + })); + + workspaceLayoutStore.getState().closeTab(workspaceKey, closedTabId); + const layout = workspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]; + const pane = findPaneById(layout.root, "main")!; + + expect(pane.tabIds).toEqual([leftTabId]); + expect(pane.focusedTabId).toBe(leftTabId); + }); + it("unfocuses and restores the previous focused pane", () => { const workspaceKey = createWorkspaceKey(); const store = workspaceLayoutStore.getState(); diff --git a/packages/app/src/stores/workspace-layout-store.ts b/packages/app/src/stores/workspace-layout-store.ts index 5dd90bca7..673fc210a 100644 --- a/packages/app/src/stores/workspace-layout-store.ts +++ b/packages/app/src/stores/workspace-layout-store.ts @@ -76,6 +76,11 @@ interface WorkspaceLayoutStore { hiddenAgentIdsByWorkspace: Record>; focusRestorationByWorkspace: Record; openTabFocused: (workspaceKey: string, target: WorkspaceTabTarget) => string | null; + openChildTabFocused: ( + workspaceKey: string, + target: WorkspaceTabTarget, + parentTabId: string, + ) => string | null; openTabInBackground: (workspaceKey: string, target: WorkspaceTabTarget) => string | null; closeTab: (workspaceKey: string, tabId: string) => void; focusTab: (workspaceKey: string, tabId: string) => void; @@ -188,6 +193,31 @@ function withoutFocusRestoration( return { focusRestorationByWorkspace }; } +function attachParentTab(input: { + layout: WorkspaceLayout; + childTabId: string | null; + parentTabId: string | null; +}): WorkspaceLayout { + const childTabId = trimNonEmpty(input.childTabId); + const parentTabId = trimNonEmpty(input.parentTabId); + if (!childTabId || !parentTabId || childTabId === parentTabId) { + return normalizeLayout(input.layout); + } + + const openTabIds = new Set(collectAllTabs(input.layout.root).map((tab) => tab.tabId)); + if (!openTabIds.has(childTabId) || !openTabIds.has(parentTabId)) { + return normalizeLayout(input.layout); + } + + return normalizeLayout({ + ...input.layout, + parentTabIdByTabId: { + ...input.layout.parentTabIdByTabId, + [childTabId]: parentTabId, + }, + }); +} + export function createWorkspaceLayoutStore( ids: WorkspaceLayoutIdSource = defaultWorkspaceLayoutIds, ) { @@ -230,6 +260,45 @@ export function createWorkspaceLayoutStore( return result.tabId; }, + openChildTabFocused: (workspaceKey, target, parentTabId) => { + const normalizedWorkspaceKey = trimNonEmpty(workspaceKey); + const normalizedParentTabId = trimNonEmpty(parentTabId); + const normalizedTarget = normalizeWorkspaceTabTarget(target); + if (!normalizedWorkspaceKey || !normalizedParentTabId || !normalizedTarget) { + return null; + } + + const result = openTabInLayoutFocused({ + layout: getWorkspaceLayout(get().layoutByWorkspace, normalizedWorkspaceKey), + target: normalizedTarget, + now: Date.now(), + }); + + set((state) => { + const layout = attachParentTab({ + layout: result.layout, + childTabId: result.tabId, + parentTabId: normalizedParentTabId, + }); + return { + ...withoutFocusRestoration(state, normalizedWorkspaceKey), + hiddenAgentIdsByWorkspace: + normalizedTarget.kind !== "agent" + ? state.hiddenAgentIdsByWorkspace + : removeAgentIdFromWorkspaceSet( + state.hiddenAgentIdsByWorkspace, + normalizedWorkspaceKey, + normalizedTarget.agentId, + ), + layoutByWorkspace: { + ...state.layoutByWorkspace, + [normalizedWorkspaceKey]: layout, + }, + }; + }); + + return result.tabId; + }, openTabInBackground: (workspaceKey, target) => { const normalizedWorkspaceKey = trimNonEmpty(workspaceKey); const normalizedTarget = normalizeWorkspaceTabTarget(target);