mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user