From 74e68afbb2e3dc321aa74f0823534a304c4ae14e Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:51:23 -0700 Subject: [PATCH] perf(renderer): share terminal agent type index (#8066) --- .../components/terminal-pane/TerminalPane.tsx | 19 ++--- .../terminal-tab-agent-type-index.test.ts | 74 +++++++++++++++++++ .../terminal-tab-agent-type-index.ts | 73 ++++++++++++++++++ 3 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 src/renderer/src/components/terminal-pane/terminal-tab-agent-type-index.test.ts create mode 100644 src/renderer/src/components/terminal-pane/terminal-tab-agent-type-index.ts diff --git a/src/renderer/src/components/terminal-pane/TerminalPane.tsx b/src/renderer/src/components/terminal-pane/TerminalPane.tsx index 297b28da36d..57b0a692776 100644 --- a/src/renderer/src/components/terminal-pane/TerminalPane.tsx +++ b/src/renderer/src/components/terminal-pane/TerminalPane.tsx @@ -101,7 +101,6 @@ import { import { shouldChatTakeOverMobileSurface } from '../native-chat/native-chat-send-eligibility' import { canToggleNativeChat } from '../native-chat/native-chat-availability' import { isNativeChatTranscriptLocalReadable } from '@/lib/native-chat-transcript-readability' -import type { AgentType } from '../../../../shared/agent-status-types' import { resolvePaneKeyForManager } from '@/lib/pane-manager/pane-key-resolution' import { safeFit } from '@/lib/pane-manager/pane-tree-ops' import { captureTerminalShutdownLayout } from './terminal-shutdown-layout-capture' @@ -148,6 +147,7 @@ import { scheduleImagePasteWebglAtlasRecovery } from './terminal-webgl-atlas-rec import { restoreTerminalFitToDesktop, restoreTerminalFitsToDesktop } from './terminal-fit-restore' import { useVisibleTerminalTabClaim } from './use-visible-terminal-tab-claim' import { TerminalSshReconnectOverlay } from './TerminalSshReconnectOverlay' +import { selectTerminalTabAgentTypesByLeaf } from './terminal-tab-agent-type-index' const NATIVE_CHAT_ROOT_SELECTOR = '[data-native-chat-root="true"]' @@ -680,19 +680,10 @@ export default function TerminalPane({ // when Orca launched a *supported* agent here or one was detected live for the // leaf, keyed `${tabId}:${leafId}`. Carry the agent identity, not just "an // agent exists", so the gate can reject Grok et al. - // Scoped to this tab's panes (leafId → agentType) and shallow-compared so an - // unrelated tab's agent status tick doesn't re-render this pane. - const tabAgentTypeByLeaf = useAppStore( - useShallow((store) => { - const prefix = `${tabId}:` - const byLeaf: Record = {} - for (const [paneKey, entry] of Object.entries(store.agentStatusByPaneKey)) { - if (paneKey.startsWith(prefix) && entry.agentType) { - byLeaf[paneKey.slice(prefix.length)] = entry.agentType - } - } - return byLeaf - }) + // Scope to this tab's panes and reuse the shared map index so hidden tabs do + // not each rescan every agent entry on unrelated store writes. + const tabAgentTypeByLeaf = useAppStore((store) => + selectTerminalTabAgentTypesByLeaf(store.agentStatusByPaneKey, tabId) ) const toggleTabViewMode = useAppStore((store) => store.toggleTabViewMode) const savedLayout = useAppStore((store) => store.terminalLayoutsByTabId[tabId] ?? EMPTY_LAYOUT) diff --git a/src/renderer/src/components/terminal-pane/terminal-tab-agent-type-index.test.ts b/src/renderer/src/components/terminal-pane/terminal-tab-agent-type-index.test.ts new file mode 100644 index 00000000000..bf000ffc697 --- /dev/null +++ b/src/renderer/src/components/terminal-pane/terminal-tab-agent-type-index.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, it, vi } from 'vitest' +import type { AgentStatusEntry } from '../../../../shared/agent-status-types' +import { createTerminalTabAgentTypeSelector } from './terminal-tab-agent-type-index' + +function entry(agentType: AgentStatusEntry['agentType'], state = 'working'): AgentStatusEntry { + return { agentType, state, updatedAt: 0 } as AgentStatusEntry +} + +describe('createTerminalTabAgentTypeSelector', () => { + it('scans one global map only once across all mounted tab selectors', () => { + const onEntryVisited = vi.fn() + const select = createTerminalTabAgentTypeSelector({ onEntryVisited }) + const state = { + 'tab-1:leaf-a': entry('claude'), + 'tab-1:leaf-b': entry('codex'), + 'tab-2:leaf-c': entry('grok') + } + + expect(select(state, 'tab-1')).toEqual({ 'leaf-a': 'claude', 'leaf-b': 'codex' }) + expect(select(state, 'tab-2')).toEqual({ 'leaf-c': 'grok' }) + for (let index = 0; index < 100; index += 1) { + select(state, `hidden-tab-${index}`) + } + + expect(onEntryVisited).toHaveBeenCalledTimes(3) + }) + + it('reuses per-tab results across state-only agent transitions', () => { + const select = createTerminalTabAgentTypeSelector() + const working = { + 'tab-1:leaf-a': entry('claude', 'working'), + 'tab-2:leaf-b': entry('codex', 'working') + } + const firstTab = select(working, 'tab-1') + const secondTab = select(working, 'tab-2') + const done = { + 'tab-1:leaf-a': entry('claude', 'done'), + 'tab-2:leaf-b': entry('codex', 'done') + } + + expect(select(done, 'tab-1')).toBe(firstTab) + expect(select(done, 'tab-2')).toBe(secondTab) + }) + + it('changes only the tab whose agent identity changes', () => { + const select = createTerminalTabAgentTypeSelector() + const before = { + 'tab-1:leaf-a': entry('claude'), + 'tab-2:leaf-b': entry('codex') + } + const firstTab = select(before, 'tab-1') + const secondTab = select(before, 'tab-2') + const after = { + ...before, + 'tab-2:leaf-b': entry('grok') + } + + expect(select(after, 'tab-1')).toBe(firstTab) + expect(select(after, 'tab-2')).not.toBe(secondTab) + expect(select(after, 'tab-2')).toEqual({ 'leaf-b': 'grok' }) + }) + + it('ignores missing agent types and keys without a tab prefix', () => { + const select = createTerminalTabAgentTypeSelector() + const state = { + malformed: entry('claude'), + ':leaf-a': entry('codex'), + 'tab-1:leaf-a': entry(undefined) + } + + expect(select(state, 'tab-1')).toEqual({}) + expect(select(state, 'malformed')).toEqual({}) + }) +}) diff --git a/src/renderer/src/components/terminal-pane/terminal-tab-agent-type-index.ts b/src/renderer/src/components/terminal-pane/terminal-tab-agent-type-index.ts new file mode 100644 index 00000000000..1b7693b45ab --- /dev/null +++ b/src/renderer/src/components/terminal-pane/terminal-tab-agent-type-index.ts @@ -0,0 +1,73 @@ +import type { AgentStatusEntry, AgentType } from '../../../../shared/agent-status-types' + +export type TerminalTabAgentTypeState = Record +export type TerminalTabAgentTypesByLeaf = Readonly> + +type SelectorDependencies = { + onEntryVisited?: (paneKey: string) => void +} + +const EMPTY_AGENT_TYPES_BY_LEAF: TerminalTabAgentTypesByLeaf = Object.freeze({}) + +function reuseRecordIfEqual( + previous: TerminalTabAgentTypesByLeaf | undefined, + next: Record +): TerminalTabAgentTypesByLeaf { + if (!previous) { + return next + } + const nextKeys = Object.keys(next) + if (Object.keys(previous).length !== nextKeys.length) { + return next + } + return nextKeys.every((key) => previous[key] === next[key]) ? previous : next +} + +export function createTerminalTabAgentTypeSelector( + dependencies: SelectorDependencies = {} +): (state: TerminalTabAgentTypeState, tabId: string) => TerminalTabAgentTypesByLeaf { + let cachedState: TerminalTabAgentTypeState | null = null + let cachedByTabId = new Map() + + return (state, tabId) => { + // Why: production writes replace this map. Its identity lets unrelated + // Zustand notifications skip the global scan entirely. + if (state !== cachedState) { + const previousByTabId = cachedByTabId + const nextByTabId = new Map>() + for (const [paneKey, entry] of Object.entries(state)) { + dependencies.onEntryVisited?.(paneKey) + if (!entry.agentType) { + continue + } + const separator = paneKey.indexOf(':') + if (separator <= 0) { + continue + } + const entryTabId = paneKey.slice(0, separator) + const leafId = paneKey.slice(separator + 1) + const byLeaf = nextByTabId.get(entryTabId) + if (byLeaf) { + byLeaf[leafId] = entry.agentType + } else { + nextByTabId.set(entryTabId, { [leafId]: entry.agentType }) + } + } + + const stabilizedByTabId = new Map() + for (const [entryTabId, byLeaf] of nextByTabId) { + stabilizedByTabId.set( + entryTabId, + reuseRecordIfEqual(previousByTabId.get(entryTabId), byLeaf) + ) + } + cachedByTabId = stabilizedByTabId + cachedState = state + } + return cachedByTabId.get(tabId) ?? EMPTY_AGENT_TYPES_BY_LEAF + } +} + +// Why: TerminalPane is mounted once per retained tab. Share one index so a +// store write scans the global agent map once, not once for every hidden tab. +export const selectTerminalTabAgentTypesByLeaf = createTerminalTabAgentTypeSelector()