mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
perf(runtime): memoize mobile snapshots for mounted terminal worktrees (#12383)
The per-worktree rebuild memo from #12207 refused to skip any worktree
with a registered TerminalPane, because builders read live PaneManager/DOM
state the memo could not witness. Hidden worktrees stay mounted (8 parked
+ 12 retained by default, unbounded with terminalHiddenViewParking off),
so that set rebuilt on every publication — on the always-on hosts the memo
targeted, ~20 worktrees paid the full content build plus the new inputs
build and deep compare every tick.
Capture the live reads instead: snapshot pane leaf ids, the live active
leaf, the serialized pane tree, and per-pane numeric/pty bindings once per
publication into MountedTerminalSurfaceCapture, compare captures by value
in the memo, and have the builders read only the capture. Live state
outside the capture is now unrepresentable in a builder, so the skip stays
provably complete — and a mounted worktree whose panes did not change
reuses its snapshot like any other.
(cherry picked from commit 64b81cde50)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { buildMobileSessionTabSnapshots } from './sync-runtime-graph'
|
||||
import { buildMobileSessionTabSnapshots, registerRuntimeTerminalTab } from './sync-runtime-graph'
|
||||
import type { AppState } from '../store/types'
|
||||
|
||||
// Why: getBrowserTabsByWorktree reads this slice once per worktree inside the build loop,
|
||||
@@ -207,3 +207,175 @@ describe('mobile session publication cost', () => {
|
||||
expect(rebuiltWorktrees).toEqual(['repo::/title-wt-7'])
|
||||
})
|
||||
})
|
||||
|
||||
// Distinct ids per test: the snapshot memo is module state shared in this file.
|
||||
const MOUNTED_WT = 'repo::/mounted-wt-0'
|
||||
const CONTROL_WT = 'repo::/mounted-wt-1'
|
||||
const MOUNTED_LEAF_ID = 'aaaaaaaa-1111-4111-8111-111111111111'
|
||||
const SPLIT_LEAF_ID = 'bbbbbbbb-1111-4111-8111-111111111111'
|
||||
|
||||
function makeMountedState(tabIdPrefix: string): {
|
||||
state: AppState
|
||||
mountedTabId: string
|
||||
titleReads: () => number
|
||||
resetTitleReads: () => void
|
||||
} {
|
||||
let titleReads = 0
|
||||
const mountedTabId = `${tabIdPrefix}-term-0`
|
||||
const state = {
|
||||
tabsByWorktree: {
|
||||
[MOUNTED_WT]: [
|
||||
{
|
||||
id: mountedTabId,
|
||||
customTitle: null,
|
||||
ptyId: null,
|
||||
get title() {
|
||||
titleReads++
|
||||
return 'Agent mounted'
|
||||
}
|
||||
}
|
||||
],
|
||||
[CONTROL_WT]: [
|
||||
{ id: `${tabIdPrefix}-term-1`, title: 'Agent control', customTitle: null, ptyId: null }
|
||||
]
|
||||
},
|
||||
terminalLayoutsByTabId: {
|
||||
[mountedTabId]: {
|
||||
root: { type: 'leaf', leafId: MOUNTED_LEAF_ID },
|
||||
activeLeafId: MOUNTED_LEAF_ID,
|
||||
expandedLeafId: null,
|
||||
ptyIdsByLeafId: { [MOUNTED_LEAF_ID]: 'pty-saved-1' }
|
||||
}
|
||||
},
|
||||
runtimePaneTitlesByTabId: {},
|
||||
groupsByWorktree: {},
|
||||
activeGroupIdByWorktree: {},
|
||||
unifiedTabsByWorktree: {},
|
||||
tabBarOrderByWorktree: {},
|
||||
activeFileId: null,
|
||||
activeFileIdByWorktree: {},
|
||||
openFiles: [],
|
||||
editorDrafts: {},
|
||||
activeTabId: null,
|
||||
agentStatusByPaneKey: {},
|
||||
browserTabsByWorktree: {}
|
||||
} as unknown as AppState
|
||||
return {
|
||||
state,
|
||||
mountedTabId,
|
||||
titleReads: () => titleReads,
|
||||
resetTitleReads: () => {
|
||||
titleReads = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeLiveSurface(tabId: string): {
|
||||
registration: Parameters<typeof registerRuntimeTerminalTab>[0]
|
||||
setPtyId: (paneId: number, ptyId: string) => void
|
||||
addPane: (paneId: number, leafId: string) => void
|
||||
} {
|
||||
const panes = [{ id: 1, leafId: MOUNTED_LEAF_ID }]
|
||||
const ptyIdByPaneId = new Map<number, string | null>([[1, 'pty-live-1']])
|
||||
const manager = {
|
||||
getPanes: () => panes.map((pane) => ({ ...pane })),
|
||||
getActivePane: () => panes[0] ?? null,
|
||||
getLeafId: (paneId: number) => panes.find((pane) => pane.id === paneId)?.leafId ?? null,
|
||||
getNumericIdForLeaf: (leafId: string) =>
|
||||
panes.find((pane) => pane.leafId === leafId)?.id ?? null
|
||||
}
|
||||
const registration = {
|
||||
tabId,
|
||||
worktreeId: MOUNTED_WT,
|
||||
getManager: () => manager,
|
||||
getContainer: () => null,
|
||||
getPtyIdForPane: (paneId: number) => ptyIdByPaneId.get(paneId) ?? null
|
||||
} as unknown as Parameters<typeof registerRuntimeTerminalTab>[0]
|
||||
return {
|
||||
registration,
|
||||
setPtyId: (paneId, ptyId) => {
|
||||
ptyIdByPaneId.set(paneId, ptyId)
|
||||
},
|
||||
addPane: (paneId, leafId) => {
|
||||
panes.push({ id: paneId, leafId })
|
||||
ptyIdByPaneId.set(paneId, `pty-live-${paneId}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function snapshotsByWorktree(
|
||||
state: AppState
|
||||
): Map<string, ReturnType<typeof buildMobileSessionTabSnapshots>[number]> {
|
||||
return new Map(
|
||||
buildMobileSessionTabSnapshots(state).map((snapshot) => [snapshot.worktree, snapshot])
|
||||
)
|
||||
}
|
||||
|
||||
describe('mounted terminal surface memoization', () => {
|
||||
it('reuses a mounted worktree snapshot when its live pane state is unchanged', () => {
|
||||
const { state, mountedTabId, titleReads, resetTitleReads } = makeMountedState('mounted-a')
|
||||
const unregister = registerRuntimeTerminalTab(makeLiveSurface(mountedTabId).registration)
|
||||
try {
|
||||
const before = snapshotsByWorktree(state)
|
||||
resetTitleReads()
|
||||
|
||||
const after = snapshotsByWorktree(state)
|
||||
|
||||
expect(titleReads()).toBe(0)
|
||||
expect(after.get(MOUNTED_WT)).toBe(before.get(MOUNTED_WT))
|
||||
} finally {
|
||||
unregister()
|
||||
}
|
||||
})
|
||||
|
||||
it('publishes a live pty rebinding on a mounted worktree', () => {
|
||||
const { state, mountedTabId } = makeMountedState('mounted-b')
|
||||
const surface = makeLiveSurface(mountedTabId)
|
||||
const unregister = registerRuntimeTerminalTab(surface.registration)
|
||||
try {
|
||||
const before = snapshotsByWorktree(state)
|
||||
|
||||
surface.setPtyId(1, 'pty-live-2')
|
||||
const after = snapshotsByWorktree(state)
|
||||
|
||||
expect(after.get(MOUNTED_WT)).not.toBe(before.get(MOUNTED_WT))
|
||||
expect(after.get(MOUNTED_WT)?.tabs).toEqual([
|
||||
expect.objectContaining({ ptyId: 'pty-live-2' })
|
||||
])
|
||||
expect(after.get(CONTROL_WT)).toBe(before.get(CONTROL_WT))
|
||||
} finally {
|
||||
unregister()
|
||||
}
|
||||
})
|
||||
|
||||
it('publishes a live pane split on a mounted worktree', () => {
|
||||
const { state, mountedTabId } = makeMountedState('mounted-c')
|
||||
const surface = makeLiveSurface(mountedTabId)
|
||||
const unregister = registerRuntimeTerminalTab(surface.registration)
|
||||
try {
|
||||
snapshotsByWorktree(state)
|
||||
|
||||
surface.addPane(2, SPLIT_LEAF_ID)
|
||||
const after = snapshotsByWorktree(state)
|
||||
|
||||
expect(
|
||||
after.get(MOUNTED_WT)?.tabs.map((tab) => (tab.type === 'terminal' ? tab.leafId : null))
|
||||
).toEqual([MOUNTED_LEAF_ID, SPLIT_LEAF_ID])
|
||||
} finally {
|
||||
unregister()
|
||||
}
|
||||
})
|
||||
|
||||
it('falls back to the persisted pty binding after the surface unmounts', () => {
|
||||
const { state, mountedTabId, titleReads, resetTitleReads } = makeMountedState('mounted-d')
|
||||
const unregister = registerRuntimeTerminalTab(makeLiveSurface(mountedTabId).registration)
|
||||
snapshotsByWorktree(state)
|
||||
|
||||
unregister()
|
||||
resetTitleReads()
|
||||
const after = snapshotsByWorktree(state)
|
||||
|
||||
expect(titleReads()).toBeGreaterThan(0)
|
||||
expect(after.get(MOUNTED_WT)?.tabs).toEqual([expect.objectContaining({ ptyId: 'pty-saved-1' })])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -31,6 +31,7 @@ import type {
|
||||
TabGroup,
|
||||
TabGroupLayoutNode,
|
||||
TerminalLayoutSnapshot,
|
||||
TerminalPaneLayoutNode,
|
||||
TerminalTab
|
||||
} from '../../../shared/types'
|
||||
import { resolveTerminalTabTitle } from '../../../shared/tab-title-resolution'
|
||||
@@ -92,6 +93,22 @@ type MobileSessionPublicationInputs = {
|
||||
generatedTitlesEnabled: boolean
|
||||
terminalTheme: RuntimeMobileTerminalTheme | undefined
|
||||
}
|
||||
/**
|
||||
* Live PaneManager/DOM reads for one mounted terminal tab, captured once per
|
||||
* publication.
|
||||
*
|
||||
* Why: builders read this instead of the registry, so live state the memo
|
||||
* cannot witness is unrepresentable — mounted worktrees memoize like the rest
|
||||
* instead of rebuilding on every publication.
|
||||
*/
|
||||
type MountedTerminalSurfaceCapture = {
|
||||
paneLeafIds: readonly string[]
|
||||
hasLiveActivePane: boolean
|
||||
liveActiveLeafId: string | null
|
||||
liveLayoutRoot: TerminalPaneLayoutNode | null
|
||||
numericPaneIdByLeafId: ReadonlyMap<string, number | null>
|
||||
ptyIdByNumericPaneId: ReadonlyMap<number, string | null>
|
||||
}
|
||||
/**
|
||||
* One worktree's complete mobile-snapshot input set.
|
||||
*
|
||||
@@ -131,8 +148,7 @@ type MobileSessionWorktreeInputs = {
|
||||
activeBrowserWorkspaceId: string | null
|
||||
generatedTitlesEnabled: boolean
|
||||
terminalTheme: RuntimeMobileTerminalTheme | undefined
|
||||
// Why: a mounted TerminalPane feeds live DOM/PaneManager state that no store reference can witness.
|
||||
hasMountedTerminalSurface: boolean
|
||||
mountedSurfaceCaptureByTabId: ReadonlyMap<string, MountedTerminalSurfaceCapture>
|
||||
}
|
||||
|
||||
const registeredTabs = new Map<string, RegisteredTerminalTab>()
|
||||
@@ -1026,11 +1042,99 @@ function buildMobileSessionWorktreeInputs(
|
||||
activeBrowserWorkspaceId: state.activeBrowserTabIdByWorktree?.[worktreeId] ?? null,
|
||||
generatedTitlesEnabled: publication.generatedTitlesEnabled,
|
||||
terminalTheme: publication.terminalTheme,
|
||||
hasMountedTerminalSurface: terminalTabs.some((tab) => registeredTabs.has(tab.id))
|
||||
mountedSurfaceCaptureByTabId: captureMountedTerminalSurfaces(
|
||||
terminalTabs,
|
||||
state.terminalLayoutsByTabId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function narrowedEntriesEqual<T>(a: ReadonlyMap<string, T>, b: ReadonlyMap<string, T>): boolean {
|
||||
function captureMountedTerminalSurfaces(
|
||||
terminalTabs: AppState['tabsByWorktree'][string],
|
||||
terminalLayoutsByTabId: AppState['terminalLayoutsByTabId']
|
||||
): ReadonlyMap<string, MountedTerminalSurfaceCapture> {
|
||||
let captures: Map<string, MountedTerminalSurfaceCapture> | null = null
|
||||
for (const tab of terminalTabs) {
|
||||
const registered = registeredTabs.get(tab.id)
|
||||
if (!registered) {
|
||||
continue
|
||||
}
|
||||
captures ??= new Map()
|
||||
captures.set(tab.id, captureMountedTerminalSurface(registered, terminalLayoutsByTabId[tab.id]))
|
||||
}
|
||||
return captures ?? EMPTY_NARROWED_BY_KEY
|
||||
}
|
||||
|
||||
function captureMountedTerminalSurface(
|
||||
registered: RegisteredTerminalTab,
|
||||
savedLayout: AppState['terminalLayoutsByTabId'][string] | undefined
|
||||
): MountedTerminalSurfaceCapture {
|
||||
const manager = registered.getManager()
|
||||
const paneLeafIds = manager?.getPanes().map((pane) => pane.leafId) ?? []
|
||||
const activePane = manager?.getActivePane() ?? null
|
||||
const firstChild = registered.getContainer()?.firstElementChild
|
||||
// Why: mirrors getRuntimeLeafIdsForTerminal so every leaf the builder resolves has a captured pane id.
|
||||
const effectiveLeafIds =
|
||||
paneLeafIds.length > 0
|
||||
? paneLeafIds
|
||||
: collectLeafIdsInOrder(savedLayout?.root).filter(isTerminalLeafId)
|
||||
const numericPaneIdByLeafId = new Map<string, number | null>()
|
||||
const ptyIdByNumericPaneId = new Map<number, string | null>()
|
||||
for (const leafId of effectiveLeafIds) {
|
||||
const numericPaneId = manager?.getNumericIdForLeaf(leafId) ?? null
|
||||
numericPaneIdByLeafId.set(leafId, numericPaneId)
|
||||
if (numericPaneId !== null) {
|
||||
ptyIdByNumericPaneId.set(numericPaneId, registered.getPtyIdForPane(numericPaneId))
|
||||
}
|
||||
}
|
||||
return {
|
||||
paneLeafIds,
|
||||
hasLiveActivePane: activePane !== null,
|
||||
liveActiveLeafId: activePane !== null ? (manager?.getLeafId(activePane.id) ?? null) : null,
|
||||
liveLayoutRoot: serializePaneTree(
|
||||
typeof HTMLElement !== 'undefined' && firstChild instanceof HTMLElement ? firstChild : null
|
||||
),
|
||||
numericPaneIdByLeafId,
|
||||
ptyIdByNumericPaneId
|
||||
}
|
||||
}
|
||||
|
||||
function mountedTerminalSurfaceCaptureEquals(
|
||||
a: MountedTerminalSurfaceCapture,
|
||||
b: MountedTerminalSurfaceCapture
|
||||
): boolean {
|
||||
return (
|
||||
a.hasLiveActivePane === b.hasLiveActivePane &&
|
||||
a.liveActiveLeafId === b.liveActiveLeafId &&
|
||||
a.paneLeafIds.length === b.paneLeafIds.length &&
|
||||
a.paneLeafIds.every((leafId, index) => b.paneLeafIds[index] === leafId) &&
|
||||
narrowedEntriesEqual(a.numericPaneIdByLeafId, b.numericPaneIdByLeafId) &&
|
||||
narrowedEntriesEqual(a.ptyIdByNumericPaneId, b.ptyIdByNumericPaneId) &&
|
||||
// Why: serialization allocates a fresh tree per capture, so it compares by content.
|
||||
jsonContentEquals(a.liveLayoutRoot, b.liveLayoutRoot)
|
||||
)
|
||||
}
|
||||
|
||||
function mountedSurfaceCapturesEqual(
|
||||
a: ReadonlyMap<string, MountedTerminalSurfaceCapture>,
|
||||
b: ReadonlyMap<string, MountedTerminalSurfaceCapture>
|
||||
): boolean {
|
||||
if (a === b) {
|
||||
return true
|
||||
}
|
||||
if (a.size !== b.size) {
|
||||
return false
|
||||
}
|
||||
for (const [tabId, capture] of a) {
|
||||
const other = b.get(tabId)
|
||||
if (!other || !mountedTerminalSurfaceCaptureEquals(capture, other)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function narrowedEntriesEqual<K, T>(a: ReadonlyMap<K, T>, b: ReadonlyMap<K, T>): boolean {
|
||||
if (a === b) {
|
||||
return true
|
||||
}
|
||||
@@ -1056,9 +1160,6 @@ function canReuseMobileSessionSnapshot(
|
||||
next: MobileSessionWorktreeInputs
|
||||
): boolean {
|
||||
return (
|
||||
// Why: live DOM/PaneManager state is invisible to store references, so a mounted surface always rebuilds.
|
||||
!previous.hasMountedTerminalSurface &&
|
||||
!next.hasMountedTerminalSurface &&
|
||||
previous.worktreeId === next.worktreeId &&
|
||||
previous.terminalTabs === next.terminalTabs &&
|
||||
previous.browserWorkspaces === next.browserWorkspaces &&
|
||||
@@ -1084,6 +1185,12 @@ function canReuseMobileSessionSnapshot(
|
||||
narrowedEntriesEqual(
|
||||
previous.certificateFailureByBrowserPageId,
|
||||
next.certificateFailureByBrowserPageId
|
||||
) &&
|
||||
// Why: live DOM/PaneManager state is invisible to store references; the
|
||||
// capture makes it comparable instead of forcing mounted worktrees to rebuild.
|
||||
mountedSurfaceCapturesEqual(
|
||||
previous.mountedSurfaceCaptureByTabId,
|
||||
next.mountedSurfaceCaptureByTabId
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1683,12 +1790,10 @@ function resolveMobileTerminalTheme(
|
||||
}
|
||||
|
||||
function getRuntimeLeafIdsForTerminal(
|
||||
tabId: string,
|
||||
capture: MountedTerminalSurfaceCapture | undefined,
|
||||
savedLayout: AppState['terminalLayoutsByTabId'][string] | undefined
|
||||
): string[] {
|
||||
const registered = registeredTabs.get(tabId)
|
||||
const manager = registered?.getManager()
|
||||
const liveLeafIds = manager?.getPanes().map((pane) => pane.leafId) ?? []
|
||||
): readonly string[] {
|
||||
const liveLeafIds = capture?.paneLeafIds ?? []
|
||||
if (liveLeafIds.length > 0) {
|
||||
return liveLeafIds
|
||||
}
|
||||
@@ -1707,18 +1812,15 @@ function buildMobileTerminalSurfaceTabs(
|
||||
terminal: NonNullable<AppState['tabsByWorktree'][string]>[number],
|
||||
unifiedTabId?: string
|
||||
): RuntimeMobileSessionSnapshotTab[] {
|
||||
const registered = registeredTabs.get(terminal.id)
|
||||
const capture = inputs.mountedSurfaceCaptureByTabId.get(terminal.id)
|
||||
const isDesktopTabActive = unifiedTabId
|
||||
? isUnifiedTabActiveInActiveGroup(inputs, unifiedTabId)
|
||||
: inputs.activeTerminalTabId === terminal.id
|
||||
const manager = registered?.getManager()
|
||||
const liveActivePaneId = manager?.getActivePane()?.id ?? null
|
||||
const savedLayout = inputs.terminalLayoutByTabId.get(terminal.id)
|
||||
const leafIds = getRuntimeLeafIdsForTerminal(terminal.id, savedLayout)
|
||||
const activeLeafId =
|
||||
liveActivePaneId !== null
|
||||
? (manager?.getLeafId(liveActivePaneId) ?? null)
|
||||
: (savedLayout?.activeLeafId ?? leafIds[0] ?? null)
|
||||
const leafIds = getRuntimeLeafIdsForTerminal(capture, savedLayout)
|
||||
const activeLeafId = capture?.hasLiveActivePane
|
||||
? capture.liveActiveLeafId
|
||||
: (savedLayout?.activeLeafId ?? leafIds[0] ?? null)
|
||||
const paneTitles = inputs.paneTitlesByTabId.get(terminal.id) ?? {}
|
||||
const generatedTitlesEnabled = inputs.generatedTitlesEnabled
|
||||
const sanitizedSavedLayout = savedLayout
|
||||
@@ -1736,11 +1838,7 @@ function buildMobileTerminalSurfaceTabs(
|
||||
? seededLaunchDraft
|
||||
: null
|
||||
const publishedLaunchDraft = launchDraftEntry?.text.trim() ? launchDraftEntry : null
|
||||
const container = registered?.getContainer()
|
||||
const firstChild = container?.firstElementChild
|
||||
const liveLayoutRoot = serializePaneTree(
|
||||
typeof HTMLElement !== 'undefined' && firstChild instanceof HTMLElement ? firstChild : null
|
||||
)
|
||||
const liveLayoutRoot = capture?.liveLayoutRoot ?? null
|
||||
const parentLayout = normalizeTerminalLayoutSnapshot({
|
||||
// Why: live DOM tree is authoritative when mounted, else the saved tree; synthesize only as a last resort, never re-guess.
|
||||
root: resolveTerminalLayoutRoot({
|
||||
@@ -1760,11 +1858,11 @@ function buildMobileTerminalSurfaceTabs(
|
||||
: {})
|
||||
} satisfies TerminalLayoutSnapshot).snapshot
|
||||
return leafIds.map((leafId) => {
|
||||
const numericPaneId = manager?.getNumericIdForLeaf(leafId) ?? null
|
||||
const numericPaneId = capture?.numericPaneIdByLeafId.get(leafId) ?? null
|
||||
const ptyId =
|
||||
numericPaneId === null
|
||||
? (savedPtyIdsByLeafId[leafId] ?? (leafIds.length === 1 ? terminal.ptyId : null))
|
||||
: (registered?.getPtyIdForPane(numericPaneId) ?? savedPtyIdsByLeafId[leafId] ?? null)
|
||||
: (capture?.ptyIdByNumericPaneId.get(numericPaneId) ?? savedPtyIdsByLeafId[leafId] ?? null)
|
||||
const legacyPaneId = numericPaneId === null ? /^pane:(\d+)$/.exec(leafId)?.[1] : null
|
||||
const paneTitle =
|
||||
numericPaneId !== null
|
||||
|
||||
Reference in New Issue
Block a user