perf: cache terminal tab lookup per tab array (#3938)

This commit is contained in:
Neil
2026-05-30 14:11:19 -07:00
committed by GitHub
parent 0bf8b4a8d8
commit e15bb949d7
3 changed files with 85 additions and 2 deletions
@@ -88,6 +88,7 @@ import {
applyTerminalPaneAttentionToManager,
subscribeTerminalPaneAttention
} from './terminal-pane-attention-subscriptions'
import { getCachedTerminalTabForWorktree } from './terminal-tab-lookup'
type TerminalPaneProps = {
tabId: string
@@ -335,8 +336,8 @@ export default function TerminalPane({
)
const clearCodexRestartNotice = useAppStore((store) => store.clearCodexRestartNotice)
const savedLayout = useAppStore((store) => store.terminalLayoutsByTabId[tabId] ?? EMPTY_LAYOUT)
const terminalTab = useAppStore(
(store) => (store.tabsByWorktree[worktreeId] ?? []).find((tab) => tab.id === tabId) ?? null
const terminalTab = useAppStore((store) =>
getCachedTerminalTabForWorktree(store.tabsByWorktree, worktreeId, tabId)
)
const setTabLayout = useAppStore((store) => store.setTabLayout)
const restoredLayout = useMemo(
@@ -0,0 +1,57 @@
import { describe, expect, it, vi } from 'vitest'
import type { TerminalTab } from '../../../../shared/types'
import { getCachedTerminalTabForWorktree } from './terminal-tab-lookup'
function makeTab(id: string): TerminalTab {
return {
id,
ptyId: null,
worktreeId: 'wt-1',
title: id,
customTitle: null,
color: null,
sortOrder: 0,
createdAt: 0
}
}
function iterableTabs(tabs: TerminalTab[]): {
value: TerminalTab[]
iterator: ReturnType<typeof vi.fn>
} {
const iterator = vi.fn(function* () {
yield* tabs
})
return {
value: { [Symbol.iterator]: iterator } as unknown as TerminalTab[],
iterator
}
}
describe('getCachedTerminalTabForWorktree', () => {
it('reuses the tab lookup while the worktree tab array is unchanged', () => {
const tabs = Array.from({ length: 200 }, (_, index) => makeTab(`tab-${index}`))
const { value, iterator } = iterableTabs(tabs)
const tabsByWorktree = { 'wt-1': value }
expect(getCachedTerminalTabForWorktree(tabsByWorktree, 'wt-1', 'tab-199')).toBe(tabs[199])
expect(getCachedTerminalTabForWorktree(tabsByWorktree, 'wt-1', 'tab-0')).toBe(tabs[0])
expect(iterator).toHaveBeenCalledTimes(1)
})
it('rebuilds the lookup when the tab array reference changes', () => {
const first = iterableTabs([makeTab('tab-1')])
const second = iterableTabs([makeTab('tab-2')])
expect(getCachedTerminalTabForWorktree({ 'wt-1': first.value }, 'wt-1', 'tab-1')?.id).toBe(
'tab-1'
)
expect(getCachedTerminalTabForWorktree({ 'wt-1': second.value }, 'wt-1', 'tab-2')?.id).toBe(
'tab-2'
)
expect(first.iterator).toHaveBeenCalledTimes(1)
expect(second.iterator).toHaveBeenCalledTimes(1)
})
})
@@ -0,0 +1,25 @@
import type { TerminalTab } from '../../../../shared/types'
const terminalTabLookupByArray = new WeakMap<readonly TerminalTab[], Map<string, TerminalTab>>()
export function getCachedTerminalTabForWorktree(
tabsByWorktree: Record<string, TerminalTab[]>,
worktreeId: string,
tabId: string
): TerminalTab | null {
const tabs = tabsByWorktree[worktreeId]
if (!tabs) {
return null
}
let lookup = terminalTabLookupByArray.get(tabs)
if (!lookup) {
// Why: every mounted TerminalPane asks for its own tab on store updates.
// Build the per-array lookup once so 200 panes do not repeat the same scan.
lookup = new Map()
for (const tab of tabs) {
lookup.set(tab.id, tab)
}
terminalTabLookupByArray.set(tabs, lookup)
}
return lookup.get(tabId) ?? null
}