perf(status-bar): avoid resource badge rebuilds on title churn (#8152)

This commit is contained in:
Neil 2026-07-10 20:52:15 -07:00 committed by GitHub
parent 141b9b7de2
commit 90afbc3cf3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 378 additions and 11 deletions

View File

@ -56,8 +56,10 @@ import {
} from './resource-session-navigation'
import {
getResourceUsageAllWorktrees,
getResourceUsagePtyIdsByTabId,
getResourceUsageRepos,
getResourceUsageRuntimePaneTitlesByTabId,
getResourceUsageTerminalLayoutsByTabId,
getResourceUsageTabsByWorktree
} from './resource-usage-open-slices'
import {
@ -73,9 +75,11 @@ import {
countUnboundDaemonSessions,
type ResourceSessionBindingInputs
} from './resource-session-bindings'
import { createClosedResourceSessionCountSelector } from './resource-session-count-selector'
import { translate } from '@/i18n/i18n'
const POLL_MS = 2_000
const selectClosedResourceSessionCount = createClosedResourceSessionCountSelector()
type SortOption = 'memory' | 'cpu' | 'name'
@ -737,9 +741,7 @@ export function ResourceUsageStatusSegment({
const memorySnapshotError = useAppStore((s) => s.memorySnapshotError)
const fetchSnapshot = useAppStore((s) => s.fetchMemorySnapshot)
const workspaceSessionReady = useAppStore((s) => s.workspaceSessionReady)
const ptyIdsByTabId = useAppStore((s) => s.ptyIdsByTabId)
const tabsByWorktreeForPtyBindings = useAppStore((s) => s.tabsByWorktree)
const terminalLayoutsByTabId = useAppStore((s) => s.terminalLayoutsByTabId)
const closedSessionCount = useAppStore(selectClosedResourceSessionCount)
const setActiveView = useAppStore((s) => s.setActiveView)
const openModal = useAppStore((s) => s.openModal)
const openSpacePage = useAppStore((s) => s.openSpacePage)
@ -774,17 +776,21 @@ export function ResourceUsageStatusSegment({
const repos = useAppStore((s) => getResourceUsageRepos(s, open))
const allWorktrees = useAppStore((s) => getResourceUsageAllWorktrees(s, open))
const tabsByWorktree = useAppStore((s) => getResourceUsageTabsByWorktree(s, open))
// Why: the closed trigger owns a scalar selector. Full binding maps stay
// behind open sentinels so unchanged counts do not rerender the segment.
const ptyIdsByTabId = useAppStore((s) => getResourceUsagePtyIdsByTabId(s, open))
const terminalLayoutsByTabId = useAppStore((s) => getResourceUsageTerminalLayoutsByTabId(s, open))
const resourceSnapshot = snapshot
// Why: ptyIdsByTabId intentionally tracks mounted/live panes only. Resource
// Manager also reads restored wake hints, but only for classification.
const resourceSessionBindings = useMemo<ResourceSessionBindingInputs>(
() => ({
ptyIdsByTabId,
tabsByWorktree: tabsByWorktreeForPtyBindings,
tabsByWorktree,
terminalLayoutsByTabId,
workspaceSessionReady
}),
[ptyIdsByTabId, tabsByWorktreeForPtyBindings, terminalLayoutsByTabId, workspaceSessionReady]
[ptyIdsByTabId, tabsByWorktree, terminalLayoutsByTabId, workspaceSessionReady]
)
// Why: after a kill confirms and the session unmounts, focus would otherwise
@ -975,12 +981,6 @@ export function ResourceUsageStatusSegment({
return countUnboundDaemonSessions(sessions, resourceSessionBindings)
}, [open, sessions, resourceSessionBindings, workspaceSessionReady])
const closedSessionCount = useMemo(() => {
if (!workspaceSessionReady) {
return 0
}
return buildResourceSessionBindingIndex(resourceSessionBindings).boundPtyIds.size
}, [resourceSessionBindings, workspaceSessionReady])
const triggerSessionCount = open ? sessions.length : closedSessionCount
const { totalMemory, totalCpu, hostShare, memBadgeLabel } = useMemo(() => {

View File

@ -0,0 +1,219 @@
import { describe, expect, it, vi } from 'vitest'
import type { TerminalLayoutSnapshot, TerminalTab } from '../../../../shared/types'
import {
buildResourceSessionBindingIndex,
type ResourceSessionBindingInputs
} from './resource-session-bindings'
import {
createClosedResourceSessionCountSelector,
type ClosedResourceSessionCountState
} from './resource-session-count-selector'
const TAB_COUNT = 100
const TITLE_WRITES = 600
function makeTab(id: string, ptyId: string | null = null, title = 'Terminal'): TerminalTab {
return {
id,
ptyId,
worktreeId: `wt-${id}`,
title,
customTitle: null,
color: null,
sortOrder: 0,
createdAt: 0
}
}
function makeLayout(ptyIdsByLeafId: Record<string, string>): TerminalLayoutSnapshot {
return {
root: { type: 'leaf', leafId: 'leaf-1' },
activeLeafId: 'leaf-1',
expandedLeafId: null,
ptyIdsByLeafId
}
}
function makeState(
overrides: Partial<ClosedResourceSessionCountState> = {}
): ClosedResourceSessionCountState {
return {
tabsByWorktree: {},
ptyIdsByTabId: {},
terminalLayoutsByTabId: {},
workspaceSessionReady: true,
...overrides
}
}
function withCountedIteration(tabs: TerminalTab[], onVisit: () => void): TerminalTab[] {
return new Proxy(tabs, {
get(target, property, receiver) {
if (property !== Symbol.iterator) {
return Reflect.get(target, property, receiver)
}
return function* countedIterator(): Generator<TerminalTab> {
for (const tab of target) {
onVisit()
yield tab
}
}
}
})
}
function makeScaleTabs(onVisit: () => void): Record<string, TerminalTab[]> {
return Object.fromEntries(
Array.from({ length: TAB_COUNT }, (_, index) => {
const worktreeId = `wt-${index}`
return [worktreeId, withCountedIteration([makeTab(`tab-${index}`, `pty-${index}`)], onVisit)]
})
)
}
function countNextTabComparison(
tab: TerminalTab,
onComparison: () => void
): [TerminalTab[], () => void] {
let counting = true
const tabs = new Proxy([tab], {
get(target, property, receiver) {
if (counting && property === '0') {
onComparison()
}
return Reflect.get(target, property, receiver)
}
})
return [
tabs,
() => {
counting = false
}
]
}
describe('closed resource session count selector', () => {
it('removes full binding-index rebuilds from title-only tab churn at scale', () => {
let oldPathTabVisits = 0
const initialTabs = makeScaleTabs(() => {
oldPathTabVisits += 1
})
const inputs: ResourceSessionBindingInputs = {
tabsByWorktree: initialTabs,
ptyIdsByTabId: {},
terminalLayoutsByTabId: {},
workspaceSessionReady: true
}
// Why: this models the previous useMemo dependency on tabsByWorktree:
// every title write replaces that map and rebuilds both tab passes.
for (let index = 0; index < TITLE_WRITES; index += 1) {
buildResourceSessionBindingIndex({ ...inputs, tabsByWorktree: { ...initialTabs } })
}
expect(oldPathTabVisits).toBe(120_000)
let optimizedTabVisits = 0
const optimizedTabs = makeScaleTabs(() => {
optimizedTabVisits += 1
})
const buildIndex = vi.fn(buildResourceSessionBindingIndex)
const selectCount = createClosedResourceSessionCountSelector(buildIndex)
let state = makeState({ tabsByWorktree: optimizedTabs })
const currentTabByWorktree = Object.fromEntries(
Object.entries(optimizedTabs).map(([worktreeId, tabs]) => [worktreeId, tabs[0]])
)
let changedArrayTabComparisons = 0
expect(selectCount(state)).toBe(TAB_COUNT)
buildIndex.mockClear()
optimizedTabVisits = 0
for (let index = 0; index < TITLE_WRITES; index += 1) {
const worktreeId = `wt-${index % TAB_COUNT}`
const nextTab = { ...currentTabByWorktree[worktreeId], title: `Terminal ${index}` }
const [nextTabs, stopCounting] = countNextTabComparison(nextTab, () => {
changedArrayTabComparisons += 1
})
state = {
...state,
tabsByWorktree: {
...state.tabsByWorktree,
[worktreeId]: nextTabs
}
}
expect(selectCount(state)).toBe(TAB_COUNT)
stopCounting()
currentTabByWorktree[worktreeId] = nextTab
}
expect(buildIndex).not.toHaveBeenCalled()
expect(optimizedTabVisits).toBe(0)
expect(changedArrayTabComparisons).toBe(TITLE_WRITES)
})
it('reacts to every binding and readiness input while ignoring display-only tab fields', () => {
const buildIndex = vi.fn(buildResourceSessionBindingIndex)
const selectCount = createClosedResourceSessionCountSelector(buildIndex)
let state = makeState({
workspaceSessionReady: false,
tabsByWorktree: {
'wt-1': [makeTab('tab-1', 'pty-wake')]
},
ptyIdsByTabId: {
'tab-1': ['pty-live']
},
terminalLayoutsByTabId: {
'tab-1': makeLayout({ 'leaf-1': 'pty-layout' })
}
})
expect(selectCount(state)).toBe(0)
expect(buildIndex).not.toHaveBeenCalled()
state = { ...state, workspaceSessionReady: true }
expect(selectCount(state)).toBe(3)
expect(buildIndex).toHaveBeenCalledTimes(1)
state = {
...state,
tabsByWorktree: {
'wt-1': [{ ...state.tabsByWorktree['wt-1'][0], title: 'Working' }]
}
}
expect(selectCount(state)).toBe(3)
expect(buildIndex).toHaveBeenCalledTimes(1)
state = {
...state,
ptyIdsByTabId: { 'tab-1': ['pty-live', 'pty-live-2'] }
}
expect(selectCount(state)).toBe(4)
expect(buildIndex).toHaveBeenCalledTimes(2)
state = {
...state,
terminalLayoutsByTabId: {
'tab-1': makeLayout({ 'leaf-1': 'pty-layout', 'leaf-2': 'pty-layout-2' })
}
}
expect(selectCount(state)).toBe(5)
expect(buildIndex).toHaveBeenCalledTimes(3)
state = {
...state,
tabsByWorktree: {
'wt-1': [{ ...state.tabsByWorktree['wt-1'][0], ptyId: null }]
}
}
expect(selectCount(state)).toBe(4)
expect(buildIndex).toHaveBeenCalledTimes(4)
state = { ...state, tabsByWorktree: {} }
expect(selectCount(state)).toBe(2)
expect(buildIndex).toHaveBeenCalledTimes(5)
state = { ...state, workspaceSessionReady: false }
expect(selectCount(state)).toBe(0)
expect(buildIndex).toHaveBeenCalledTimes(5)
})
})

View File

@ -0,0 +1,102 @@
import type { AppState } from '../../store'
import {
buildResourceSessionBindingIndex,
type ResourceSessionBindingIndex,
type ResourceSessionBindingInputs
} from './resource-session-bindings'
export type ClosedResourceSessionCountState = Pick<
AppState,
'tabsByWorktree' | 'ptyIdsByTabId' | 'terminalLayoutsByTabId' | 'workspaceSessionReady'
>
type BuildResourceSessionBindingIndex = (
inputs: ResourceSessionBindingInputs
) => ResourceSessionBindingIndex
export type ClosedResourceSessionCountSelector = (state: ClosedResourceSessionCountState) => number
function haveSameTabBindings(
previous: AppState['tabsByWorktree'],
next: AppState['tabsByWorktree']
): boolean {
if (previous === next) {
return true
}
const previousWorktreeIds = Object.keys(previous)
const nextWorktreeIds = Object.keys(next)
if (previousWorktreeIds.length !== nextWorktreeIds.length) {
return false
}
for (const worktreeId of nextWorktreeIds) {
const previousTabs = previous[worktreeId]
const nextTabs = next[worktreeId]
if (previousTabs === nextTabs) {
continue
}
if (!previousTabs || previousTabs.length !== nextTabs.length) {
return false
}
for (let index = 0; index < nextTabs.length; index += 1) {
const previousTab = previousTabs[index]
const nextTab = nextTabs[index]
// Why: the closed badge counts PTY ownership only. Titles and other
// display fields can churn per terminal frame without changing it.
if (previousTab.id !== nextTab.id || previousTab.ptyId !== nextTab.ptyId) {
return false
}
}
}
return true
}
export function createClosedResourceSessionCountSelector(
buildBindingIndex: BuildResourceSessionBindingIndex = buildResourceSessionBindingIndex
): ClosedResourceSessionCountSelector {
// Why: Zustand runs selectors for every store notification. Keep the last
// liveness inputs here so unrelated and title-only writes stay scalar-cheap.
let initialized = false
let previousTabsByWorktree: AppState['tabsByWorktree'] = {}
let previousPtyIdsByTabId: AppState['ptyIdsByTabId'] = {}
let previousTerminalLayoutsByTabId: AppState['terminalLayoutsByTabId'] = {}
let previousWorkspaceSessionReady = false
let count = 0
return (state): number => {
const bindingMapChanged =
state.ptyIdsByTabId !== previousPtyIdsByTabId ||
state.terminalLayoutsByTabId !== previousTerminalLayoutsByTabId
const readinessChanged = state.workspaceSessionReady !== previousWorkspaceSessionReady
const tabsReferenceChanged = state.tabsByWorktree !== previousTabsByWorktree
let tabBindingsChanged = tabsReferenceChanged
if (
initialized &&
state.workspaceSessionReady &&
!bindingMapChanged &&
!readinessChanged &&
tabsReferenceChanged
) {
tabBindingsChanged = !haveSameTabBindings(previousTabsByWorktree, state.tabsByWorktree)
}
const shouldRebuild =
state.workspaceSessionReady &&
(!initialized || bindingMapChanged || readinessChanged || tabBindingsChanged)
if (shouldRebuild) {
count = buildBindingIndex(state).boundPtyIds.size
} else if (!state.workspaceSessionReady) {
count = 0
}
previousTabsByWorktree = state.tabsByWorktree
previousPtyIdsByTabId = state.ptyIdsByTabId
previousTerminalLayoutsByTabId = state.terminalLayoutsByTabId
previousWorkspaceSessionReady = state.workspaceSessionReady
initialized = true
return count
}
}

View File

@ -1,8 +1,10 @@
import { describe, expect, it } from 'vitest'
import {
getResourceUsageAllWorktrees,
getResourceUsagePtyIdsByTabId,
getResourceUsageRepos,
getResourceUsageRuntimePaneTitlesByTabId,
getResourceUsageTerminalLayoutsByTabId,
getResourceUsageTabsByWorktree
} from './resource-usage-open-slices'
import type { AppState } from '../../store'
@ -41,11 +43,21 @@ const worktree = (): AppState['worktreesByRepo'][string][number] => ({
describe('resource usage open slices', () => {
it('returns stable empty slices while the popover is closed', () => {
const tabsByWorktree = { 'wt-1': [terminalTab('tab-1')] }
const ptyIdsByTabId = { 'tab-1': ['pty-1'] }
const terminalLayoutsByTabId = {
'tab-1': {
root: { type: 'leaf' as const, leafId: 'leaf-1' },
activeLeafId: 'leaf-1',
expandedLeafId: null
}
}
const runtimePaneTitlesByTabId = {
'tab-1': { 'tab-1:0': 'Working' }
} as AppState['runtimePaneTitlesByTabId']
const closedTabs = getResourceUsageTabsByWorktree({ tabsByWorktree }, false)
const closedPtyIds = getResourceUsagePtyIdsByTabId({ ptyIdsByTabId }, false)
const closedLayouts = getResourceUsageTerminalLayoutsByTabId({ terminalLayoutsByTabId }, false)
const closedTitles = getResourceUsageRuntimePaneTitlesByTabId(
{ runtimePaneTitlesByTabId },
false
@ -55,17 +67,35 @@ describe('resource usage open slices', () => {
expect(closedTitles).toBe(
getResourceUsageRuntimePaneTitlesByTabId({ runtimePaneTitlesByTabId: {} }, false)
)
expect(closedPtyIds).toBe(getResourceUsagePtyIdsByTabId({ ptyIdsByTabId: {} }, false))
expect(closedLayouts).toBe(
getResourceUsageTerminalLayoutsByTabId({ terminalLayoutsByTabId: {} }, false)
)
expect(closedTabs).toEqual({})
expect(closedPtyIds).toEqual({})
expect(closedLayouts).toEqual({})
expect(closedTitles).toEqual({})
})
it('returns live slices while the popover is open', () => {
const tabsByWorktree = { 'wt-1': [terminalTab('tab-1')] }
const ptyIdsByTabId = { 'tab-1': ['pty-1'] }
const terminalLayoutsByTabId = {
'tab-1': {
root: { type: 'leaf' as const, leafId: 'leaf-1' },
activeLeafId: 'leaf-1',
expandedLeafId: null
}
}
const runtimePaneTitlesByTabId = {
'tab-1': { 'tab-1:0': 'Working' }
} as AppState['runtimePaneTitlesByTabId']
expect(getResourceUsageTabsByWorktree({ tabsByWorktree }, true)).toBe(tabsByWorktree)
expect(getResourceUsagePtyIdsByTabId({ ptyIdsByTabId }, true)).toBe(ptyIdsByTabId)
expect(getResourceUsageTerminalLayoutsByTabId({ terminalLayoutsByTabId }, true)).toBe(
terminalLayoutsByTabId
)
expect(getResourceUsageRuntimePaneTitlesByTabId({ runtimePaneTitlesByTabId }, true)).toBe(
runtimePaneTitlesByTabId
)

View File

@ -2,6 +2,8 @@ import type { AppState } from '../../store'
import { getAllWorktreesFromState } from '../../store/selectors'
const EMPTY_TABS_BY_WORKTREE: AppState['tabsByWorktree'] = {}
const EMPTY_PTY_IDS_BY_TAB_ID: AppState['ptyIdsByTabId'] = {}
const EMPTY_TERMINAL_LAYOUTS_BY_TAB_ID: AppState['terminalLayoutsByTabId'] = {}
const EMPTY_RUNTIME_PANE_TITLES_BY_TAB_ID: AppState['runtimePaneTitlesByTabId'] = {}
const EMPTY_REPOS: AppState['repos'] = []
const EMPTY_WORKTREES: ReturnType<typeof getAllWorktreesFromState> = []
@ -13,6 +15,20 @@ export function getResourceUsageTabsByWorktree(
return open ? state.tabsByWorktree : EMPTY_TABS_BY_WORKTREE
}
export function getResourceUsagePtyIdsByTabId(
state: Pick<AppState, 'ptyIdsByTabId'>,
open: boolean
): AppState['ptyIdsByTabId'] {
return open ? state.ptyIdsByTabId : EMPTY_PTY_IDS_BY_TAB_ID
}
export function getResourceUsageTerminalLayoutsByTabId(
state: Pick<AppState, 'terminalLayoutsByTabId'>,
open: boolean
): AppState['terminalLayoutsByTabId'] {
return open ? state.terminalLayoutsByTabId : EMPTY_TERMINAL_LAYOUTS_BY_TAB_ID
}
export function getResourceUsageRuntimePaneTitlesByTabId(
state: Pick<AppState, 'runtimePaneTitlesByTabId'>,
open: boolean