perf(renderer): index web session editor tab reconciliation (#8098)

Replace the per-editor-tab Array.find over the worktree's unified tabs with a
lazily-built id/fileId map, and swap two O(n^2) includes-in-a-loop scans for
Set membership. The map is only materialized when a snapshot actually carries
a mirrored editor tab, so terminal-only snapshots pay nothing.
This commit is contained in:
Neil
2026-08-15 04:27:58 -07:00
committed by GitHub
parent e919f4843d
commit c4e397bcdc
3 changed files with 125 additions and 23 deletions
@@ -0,0 +1,50 @@
import { describe, expect, it } from 'vitest'
import type { Tab } from '../../../shared/tab-types'
import { buildWebSessionExistingTabIndex } from './web-session-existing-tab-index'
const WT = 'repo::/worktree'
function makeTab(id: string, entityId: string, contentType: 'editor' | 'browser'): Tab {
return {
id,
entityId,
groupId: 'group-1',
worktreeId: WT,
contentType,
label: id,
customLabel: null,
color: null,
sortOrder: 0,
createdAt: 1
}
}
describe('buildWebSessionExistingTabIndex', () => {
it('preserves first-match editor lookup behavior across both accepted keys', () => {
const firstFileId = '/repo/file.ts'
const firstByFileId = makeTab('older-host-id', firstFileId, 'editor')
const laterByHostId = makeTab('current-host-id', '/repo/other.ts', 'editor')
const index = buildWebSessionExistingTabIndex({
unifiedTabs: [firstByFileId, laterByHostId]
})
expect(index.getEditorUnifiedTab(firstFileId, laterByHostId.id)).toBe(firstByFileId)
expect(index.getEditorUnifiedTab(laterByHostId.entityId, firstByFileId.id)).toBe(firstByFileId)
})
it('ignores browser tabs and returns null when neither key matches', () => {
const index = buildWebSessionExistingTabIndex({
unifiedTabs: [makeTab('browser-tab', 'workspace-1', 'browser')]
})
expect(index.getEditorUnifiedTab('/repo/missing.ts', 'absent-host-id')).toBeNull()
})
it('resolves by host tab id and by file id independently', () => {
const byHostId = makeTab('host-1', '/repo/a.ts', 'editor')
const index = buildWebSessionExistingTabIndex({ unifiedTabs: [byHostId] })
expect(index.getEditorUnifiedTab('/repo/unrelated.ts', 'host-1')).toBe(byHostId)
expect(index.getEditorUnifiedTab('/repo/a.ts', 'unrelated-host')).toBe(byHostId)
})
})
@@ -0,0 +1,60 @@
import type { Tab } from '../../../shared/tab-types'
type PositionedTab = {
position: number
tab: Tab
}
export type WebSessionExistingTabIndex = {
getEditorUnifiedTab: (fileId: string, hostTabId: string) => Tab | null
}
type BuildWebSessionExistingTabIndexArgs = {
unifiedTabs: readonly Tab[]
}
function setFirst<K, V>(map: Map<K, V>, key: K, value: V): void {
if (!map.has(key)) {
map.set(key, value)
}
}
export function buildWebSessionExistingTabIndex({
unifiedTabs
}: BuildWebSessionExistingTabIndexArgs): WebSessionExistingTabIndex {
let indexes: {
editorTabById: Map<string, PositionedTab>
editorTabByFileId: Map<string, PositionedTab>
} | null = null
// Why: terminal-only snapshots are the common case, so a snapshot carrying no
// mirrored editor tab never pays to walk the worktree's unified tab list.
const getIndexes = (): NonNullable<typeof indexes> => {
if (!indexes) {
const editorTabById = new Map<string, PositionedTab>()
const editorTabByFileId = new Map<string, PositionedTab>()
unifiedTabs.forEach((tab, position) => {
if (tab.contentType === 'editor') {
const positioned = { position, tab }
setFirst(editorTabById, tab.id, positioned)
setFirst(editorTabByFileId, tab.entityId, positioned)
}
})
indexes = { editorTabById, editorTabByFileId }
}
return indexes
}
return {
getEditorUnifiedTab: (fileId, hostTabId) => {
const { editorTabById, editorTabByFileId } = getIndexes()
const byHostId = editorTabById.get(hostTabId)
const byFileId = editorTabByFileId.get(fileId)
// Why: the former Array.find accepted either key, so duplicate legacy
// entries must still resolve to whichever candidate appeared first.
if (byHostId && byFileId) {
return byHostId.position <= byFileId.position ? byHostId.tab : byFileId.tab
}
return byHostId?.tab ?? byFileId?.tab ?? null
}
}
}
@@ -96,6 +96,10 @@ import {
resetWebSessionBrowserPlacementsForTests
} from './web-session-browser-placement'
import { suppressE2eWebRuntimeBrowserSnapshot } from './web-runtime-browser-creation-e2e-fault'
import {
buildWebSessionExistingTabIndex,
type WebSessionExistingTabIndex
} from './web-session-existing-tab-index'
const WEB_SESSION_GROUP_PREFIX = 'web-session-tabs:'
export const WEB_SESSION_TABS_VISIBILITY_RESUME_STAGGER_MS = 100
@@ -1394,24 +1398,11 @@ function buildEditorUnifiedTab(
}
}
function findExistingEditorUnifiedTab(
state: WebSessionTabsSyncState,
worktreeId: string,
fileId: string,
hostTabId: string
): Tab | null {
return (
(state.unifiedTabsByWorktree[worktreeId] ?? []).find(
(tab) => tab.contentType === 'editor' && (tab.id === hostTabId || tab.entityId === fileId)
) ?? null
)
}
function buildMirroredEditorTabs(
snapshot: RuntimeMobileSessionTabsResult,
environmentId: string,
state: WebSessionTabsSyncState,
worktreeOpenFileById: ReadonlyMap<string, OpenFile>,
existingTabIndex: WebSessionExistingTabIndex,
hostGroupIdByTabId: ReadonlyMap<string, string>,
fallbackGroupId: string,
sortOffset: number,
@@ -1420,12 +1411,7 @@ function buildMirroredEditorTabs(
return snapshot.tabs.filter(isReadyEditorTab).map((tab, index) => {
const fileId = localEditorFileId(tab)
const existingFile = worktreeOpenFileById.get(fileId)
const existingUnifiedTab = findExistingEditorUnifiedTab(
state,
snapshot.worktree,
fileId,
tab.id
)
const existingUnifiedTab = existingTabIndex.getEditorUnifiedTab(fileId, tab.id)
const sourceFileId = editorSourceFileId(tab)
const groupId = hostGroupIdByTabId.get(tab.id) ?? fallbackGroupId
const file: OpenFile = {
@@ -1850,8 +1836,9 @@ function buildMirroredHostGroups({
validUnifiedTabIds.has(tabId) &&
!clientGroupIdByLocalTabId.has(tabId)
)
const localHostOrderIds = new Set(localHostOrder)
const hostTabOrder = [
...(existing?.tabOrder.filter((tabId) => !localHostOrder.includes(tabId)) ?? []),
...(existing?.tabOrder.filter((tabId) => !localHostOrderIds.has(tabId)) ?? []),
...localHostOrder
]
// Why: a pending client reorder wins over a stale pre-move host order until the host echoes the move (or membership changes).
@@ -2459,6 +2446,9 @@ function applyWebSessionTabsSnapshotWithContext(
const targetGroupId = chooseTargetGroupId(state, snapshot)
const hostGroupIdByTabId = buildHostGroupIdByTabId(snapshot.tabGroups)
const existingTabIndex = buildWebSessionExistingTabIndex({
unifiedTabs: state.unifiedTabsByWorktree[worktreeId] ?? []
})
const readyBrowserTabs = snapshot.tabs.filter(isReadyBrowserTab)
const nextRemoteBrowserPageIds = new Set(readyBrowserTabs.map((tab) => tab.browserPageId))
const mirroredBrowserTabs = buildMirroredBrowserTabs(
@@ -2505,8 +2495,8 @@ function applyWebSessionTabsSnapshotWithContext(
const mirroredEditorTabs = buildMirroredEditorTabs(
snapshot,
environmentId,
state,
firstOpenFileByIdForWorktree(worktreeOpenFiles),
existingTabIndex,
hostGroupIdByTabId,
targetGroupId,
mirroredTerminalTabEntries.length + mirroredBrowserTabs.length,
@@ -2830,8 +2820,10 @@ function applyWebSessionTabsSnapshotWithContext(
.filter((tabId): tabId is string => tabId !== undefined && validTabBarIds.has(tabId))
) ?? []
const next: string[] = []
const seen = new Set<string>()
const push = (tabId: string): void => {
if (validTabBarIds.has(tabId) && !next.includes(tabId)) {
if (validTabBarIds.has(tabId) && !seen.has(tabId)) {
seen.add(tabId)
next.push(tabId)
}
}