perf(renderer): index unread completion tabs (#17246)

This commit is contained in:
Neil
2026-08-29 16:01:42 -07:00
committed by GitHub
parent be68aa2718
commit 79aea81173
2 changed files with 90 additions and 15 deletions
@@ -3,6 +3,7 @@ import type { AgentStatusEntry } from '../../../../shared/agent-status-types'
import type { TerminalTab } from '../../../../shared/terminal-tab-types'
import {
hasUnreadAgentCompletionForTerminalTab,
resetUnreadAgentCompletionTabIdsCacheForTest,
resetTerminalTabActivityFlagsCacheForTest,
resolveTerminalTabActivityStatus,
resolveTerminalTabAttentionBadge,
@@ -41,6 +42,7 @@ const LIVE_PTY = { [TAB_ID]: ['pty-1'] }
beforeEach(() => {
resetTerminalTabActivityFlagsCacheForTest()
resetUnreadAgentCompletionTabIdsCacheForTest()
vi.useFakeTimers()
vi.setSystemTime(NOW)
})
@@ -48,6 +50,7 @@ beforeEach(() => {
afterEach(() => {
vi.useRealTimers()
resetTerminalTabActivityFlagsCacheForTest()
resetUnreadAgentCompletionTabIdsCacheForTest()
})
describe('resolveTerminalTabActivityStatus', () => {
@@ -267,6 +270,7 @@ describe('hasUnreadAgentCompletionForTerminalTab', () => {
expect(
hasUnreadAgentCompletionForTerminalTab({ [`tab-2:${SECOND_LEAF_ID}`]: true }, TAB_ID)
).toBe(false)
expect(hasUnreadAgentCompletionForTerminalTab(undefined, TAB_ID)).toBe(false)
})
// Why: the param accepts boolean maps, so a cleared-to-`false` marker must not read as unread.
@@ -275,6 +279,57 @@ describe('hasUnreadAgentCompletionForTerminalTab', () => {
hasUnreadAgentCompletionForTerminalTab({ [`${TAB_ID}:${FIRST_LEAF_ID}`]: false }, TAB_ID)
).toBe(false)
})
it('preserves first-colon ownership for legacy and malformed pane keys', () => {
expect(hasUnreadAgentCompletionForTerminalTab({ [TAB_ID]: true }, TAB_ID)).toBe(true)
expect(hasUnreadAgentCompletionForTerminalTab({ [`${TAB_ID}:3`]: true }, TAB_ID)).toBe(true)
expect(
hasUnreadAgentCompletionForTerminalTab(
{ [`${TAB_ID}:${FIRST_LEAF_ID}:suffix`]: true },
TAB_ID
)
).toBe(true)
expect(
hasUnreadAgentCompletionForTerminalTab({ [`${TAB_ID}0:${SECOND_LEAF_ID}`]: true }, TAB_ID)
).toBe(false)
})
it('changes only the owning tab when immutable marker snapshots add and clear unread', () => {
const before = { [`tab-2:${SECOND_LEAF_ID}`]: true }
const added = { ...before, [`${TAB_ID}:${FIRST_LEAF_ID}`]: true }
const cleared = { ...added, [`${TAB_ID}:${FIRST_LEAF_ID}`]: false }
expect(hasUnreadAgentCompletionForTerminalTab(before, TAB_ID)).toBe(false)
expect(hasUnreadAgentCompletionForTerminalTab(added, TAB_ID)).toBe(true)
expect(hasUnreadAgentCompletionForTerminalTab(cleared, TAB_ID)).toBe(false)
expect(hasUnreadAgentCompletionForTerminalTab(before, 'tab-2')).toBe(true)
expect(hasUnreadAgentCompletionForTerminalTab(added, 'tab-2')).toBe(true)
expect(hasUnreadAgentCompletionForTerminalTab(cleared, 'tab-2')).toBe(true)
})
it('indexes one immutable marker snapshot once across all mounted tab lookups', () => {
const ownKeys = vi.fn(Reflect.ownKeys)
let valueReads = 0
const markers: Record<string, true> = Object.fromEntries(
Array.from({ length: 1_000 }, (_, index) => [`owner-${index}:leaf`, true] as const)
)
const unread = new Proxy<Record<string, true>>(markers, {
ownKeys,
get: (target, property, receiver) => {
valueReads += 1
return Reflect.get(target, property, receiver)
}
})
for (let publication = 0; publication < 5; publication += 1) {
for (let tab = 0; tab < 500; tab += 1) {
expect(hasUnreadAgentCompletionForTerminalTab(unread, `absent-${tab}`)).toBe(false)
}
}
expect(ownKeys).toHaveBeenCalledTimes(1)
expect(valueReads).toBe(1_000)
})
})
describe('resolveTerminalTabAttentionBadge', () => {
@@ -256,29 +256,49 @@ export function terminalTabHasUnreadActivity({
)
}
// Why: production writes replace this map; WeakMap supports retained snapshots without pinning them.
let unreadAgentCompletionTabIdsBySnapshot = new WeakMap<
Record<string, boolean | undefined>,
ReadonlySet<string>
>()
function getUnreadAgentCompletionTabIds(
unreadAgentCompletionPanes: Record<string, boolean | undefined>
): ReadonlySet<string> {
const cached = unreadAgentCompletionTabIdsBySnapshot.get(unreadAgentCompletionPanes)
if (cached) {
return cached
}
// Why: every mounted tab runs this selector per store write; index each immutable marker snapshot once.
const tabIds = new Set<string>()
for (const paneKey of Object.keys(unreadAgentCompletionPanes)) {
if (!unreadAgentCompletionPanes[paneKey]) {
continue
}
const separatorIndex = paneKey.indexOf(':')
tabIds.add(separatorIndex === -1 ? paneKey : paneKey.slice(0, separatorIndex))
}
unreadAgentCompletionTabIdsBySnapshot.set(unreadAgentCompletionPanes, tabIds)
return tabIds
}
/** Match pane-level unread completion markers to their owning terminal tab. */
export function hasUnreadAgentCompletionForTerminalTab(
unreadAgentCompletionPanes: Record<string, boolean | undefined> | undefined,
tabId: string
): boolean {
for (const [paneKey, unread] of Object.entries(unreadAgentCompletionPanes ?? {})) {
// Why entries, not keys: the widened value type lets a cleared marker linger as `false`.
if (!unread) {
continue
}
// paneKey is `${tabId}:${leafId}` and tab ids never contain ":", so the
// prefix up to the first ":" is the owning tab id (see
// selectFloatingWorkspaceHasUnread). Prefix-match to keep legacy keys.
const separatorIndex = paneKey.indexOf(':')
const owningTabId = separatorIndex === -1 ? paneKey : paneKey.slice(0, separatorIndex)
if (owningTabId === tabId) {
return true
}
}
return false
return unreadAgentCompletionPanes
? getUnreadAgentCompletionTabIds(unreadAgentCompletionPanes).has(tabId)
: false
}
/** Test-only: clear the memoized per-tab flag cache between cases. */
export function resetTerminalTabActivityFlagsCacheForTest(): void {
flagsCache = null
}
/** Test-only: clear the unread marker snapshot index between cases. */
export function resetUnreadAgentCompletionTabIdsCacheForTest(): void {
unreadAgentCompletionTabIdsBySnapshot = new WeakMap()
}