perf(renderer): FIFO-cap recentlyClosedAgentStatusTabIds (#7645)

recentlyClosedAgentStatusTabIds (agent-status store) suppresses late
hook/status events for a just-closed terminal tab. It was only ever
added to — one `true` entry per agent-tab close, keyed by the ephemeral
tabId, never deleted or capped — so it grew for the renderer's whole
session. It's the renderer twin of the main-process
closedAgentStatusTabIds set that #7561 already FIFO-capped.

Fix: bound it to the 1024 most-recent closed tabs via delete-then-set
LRU with oldest-key eviction (Record key order is insertion order),
mirroring #7561. A status event for a tab closed >1024 tabs ago cannot
still arrive, so suppression behavior is unchanged.

Test (red->green): closing 1029 tabs leaves exactly 1024 markers with
the oldest evicted and the most-recent retained; without the cap all
1029 persist.
This commit is contained in:
Neil
2026-07-06 21:28:03 -07:00
committed by GitHub
parent 8936225889
commit d0403d808e
2 changed files with 52 additions and 4 deletions
@@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { AgentStatusEntry } from '../../../../shared/agent-status-types'
import type { TerminalTab } from '../../../../shared/types'
import type { RetainedAgentEntry } from './agent-status'
import { RECENTLY_CLOSED_AGENT_STATUS_TAB_IDS_MAX } from './agent-status'
import { createTestStore } from './store-test-helpers'
// Why: dropAgentStatus and dismissRetainedAgentsByWorktree mirror the renderer-
@@ -119,6 +120,24 @@ describe('dropAgentStatusByTabPrefix -> IPC fan-out', () => {
'tab-new': true
})
})
it('FIFO-caps recentlyClosedAgentStatusTabIds so it cannot grow unbounded', () => {
stubWindowApi()
const store = createTestStore()
const cap = RECENTLY_CLOSED_AGENT_STATUS_TAB_IDS_MAX
for (let i = 0; i < cap + 5; i++) {
store.getState().dropAgentStatusByTabPrefix(`tab-${i}`)
}
const closed = store.getState().recentlyClosedAgentStatusTabIds
expect(Object.keys(closed)).toHaveLength(cap)
// Oldest evicted, most-recent retained (a status event for a tab closed
// >cap tabs ago cannot still arrive, so suppression is unaffected).
expect(closed['tab-0']).toBeUndefined()
expect(closed['tab-4']).toBeUndefined()
expect(closed[`tab-${cap + 4}`]).toBe(true)
})
})
describe('dismissRetainedAgentsByWorktree → IPC fan-out', () => {
+33 -4
View File
@@ -733,6 +733,35 @@ function getLaunchConfigForEntry(
: undefined
}
// Why: the renderer twin of the main-process closedAgentStatusTabIds set that
// #7561 FIFO-capped. It suppresses late hook/status events for a just-closed tab,
// so it must outlive the tab briefly — but tabId is ephemeral and it was only
// ever added to, growing one entry per tab-close for the renderer's whole life.
export const RECENTLY_CLOSED_AGENT_STATUS_TAB_IDS_MAX = 1024
// delete-then-set for LRU recency, then evict the oldest keys past the cap (Record
// key order is insertion order for non-integer string keys). A status event for a
// tab closed >MAX tabs ago cannot still arrive, so eviction is safe.
function boundRecentlyClosedAgentStatusTabIds(
existing: Record<string, true>,
tabId: string
): Record<string, true> {
const next: Record<string, true> = {}
for (const key of Object.keys(existing)) {
if (key !== tabId) {
next[key] = true
}
}
next[tabId] = true
const keys = Object.keys(next)
if (keys.length > RECENTLY_CLOSED_AGENT_STATUS_TAB_IDS_MAX) {
for (const stale of keys.slice(0, keys.length - RECENTLY_CLOSED_AGENT_STATUS_TAB_IDS_MAX)) {
delete next[stale]
}
}
return next
}
function getLaunchConfigForStatusMetadata(
state: AppState,
metadata: AgentLaunchConfigStatusMetadata
@@ -1632,10 +1661,10 @@ export const createAgentStatusSlice: StateCreator<AppState, [], [], AgentStatusS
delete nextAck[k]
}
}
const nextClosedTabs: Record<string, true> = {
...s.recentlyClosedAgentStatusTabIds,
[tabIdPrefix]: true
}
const nextClosedTabs = boundRecentlyClosedAgentStatusTabIds(
s.recentlyClosedAgentStatusTabIds,
tabIdPrefix
)
if (
liveKeys.length === 0 &&