Unify tab surface selection across workspace activation (#19635)

* Unify tab surface selection across workspace activation

* Cover the folder activation entry point and name its selection contract

Rewrite the folder-workspace selection tests to drive setActiveFolderWorkspace,
the entry point this PR rewrote; they previously went through setActiveWorktree
and exercised the git-worktree projection instead, so none of them failed
against pre-PR code. Add the layout-only ownership case.

Hoist the remembered-file condition out of a three-deep nested ternary and pin
the remembered agent-session/simulator cases that make it load-bearing, and
replace the Parameters<typeof ...> indirection with a named
ActiveSurfaceSourceState.

* Pin the folder-path openFiles fallback

The folder path now reaches the shared openFiles fallback: with no groups, no
layout and the remembered browser tab gone, an open file selects the editor
surface instead of falling through to terminal. That is parity with the
long-shipped git path, and nothing covered it.

---------

Co-authored-by: Merge Sim <sim@local>
This commit is contained in:
Brennan Benson
2026-09-09 00:17:27 -07:00
committed by GitHub
co-authored by Merge Sim
parent 5868fdc9e3
commit acd501486d
5 changed files with 399 additions and 200 deletions
@@ -47,6 +47,29 @@ function makeFolderWorkspace(overrides: Partial<FolderWorkspace> = {}): FolderWo
}
}
function rememberedBrowserSurface(workspaceKey: string): Partial<AppState> {
return {
browserTabsByWorktree: {
[workspaceKey]: [
{
id: 'remembered',
worktreeId: workspaceKey,
url: 'about:blank',
title: 'Browser',
loading: false,
faviconUrl: null,
canGoBack: false,
canGoForward: false,
loadError: null,
createdAt: 1
}
]
},
activeBrowserTabIdByWorktree: { [workspaceKey]: 'remembered' },
activeTabTypeByWorktree: { [workspaceKey]: 'browser' }
}
}
type FolderWorkspaceUpdateArgs = {
folderWorkspaceId: string
updates: Partial<FolderWorkspace>
@@ -121,6 +144,100 @@ describe('folder workspace generic activation and activity', () => {
})
})
it.each([
['simulator', 'local'],
['agent-session', 'local'],
['simulator', 'ssh:test-host'],
['agent-session', 'ssh:test-host']
] as const)(
'restores a folder %s tab on %s using its concrete visible type',
(contentType, executionHostId) => {
const folder = makeFolderWorkspace({ executionHostId })
const workspaceKey = folderWorkspaceKey(folder.id)
const store = seedLocalFolderStore(folder)
store.getState().createUnifiedTab(workspaceKey, contentType, { id: 'selected' })
store.setState({ activeTabTypeByWorktree: { [workspaceKey]: 'editor' } })
store.getState().setActiveFolderWorkspace(folder.id, executionHostId)
expect(store.getState().activeWorkspaceExecutionHostId).toBe(executionHostId)
expect(store.getState().activeTabType).toBe(contentType)
expect(store.getState().activeTabTypeByWorktree[workspaceKey]).toBe(contentType)
expect(store.getState().getActiveTab(workspaceKey)?.id).toBe('selected')
}
)
it('does not let remembered browser state select content in an empty folder group', () => {
const folder = makeFolderWorkspace()
const workspaceKey = folderWorkspaceKey(folder.id)
const store = seedLocalFolderStore(folder)
store.setState({
...rememberedBrowserSurface(workspaceKey),
groupsByWorktree: {
[workspaceKey]: [
{
id: 'empty',
worktreeId: workspaceKey,
activeTabId: null,
tabOrder: []
}
]
},
activeGroupIdByWorktree: { [workspaceKey]: 'empty' }
} as Partial<AppState>)
store.getState().setActiveFolderWorkspace(folder.id)
expect(store.getState().activeTabType).toBe('terminal')
expect(store.getState().activeBrowserTabId).toBe('remembered')
})
it('keeps layout-only folder ownership above remembered browser state', () => {
const folder = makeFolderWorkspace()
const workspaceKey = folderWorkspaceKey(folder.id)
const store = seedLocalFolderStore(folder)
store.setState({
...rememberedBrowserSurface(workspaceKey),
groupsByWorktree: {},
layoutByWorktree: { [workspaceKey]: { type: 'leaf', groupId: 'pending' } }
} as Partial<AppState>)
store.getState().setActiveFolderWorkspace(folder.id)
expect(store.getState().activeTabType).toBe('terminal')
expect(store.getState().activeBrowserTabId).toBe('remembered')
})
it('falls back to an open file when nothing else owns the folder surface', () => {
const folder = makeFolderWorkspace()
const workspaceKey = folderWorkspaceKey(folder.id)
const store = seedLocalFolderStore(folder)
store.setState({
groupsByWorktree: {},
layoutByWorktree: {},
// Why: the remembered browser tab is gone, so only the open file is left to show.
activeBrowserTabIdByWorktree: { [workspaceKey]: 'closed' },
browserTabsByWorktree: { [workspaceKey]: [] },
activeTabTypeByWorktree: { [workspaceKey]: 'browser' },
openFiles: [
{
id: 'fallback-file',
worktreeId: workspaceKey,
filePath: '/workspace/folder/file',
relativePath: 'file',
language: 'plaintext',
isDirty: false,
mode: 'edit'
}
]
} as Partial<AppState>)
store.getState().setActiveFolderWorkspace(folder.id)
expect(store.getState().activeTabType).toBe('editor')
expect(store.getState().activeFileId).toBe('fallback-file')
})
it('coalesces repeated activity persistence while keeping local activity current', async () => {
vi.useFakeTimers()
vi.setSystemTime(1_000)
@@ -0,0 +1,228 @@
import { describe, expect, it } from 'vitest'
import type { Tab, TabContentType } from '../../../../../shared/tab-types'
import { buildHydratedTabState } from '../tabs-hydration'
import { resolveActivatedWorktreeSurface } from '../worktrees/session/active-worktree-surface'
import { deriveActiveSurfaceForWorktree } from './tabs-surface'
type SelectionState = Parameters<typeof resolveActivatedWorktreeSurface>[0]
const workspace = 'repo::/workspace'
function selectedTab(contentType: TabContentType): Tab {
return {
id: 'selected',
entityId: 'selected-entity',
groupId: 'group',
worktreeId: workspace,
contentType,
label: 'Selected',
customLabel: null,
color: null,
sortOrder: 0,
createdAt: 1
}
}
function selectionState(tab: Tab | null): SelectionState {
return {
rightSidebarExplorerViewByWorktree: {},
activeGroupIdByWorktree: { [workspace]: 'group' },
groupsByWorktree: {
[workspace]: [
{
id: 'group',
worktreeId: workspace,
activeTabId: tab?.id ?? null,
tabOrder: tab ? [tab.id] : []
}
]
},
unifiedTabsByWorktree: { [workspace]: tab ? [tab] : [] },
layoutByWorktree: {},
activeTabIdByWorktree: { [workspace]: 'remembered-terminal' },
activeFileIdByWorktree: { [workspace]: 'remembered-file' },
activeBrowserTabIdByWorktree: { [workspace]: 'remembered-browser' },
activeTabTypeByWorktree: { [workspace]: 'browser' },
tabsByWorktree: {
[workspace]: [
{
id: 'remembered-terminal',
worktreeId: workspace,
ptyId: null,
title: 'Terminal',
customTitle: null,
color: null,
sortOrder: 0,
createdAt: 1
}
]
},
browserTabsByWorktree: {
[workspace]: [
{
id: 'remembered-browser',
worktreeId: workspace,
url: 'about:blank',
title: 'Browser',
loading: false,
faviconUrl: null,
canGoBack: false,
canGoForward: false,
loadError: null,
createdAt: 1
}
]
},
openFiles: [
{
id: 'remembered-file',
worktreeId: workspace,
filePath: '/workspace/file',
relativePath: 'file',
language: 'plaintext',
isDirty: false,
mode: 'edit'
}
]
}
}
function activate(state: SelectionState) {
const { restoredRightSidebarExplorerView: _view, ...surface } = resolveActivatedWorktreeSurface(
state,
workspace,
undefined,
null
)
return surface
}
describe('tab selection and hydration ownership', () => {
it.each([
['terminal', 'terminal'],
['editor', 'editor'],
['diff', 'editor'],
['conflict-review', 'editor'],
['check-details', 'editor'],
['browser', 'browser'],
['simulator', 'simulator'],
['agent-session', 'agent-session']
] as const)(
'projects %s selection while retaining other remembered surfaces',
(kind, visible) => {
const state = selectionState(selectedTab(kind))
const expected = {
activeTabType: visible,
activeTabId: kind === 'terminal' ? 'selected-entity' : 'remembered-terminal',
activeFileId: visible === 'editor' ? 'selected-entity' : 'remembered-file',
activeBrowserTabId: kind === 'browser' ? 'selected-entity' : 'remembered-browser'
}
expect(deriveActiveSurfaceForWorktree(state, workspace)).toEqual(expected)
expect(activate(state)).toEqual(expected)
}
)
it('keeps empty groups authoritative over remembered browser/editor surfaces', () => {
const state = selectionState(null)
expect(activate(state).activeTabType).toBe('terminal')
expect(deriveActiveSurfaceForWorktree(state, workspace).activeTabType).toBe('terminal')
})
it('keeps layout-only ownership authoritative during staged hydration', () => {
const state = selectionState(null)
state.groupsByWorktree = {}
state.layoutByWorktree = { [workspace]: { type: 'leaf', groupId: 'pending' } }
expect(activate(state).activeTabType).toBe('terminal')
expect(deriveActiveSurfaceForWorktree(state, workspace).activeTabType).toBe('terminal')
})
it('distinguishes workspace restoration from group-focus legacy fallback', () => {
const state = selectionState(null)
state.groupsByWorktree = {}
state.activeTabTypeByWorktree[workspace] = 'terminal'
expect(activate(state).activeTabType).toBe('terminal')
expect(deriveActiveSurfaceForWorktree(state, workspace).activeTabType).toBe('browser')
})
it('does not select a preferred tab owned by another group', () => {
const state = selectionState(selectedTab('browser'))
state.unifiedTabsByWorktree[workspace].push({
...selectedTab('editor'),
id: 'foreign',
groupId: 'other'
})
expect(resolveActivatedWorktreeSurface(state, workspace, 'foreign', null).activeTabType).toBe(
'terminal'
)
})
it('resolves stale active-group IDs to the first group consistently', () => {
const state = selectionState(selectedTab('simulator'))
state.activeGroupIdByWorktree[workspace] = 'removed'
expect(activate(state).activeTabType).toBe('simulator')
expect(deriveActiveSurfaceForWorktree(state, workspace).activeTabType).toBe('simulator')
})
it('requires group ownership even for an explicit preferred tab during hydration', () => {
const state = selectionState(selectedTab('simulator'))
state.groupsByWorktree = {}
state.activeTabTypeByWorktree[workspace] = 'terminal'
expect(resolveActivatedWorktreeSurface(state, workspace, 'selected', null).activeTabType).toBe(
'terminal'
)
})
it('honors a preferred tab in the selected group without mutating selection', () => {
const state = selectionState(selectedTab('browser'))
const preferred = { ...selectedTab('simulator'), id: 'preferred' }
state.unifiedTabsByWorktree[workspace].push(preferred)
state.groupsByWorktree[workspace][0].tabOrder.push(preferred.id)
expect(
resolveActivatedWorktreeSurface(state, workspace, preferred.id, null).activeTabType
).toBe('simulator')
expect(state.groupsByWorktree[workspace][0].activeTabId).toBe('selected')
})
it.each([
['terminal', 'terminal', 'remembered-file'],
['editor', 'editor', 'remembered-file'],
['browser', 'browser', 'remembered-file'],
// Why: nothing renders a remembered agent-session/simulator once its tab is gone, so the browser
// surface takes over and must not leave the remembered file selected underneath it.
['agent-session', 'browser', null],
['simulator', 'browser', null]
] as const)(
'projects legacy %s memory as %s when unified groups are absent',
(activeTabType, visible, activeFileId) => {
const state = selectionState(null)
state.groupsByWorktree = {}
state.activeTabTypeByWorktree[workspace] = activeTabType
expect(activate(state)).toEqual({
activeTabType: visible,
activeTabId: 'remembered-terminal',
activeFileId,
activeBrowserTabId: 'remembered-browser'
})
}
)
it('hydrates unified selection without allowing conflicting legacy memories to choose it', () => {
const tab = selectedTab('simulator')
const state = selectionState(tab)
const session = {
activeRepoId: null,
activeWorktreeId: workspace,
activeTabId: 'remembered-terminal',
tabsByWorktree: {},
terminalLayoutsByTabId: {},
unifiedTabs: state.unifiedTabsByWorktree,
tabGroups: state.groupsByWorktree,
activeGroupIdByWorktree: state.activeGroupIdByWorktree,
activeTabTypeByWorktree: { [workspace]: 'browser' as const },
activeTabIdByWorktree: state.activeTabIdByWorktree
}
const before = structuredClone(session)
const hydrated = buildHydratedTabState(session, new Set([workspace]))
expect(activate({ ...state, ...hydrated }).activeTabType).toBe('simulator')
expect(session).toEqual(before)
})
})
@@ -2,22 +2,26 @@ import type { AppState } from '../../types'
import { toVisibleTabType } from '../../../../../shared/tab-types'
import type { WorkspaceVisibleTabType } from '../../../../../shared/tab-types'
export type ActiveSurfaceSourceState = Pick<
AppState,
| 'activeBrowserTabIdByWorktree'
| 'activeFileIdByWorktree'
| 'activeGroupIdByWorktree'
| 'activeTabIdByWorktree'
| 'activeTabTypeByWorktree'
| 'browserTabsByWorktree'
| 'groupsByWorktree'
| 'layoutByWorktree'
| 'openFiles'
| 'tabsByWorktree'
| 'unifiedTabsByWorktree'
>
export function deriveActiveSurfaceForWorktree(
state: Pick<
AppState,
| 'activeBrowserTabIdByWorktree'
| 'activeFileIdByWorktree'
| 'activeGroupIdByWorktree'
| 'activeTabIdByWorktree'
| 'browserTabsByWorktree'
| 'groupsByWorktree'
| 'layoutByWorktree'
| 'openFiles'
| 'tabsByWorktree'
| 'unifiedTabsByWorktree'
>,
state: ActiveSurfaceSourceState,
worktreeId: string,
preferredGroupId?: string | null
preferredGroupId?: string | null,
options?: { preferredTabId?: string; legacySelection?: 'remembered-type' }
): {
activeBrowserTabId: string | null
activeFileId: string | null
@@ -28,10 +32,12 @@ export function deriveActiveSurfaceForWorktree(
const activeGroupId = preferredGroupId ?? state.activeGroupIdByWorktree[worktreeId] ?? null
const activeGroup =
(activeGroupId ? groups.find((group) => group.id === activeGroupId) : null) ?? groups[0] ?? null
const activeUnifiedTabId = options?.preferredTabId ?? activeGroup?.activeTabId
const activeUnifiedTab =
activeGroup?.activeTabId != null
activeUnifiedTabId != null
? ((state.unifiedTabsByWorktree[worktreeId] ?? []).find(
(tab) => tab.id === activeGroup.activeTabId && tab.groupId === activeGroup.id
(tab) =>
tab.id === activeUnifiedTabId && activeGroup != null && tab.groupId === activeGroup.id
) ?? null)
: null
const restoredFileId = state.activeFileIdByWorktree[worktreeId] ?? null
@@ -50,6 +56,14 @@ export function deriveActiveSurfaceForWorktree(
: false
const hasGroupOwnedSurface = groups.length > 0 || Boolean(state.layoutByWorktree[worktreeId])
const restoreLegacyType = options?.legacySelection === 'remembered-type'
const restoredTabType = restoreLegacyType
? (state.activeTabTypeByWorktree[worktreeId] ?? 'terminal')
: null
// Why: only a remembered browser type — or group focus, which remembers no type at all — may keep
// the remembered file selected under the browser surface; a stale agent-session/simulator clears it.
const keepRememberedFileUnderBrowser = restoredTabType === null || restoredTabType === 'browser'
let activeFileId: string | null
let activeBrowserTabId: string | null
let activeTabType: WorkspaceVisibleTabType
@@ -76,8 +90,16 @@ export function deriveActiveSurfaceForWorktree(
activeBrowserTabId = browserTabStillOpen ? restoredBrowserTabId : (browserTabs[0]?.id ?? null)
// Why: focusing an empty split should target its default terminal area, not the previously active browser/editor in another group.
activeTabType = 'terminal'
} else if (browserTabStillOpen) {
} else if (restoredTabType === 'terminal') {
activeFileId = fileStillOpen ? restoredFileId : null
activeBrowserTabId = browserTabStillOpen ? restoredBrowserTabId : (browserTabs[0]?.id ?? null)
activeTabType = 'terminal'
} else if (restoredTabType === 'editor' && fileStillOpen) {
activeFileId = restoredFileId
activeBrowserTabId = browserTabStillOpen ? restoredBrowserTabId : (browserTabs[0]?.id ?? null)
activeTabType = 'editor'
} else if (browserTabStillOpen) {
activeFileId = keepRememberedFileUnderBrowser && fileStillOpen ? restoredFileId : null
activeBrowserTabId = restoredBrowserTabId
activeTabType = 'browser'
} else if (fileStillOpen) {
@@ -106,20 +128,7 @@ export function deriveActiveSurfaceForWorktree(
}
export function buildActiveSurfacePatch(
state: Pick<
AppState,
| 'activeBrowserTabIdByWorktree'
| 'activeFileIdByWorktree'
| 'activeGroupIdByWorktree'
| 'activeTabIdByWorktree'
| 'activeTabTypeByWorktree'
| 'browserTabsByWorktree'
| 'groupsByWorktree'
| 'layoutByWorktree'
| 'openFiles'
| 'tabsByWorktree'
| 'unifiedTabsByWorktree'
>,
state: ActiveSurfaceSourceState,
worktreeId: string,
preferredGroupId?: string | null
): Pick<
@@ -1,9 +1,10 @@
import type { AppState } from '../../../types'
import type { WorkspaceVisibleTabType } from '../../../../../../shared/tab-types'
import { toVisibleTabType } from '../../../../../../shared/tab-types'
import type { ActiveSurfaceSourceState } from '../../tabs/tabs-surface'
import { deriveActiveSurfaceForWorktree } from '../../tabs/tabs-surface'
export function resolveActivatedWorktreeSurface(
s: AppState,
s: ActiveSurfaceSourceState & Pick<AppState, 'rightSidebarExplorerViewByWorktree'>,
worktreeId: string,
preferredActiveUnifiedTabId: string | undefined,
reconciledActiveTabId: string | null
@@ -16,107 +17,11 @@ export function resolveActivatedWorktreeSurface(
activeTabType: WorkspaceVisibleTabType
activeTabId: string | null
} {
// Why: Search lives under Explorer, so the files/search sub-route must switch with the worktree, not leak the prior one.
const restoredRightSidebarExplorerView =
s.rightSidebarExplorerViewByWorktree?.[worktreeId] ?? 'files'
const restoredFileId = s.activeFileIdByWorktree[worktreeId] ?? null
const restoredBrowserTabId = s.activeBrowserTabIdByWorktree[worktreeId] ?? null
const restoredTabType = s.activeTabTypeByWorktree[worktreeId] ?? 'terminal'
const activeGroupId =
s.activeGroupIdByWorktree[worktreeId] ?? s.groupsByWorktree[worktreeId]?.[0]?.id ?? null
const activeGroup = activeGroupId
? ((s.groupsByWorktree[worktreeId] ?? []).find((group) => group.id === activeGroupId) ?? null)
: null
const activeUnifiedTabId =
preferredActiveUnifiedTabId ?? reconciledActiveTabId ?? activeGroup?.activeTabId ?? null
const activeUnifiedTab =
activeUnifiedTabId != null
? ((s.unifiedTabsByWorktree[worktreeId] ?? []).find(
(tab) => tab.id === activeUnifiedTabId && (!activeGroup || tab.groupId === activeGroup.id)
) ?? null)
: null
// Verify the restored file still exists in openFiles
const fileStillOpen = restoredFileId
? s.openFiles.some((f) => f.id === restoredFileId && f.worktreeId === worktreeId)
: false
const browserTabs = s.browserTabsByWorktree[worktreeId] ?? []
const browserTabStillOpen = restoredBrowserTabId
? browserTabs.some((tab) => tab.id === restoredBrowserTabId)
: false
const hasGroupOwnedSurface =
(s.groupsByWorktree[worktreeId]?.length ?? 0) > 0 || Boolean(s.layoutByWorktree[worktreeId])
// Why: restore from the reconciled tab-group model first; preferring legacy fallbacks can show a blank worktree.
let activeFileId: string | null
let activeBrowserTabId: string | null
let activeTabType: WorkspaceVisibleTabType
if (activeUnifiedTab) {
activeFileId =
activeUnifiedTab.contentType === 'editor' ||
activeUnifiedTab.contentType === 'diff' ||
activeUnifiedTab.contentType === 'conflict-review' ||
activeUnifiedTab.contentType === 'check-details'
? activeUnifiedTab.entityId
: fileStillOpen
? restoredFileId
: null
activeBrowserTabId =
activeUnifiedTab.contentType === 'browser'
? activeUnifiedTab.entityId
: browserTabStillOpen
? restoredBrowserTabId
: (browserTabs[0]?.id ?? null)
activeTabType = toVisibleTabType(activeUnifiedTab.contentType)
} else if (hasGroupOwnedSurface) {
activeFileId = fileStillOpen ? restoredFileId : null
activeBrowserTabId = browserTabStillOpen ? restoredBrowserTabId : (browserTabs[0]?.id ?? null)
activeTabType = 'terminal'
} else if (restoredTabType === 'terminal') {
activeFileId = fileStillOpen ? restoredFileId : null
activeBrowserTabId = browserTabStillOpen ? restoredBrowserTabId : (browserTabs[0]?.id ?? null)
activeTabType = 'terminal'
} else if (restoredTabType === 'browser' && browserTabStillOpen) {
activeFileId = fileStillOpen ? restoredFileId : null
activeBrowserTabId = restoredBrowserTabId
activeTabType = 'browser'
} else if (restoredTabType === 'editor' && fileStillOpen) {
activeFileId = restoredFileId
activeBrowserTabId = browserTabStillOpen ? restoredBrowserTabId : (browserTabs[0]?.id ?? null)
activeTabType = 'editor'
} else if (browserTabStillOpen) {
activeFileId = null
activeBrowserTabId = restoredBrowserTabId
activeTabType = 'browser'
} else if (fileStillOpen) {
activeFileId = restoredFileId
activeBrowserTabId = browserTabs[0]?.id ?? null
activeTabType = 'editor'
} else {
const fallbackFile = s.openFiles.find((f) => f.worktreeId === worktreeId)
const fallbackBrowserTab = browserTabs[0] ?? null
activeFileId = fallbackFile?.id ?? null
activeBrowserTabId = browserTabStillOpen
? restoredBrowserTabId
: (fallbackBrowserTab?.id ?? null)
activeTabType = fallbackFile ? 'editor' : fallbackBrowserTab ? 'browser' : 'terminal'
}
// Why: restore the last-active terminal tab so the user returns to where they left, not tab 0.
const restoredTabId = s.activeTabIdByWorktree[worktreeId] ?? null
const worktreeTabs = s.tabsByWorktree[worktreeId] ?? []
const tabStillExists = restoredTabId ? worktreeTabs.some((t) => t.id === restoredTabId) : false
const activeTabId =
activeUnifiedTab?.contentType === 'terminal'
? activeUnifiedTab.entityId
: tabStillExists
? restoredTabId
: (worktreeTabs[0]?.id ?? null)
return {
restoredRightSidebarExplorerView,
activeFileId,
activeBrowserTabId,
activeTabType,
activeTabId
restoredRightSidebarExplorerView: s.rightSidebarExplorerViewByWorktree?.[worktreeId] ?? 'files',
...deriveActiveSurfaceForWorktree(s, worktreeId, undefined, {
legacySelection: 'remembered-type',
preferredTabId: preferredActiveUnifiedTabId ?? reconciledActiveTabId ?? undefined
})
}
}
@@ -8,6 +8,7 @@ import {
folderWorkspaceMatchesHost
} from '../listing/detected-worktree-meta'
import { shouldDeferActivationTerminalPrep } from './activation-terminal-prep'
import { deriveActiveSurfaceForWorktree } from '../../tabs/tabs-surface'
export function createSetActiveFolderWorkspace(
set: WorktreeSliceSet,
@@ -28,72 +29,11 @@ export function createSetActiveFolderWorkspace(
const reconciledActiveTabId =
get().reconcileWorktreeTabModel(workspaceKey).activeRenderableTabId
set((s) => {
const restoredFileId = s.activeFileIdByWorktree[workspaceKey] ?? null
const restoredBrowserTabId = s.activeBrowserTabIdByWorktree[workspaceKey] ?? null
const restoredTabType = s.activeTabTypeByWorktree[workspaceKey] ?? 'terminal'
const activeGroupId =
s.activeGroupIdByWorktree[workspaceKey] ?? s.groupsByWorktree[workspaceKey]?.[0]?.id ?? null
const activeGroup = activeGroupId
? ((s.groupsByWorktree[workspaceKey] ?? []).find((group) => group.id === activeGroupId) ??
null)
: null
const activeUnifiedTabId = reconciledActiveTabId ?? activeGroup?.activeTabId ?? null
const activeUnifiedTab =
activeUnifiedTabId != null
? ((s.unifiedTabsByWorktree[workspaceKey] ?? []).find(
(tab) =>
tab.id === activeUnifiedTabId && (!activeGroup || tab.groupId === activeGroup.id)
) ?? null)
: null
const fileStillOpen = restoredFileId
? s.openFiles.some((file) => file.id === restoredFileId && file.worktreeId === workspaceKey)
: false
const browserTabs = s.browserTabsByWorktree[workspaceKey] ?? []
const browserTabStillOpen = restoredBrowserTabId
? browserTabs.some((tab) => tab.id === restoredBrowserTabId)
: false
const worktreeTabs = s.tabsByWorktree[workspaceKey] ?? []
const restoredTabId = s.activeTabIdByWorktree[workspaceKey] ?? null
const tabStillExists = restoredTabId
? worktreeTabs.some((tab) => tab.id === restoredTabId)
: false
const activeFileId =
activeUnifiedTab?.contentType === 'editor' ||
activeUnifiedTab?.contentType === 'diff' ||
activeUnifiedTab?.contentType === 'conflict-review' ||
activeUnifiedTab?.contentType === 'check-details'
? activeUnifiedTab.entityId
: fileStillOpen
? restoredFileId
: null
const activeBrowserTabId =
activeUnifiedTab?.contentType === 'browser'
? activeUnifiedTab.entityId
: browserTabStillOpen
? restoredBrowserTabId
: (browserTabs[0]?.id ?? null)
const activeTabType =
activeUnifiedTab?.contentType === 'terminal'
? 'terminal'
: activeUnifiedTab?.contentType === 'browser'
? 'browser'
: activeUnifiedTab
? 'editor'
: restoredTabType === 'browser' && browserTabStillOpen
? 'browser'
: restoredTabType === 'editor' && fileStillOpen
? 'editor'
: fileStillOpen
? 'editor'
: browserTabs.length > 0
? 'browser'
: 'terminal'
const activeTabId =
activeUnifiedTab?.contentType === 'terminal'
? activeUnifiedTab.entityId
: tabStillExists
? restoredTabId
: (worktreeTabs[0]?.id ?? null)
const { activeFileId, activeBrowserTabId, activeTabType, activeTabId } =
deriveActiveSurfaceForWorktree(s, workspaceKey, undefined, {
legacySelection: 'remembered-type',
preferredTabId: reconciledActiveTabId ?? undefined
})
const nextEverActivated = s.everActivatedWorktreeIds.has(workspaceKey)
? s.everActivatedWorktreeIds
: new Set([...s.everActivatedWorktreeIds, workspaceKey])