fix: harden structured desktop status projection

This commit is contained in:
Brennan Benson
2026-08-10 23:52:06 -07:00
parent 07af7c9285
commit c243916d5f
4 changed files with 67 additions and 5 deletions
@@ -4,6 +4,7 @@ import { parsePaneKey } from '../../../../shared/stable-pane-id'
export type LiveEntriesByWorktreeCache = {
tabsByWorktree: AppState['tabsByWorktree']
unifiedTabsByWorktree: AppState['unifiedTabsByWorktree'] | undefined
agentStatusByPaneKey: AppState['agentStatusByPaneKey']
entriesByWorktree: Map<string, AgentStatusEntry[]>
}
@@ -3,7 +3,7 @@ import type {
AgentStatusEntry,
MigrationUnsupportedPtyEntry
} from '../../../../shared/agent-status-types'
import type { TerminalTab } from '../../../../shared/types'
import type { Tab, TerminalTab } from '../../../../shared/types'
import type { RetainedAgentEntry } from '@/store/slices/agent-status'
import { makePaneKey } from '../../../../shared/stable-pane-id'
import { getLiveEntriesFullRebuildCountForTests } from './worktree-agent-live-index-patch'
@@ -152,6 +152,44 @@ describe('selectLiveAgentStatusEntriesForWorktree', () => {
expect(selectLiveAgentStatusEntriesForWorktree(state, 'wt-1')).toEqual([childEntry])
})
it('keeps an idle structured session visible while its unified tab exists', () => {
const entry = makeEntry(PANE_KEY_1, 1000, {
state: 'done',
sessionBoundary: true,
worktreeId: 'wt-1',
tabId: 'tab-1'
})
const structuredTab = {
id: 'tab-1',
worktreeId: 'wt-1',
groupId: 'group-1',
contentType: 'agent-session',
entityId: 'session-1',
label: 'Codex Chat',
customLabel: null,
color: null,
sortOrder: 0,
createdAt: 0,
isPinned: false,
agentSessionAgent: 'codex'
} satisfies Tab
const state = {
tabsByWorktree: { 'wt-1': [] },
unifiedTabsByWorktree: { 'wt-1': [structuredTab] },
agentStatusByPaneKey: { [PANE_KEY_1]: entry },
migrationUnsupportedByPtyId: {},
retainedAgentsByPaneKey: {}
}
expect(selectLiveAgentStatusEntriesForWorktree(state, 'wt-1')).toEqual([entry])
expect(
selectLiveAgentStatusEntriesForWorktree(
{ ...state, unifiedTabsByWorktree: { 'wt-1': [] } },
'wt-1'
)
).toEqual([])
})
it('patches instead of full-rebuilding across within-state pings, and stays correct on transitions', () => {
const wt1Entry = makeEntry(PANE_KEY_1, 1000, { state: 'working', prompt: 'wt1 prompt' })
const wt2Entry = makeEntry(PANE_KEY_2, 1000, { state: 'working', prompt: 'wt2 prompt' })
@@ -28,7 +28,9 @@ type WorktreeAgentRowsState = Pick<
| 'migrationUnsupportedByPtyId'
| 'retainedAgentsByPaneKey'
| 'tabsByWorktree'
>
> & {
unifiedTabsByWorktree?: AppState['unifiedTabsByWorktree']
}
type TabWorktreeIndexCache = {
tabsByWorktree: WorktreeAgentRowsState['tabsByWorktree']
@@ -82,18 +84,38 @@ function getTabIdToWorktreeId(
return tabIdToWorktreeId
}
function getLiveTabIdToWorktreeId(
tabsByWorktree: WorktreeAgentRowsState['tabsByWorktree'],
unifiedTabsByWorktree: WorktreeAgentRowsState['unifiedTabsByWorktree']
): Map<string, string> {
const tabIdToWorktreeId = new Map(getTabIdToWorktreeId(tabsByWorktree))
for (const [worktreeId, tabs] of Object.entries(unifiedTabsByWorktree ?? EMPTY_RECORD)) {
for (const tab of tabs) {
if (tab.contentType === 'agent-session') {
tabIdToWorktreeId.set(tab.id, worktreeId)
}
}
}
return tabIdToWorktreeId
}
function getLiveEntriesByWorktree(state: WorktreeAgentRowsState): Map<string, AgentStatusEntry[]> {
const agentStatusByPaneKey = state.agentStatusByPaneKey ?? EMPTY_RECORD
const tabsByWorktree = state.tabsByWorktree ?? EMPTY_RECORD
const unifiedTabsByWorktree = state.unifiedTabsByWorktree
if (
liveEntriesByWorktreeCache?.tabsByWorktree === tabsByWorktree &&
liveEntriesByWorktreeCache.unifiedTabsByWorktree === unifiedTabsByWorktree &&
liveEntriesByWorktreeCache.agentStatusByPaneKey === agentStatusByPaneKey
) {
return liveEntriesByWorktreeCache.entriesByWorktree
}
const tabIdToWorktreeId = getTabIdToWorktreeId(tabsByWorktree)
if (liveEntriesByWorktreeCache?.tabsByWorktree === tabsByWorktree) {
const tabIdToWorktreeId = getLiveTabIdToWorktreeId(tabsByWorktree, unifiedTabsByWorktree)
if (
liveEntriesByWorktreeCache?.tabsByWorktree === tabsByWorktree &&
liveEntriesByWorktreeCache.unifiedTabsByWorktree === unifiedTabsByWorktree
) {
const patched = patchLiveEntriesByWorktree(
liveEntriesByWorktreeCache,
agentStatusByPaneKey,
@@ -102,6 +124,7 @@ function getLiveEntriesByWorktree(state: WorktreeAgentRowsState): Map<string, Ag
if (patched) {
liveEntriesByWorktreeCache = {
tabsByWorktree,
unifiedTabsByWorktree,
agentStatusByPaneKey,
entriesByWorktree: patched
}
@@ -128,6 +151,7 @@ function getLiveEntriesByWorktree(state: WorktreeAgentRowsState): Map<string, Ag
}
liveEntriesByWorktreeCache = {
tabsByWorktree,
unifiedTabsByWorktree,
agentStatusByPaneKey,
entriesByWorktree
}
@@ -2280,7 +2280,6 @@ function applyWebSessionTabsSnapshotWithContext(
return sameOpenFiles(state.openFiles, next) ? state.openFiles : next
})()
advanceWebSessionOpenFilesIndex(batchContext, nextOpenFiles, worktreeId)
const currentUnifiedTabs = state.unifiedTabsByWorktree[worktreeId] ?? []
const retainedUnifiedTabs = currentUnifiedTabs.filter((tab) => {
if (tab.contentType === 'agent-session') {
return false