perf(sidebar): share tab ownership across activity projections

This commit is contained in:
Neil
2026-11-11 15:52:05 -08:00
parent c0e3523606
commit 1a76ebe364
4 changed files with 30 additions and 25 deletions
@@ -18,7 +18,7 @@ import {
import type { AgentStatusOrchestrationContext } from '../../../../../../shared/agent-status/contracts/agent-status-types'
import { agentVerdictDisplayMark } from '../../../../../../shared/agent-status/lifecycle/agent-main-agent-verdict'
import { applyAgentPaneActivityFlags } from '../../../../../../shared/worktree-status-rollup'
import { getTabIdToWorktreeId } from './worktree-agent-row-selectors'
import { getTabIdToWorktreeId } from '@/lib/workspace/tabs/tab-worktree-index'
export type WorktreeAgentActivitySummary = {
hasPermission: boolean
@@ -1,6 +1,7 @@
import { getStructuredAgentSessionTabsByPaneKey } from '@/components/native-chat/session/status/structured-agent-session-tabs'
import type { RetainedAgentEntry } from '@/store/slices/agent-status/agent-status-contract'
import type { AppState } from '@/store/types'
import { getTabIdToWorktreeId } from '@/lib/workspace/tabs/tab-worktree-index'
import type { AgentStatusEntry } from '../../../../../../shared/agent-status/contracts/agent-status-types'
import type { MigrationUnsupportedPtyEntry } from '../../../../../../shared/agent-status/contracts/agent-status-ipc-payload'
import { parsePaneKey } from '../../../../../../shared/workspace-layout/stable-pane-id'
@@ -35,10 +36,8 @@ type WorktreeAgentRowsState = Pick<
unifiedTabsByWorktree?: AppState['unifiedTabsByWorktree']
}
type TabOwnershipRows = Record<string, readonly { id: string }[]>
type TabWorktreeIndexCache = {
tabsByWorktree: TabOwnershipRows
tabsByWorktree: WorktreeAgentRowsState['tabsByWorktree']
tabIdToWorktreeId: Map<string, string>
}
@@ -57,7 +56,6 @@ type RetainedEntriesByWorktreeCache = {
entriesByWorktree: Map<string, RetainedAgentEntry[]>
}
let tabWorktreeIndexCache: TabWorktreeIndexCache | null = null
let liveTabWorktreeIndexCache: LiveTabWorktreeIndexCache | null = null
let liveEntriesByWorktreeCache: LiveEntriesByWorktreeCache | null = null
let migrationUnsupportedByWorktreeCache: MigrationUnsupportedByWorktreeCache | null = null
@@ -76,20 +74,7 @@ export function reuseArrayIfEqual<T>(previous: T[] | undefined, next: T[]): T[]
return previous
}
// Settings reuses this tab index to avoid rescanning every bucket on each store write.
export function getTabIdToWorktreeId(tabsByWorktree: TabOwnershipRows): Map<string, string> {
if (tabWorktreeIndexCache?.tabsByWorktree === tabsByWorktree) {
return tabWorktreeIndexCache.tabIdToWorktreeId
}
const tabIdToWorktreeId = new Map<string, string>()
for (const [worktreeId, tabs] of Object.entries(tabsByWorktree)) {
for (const tab of tabs) {
tabIdToWorktreeId.set(tab.id, worktreeId)
}
}
tabWorktreeIndexCache = { tabsByWorktree, tabIdToWorktreeId }
return tabIdToWorktreeId
}
export { getTabIdToWorktreeId }
function getLiveTabIdToWorktreeId(
tabsByWorktree: WorktreeAgentRowsState['tabsByWorktree'],
@@ -1,4 +1,5 @@
import type { AppState } from '@/store/types'
import { getTabIdToWorktreeId } from '../tabs/tab-worktree-index'
import { createAgentStatusEpochSelector } from '../../agent-status/evidence/agent-status-epoch-selector'
import { tabHasLivePty } from '../../../../../shared/tab-has-live-pty'
import type { TerminalTab } from '../../../../../shared/workspace-layout/terminal-tab-types'
@@ -50,12 +51,7 @@ export function getLiveAgentStatusByWorktreeId(
if (entries.length === 0) {
return new Map()
}
const worktreeIdByTabId = new Map<string, string>()
for (const [worktreeId, tabs] of Object.entries(tabsByWorktree ?? {})) {
for (const tab of tabs) {
worktreeIdByTabId.set(tab.id, worktreeId)
}
}
const worktreeIdByTabId = getTabIdToWorktreeId(tabsByWorktree)
const result = new Map<string, LiveAgentWorktreeStatus>()
for (const entry of entries) {
const worktreeId = resolveAgentStatusWorktreeId(entry, worktreeIdByTabId)
@@ -0,0 +1,24 @@
export type TabOwnershipRows = Record<string, readonly { id: string }[]>
// Keep full and projected catalogs cached without retaining replaced snapshots.
const tabWorktreeIndexes = new WeakMap<TabOwnershipRows, Map<string, string>>()
export function getTabIdToWorktreeId(
tabsByWorktree: TabOwnershipRows | null | undefined
): Map<string, string> {
if (!tabsByWorktree) {
return new Map()
}
const cached = tabWorktreeIndexes.get(tabsByWorktree)
if (cached) {
return cached
}
const index = new Map<string, string>()
for (const [worktreeId, tabs] of Object.entries(tabsByWorktree)) {
for (const tab of tabs) {
index.set(tab.id, worktreeId)
}
}
tabWorktreeIndexes.set(tabsByWorktree, index)
return index
}