mirror of
https://github.com/stablyai/orca.git
synced 2026-09-24 00:02:24 +00:00
perf(renderer): index unread completion tabs (#17246)
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user