mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 08:03:33 +00:00
perf(terminal): fast-paint parked paired sessions
This commit is contained in:
@@ -8527,6 +8527,37 @@ describe('connectPanePty', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('resets cached parked terminal modes before authoritative snapshot replay', async () => {
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
enableActiveRuntimeEnvironment()
|
||||
const remotePtyId = 'remote:env-1@@terminal-1'
|
||||
const transport = createMockTransport(remotePtyId)
|
||||
const replayCallback: { current: ((data: string) => void) | null } = { current: null }
|
||||
transport.connect.mockImplementation(async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
|
||||
replayCallback.current = callbacks.onReplayData ?? null
|
||||
return { id: remotePtyId, replay: '' }
|
||||
})
|
||||
transportFactoryQueue.push(transport)
|
||||
|
||||
const pane = createPane(1)
|
||||
pane.container.dataset.parkedViewportFrame = 'true'
|
||||
pane.terminal.write = vi.fn((_data: string, callback?: () => void) => {
|
||||
callback?.()
|
||||
})
|
||||
|
||||
const binding = connectPanePty(pane as never, createManager(1) as never, createDeps() as never)
|
||||
await flushAsyncTicks(6)
|
||||
replayCallback.current?.('authoritative snapshot')
|
||||
await flushAsyncTicks(12)
|
||||
|
||||
const writes = pane.terminal.write.mock.calls.map(([data]) => data)
|
||||
expect(writes.indexOf('\x1bc')).toBeGreaterThanOrEqual(0)
|
||||
expect(writes.indexOf('\x1bc')).toBeLessThan(writes.indexOf('authoritative snapshot'))
|
||||
expect(writes).not.toContain('\x1b[2J\x1b[3J\x1b[H')
|
||||
expect(pane.container.dataset.parkedViewportFrame).toBeUndefined()
|
||||
binding.dispose()
|
||||
})
|
||||
|
||||
it('resizes the pane to the snapshot grid before replaying daemon snapshot bytes (bug #7279)', async () => {
|
||||
// Why: the daemon serializes soft-wrapped lines flat, so reattach must resize xterm to the snapshot grid before writing, or rows rewrap wrong.
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
|
||||
@@ -272,6 +272,7 @@ import {
|
||||
} from './command-code-done-settle'
|
||||
import { canCommandCodeOutputOwnPane } from './command-code-output-ownership'
|
||||
import { isTerminalTabParked } from './terminal-parked-watcher-registry'
|
||||
import { consumeParkedTerminalViewportFrameMarker } from './terminal-parked-viewport-frame'
|
||||
import {
|
||||
getExecutionHostIdForWorktree,
|
||||
getSettingsForWorktreeRuntimeOwner
|
||||
@@ -5692,7 +5693,9 @@ export function connectPanePty(
|
||||
// xterm. Local eager replay decides this earlier so metadata-only frames
|
||||
// can keep restored scrollback while still using the replay guard.
|
||||
if (clearBeforeReplay) {
|
||||
await writeReplayDataAsync('\x1b[2J\x1b[3J\x1b[H')
|
||||
await writeReplayDataAsync(
|
||||
consumeParkedTerminalViewportFrameMarker(pane) ? '\x1bc' : '\x1b[2J\x1b[3J\x1b[H'
|
||||
)
|
||||
if (!isCurrentPayload()) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -24,15 +24,20 @@ import {
|
||||
import { collapseParkedExitedLeaf, startParkedPtyWatcher } from './terminal-parked-pty-watcher'
|
||||
import {
|
||||
capturedPanesByTabId,
|
||||
consumeParkedTerminalViewportFrames,
|
||||
disposeParkedTabWatchers,
|
||||
parkedWatchersByTabId,
|
||||
type ParkedTabWatcherEntry,
|
||||
type ParkedTerminalPaneCapture
|
||||
} from './terminal-parked-watcher-registry'
|
||||
import type { PaneManager } from '@/lib/pane-manager/pane-manager'
|
||||
import type { ReplayingPanesRef } from './replay-guard'
|
||||
import { replayParkedTerminalViewportFrames } from './terminal-parked-viewport-frame'
|
||||
|
||||
// Why: re-export so callers keep one import surface; the registry split only breaks the store-slice import cycle.
|
||||
export {
|
||||
captureParkedTerminalPaneCandidates,
|
||||
consumeParkedTerminalViewportFrames,
|
||||
disposeAllParkedTerminalWatchers,
|
||||
disposeRemovedWorktreeParkedTerminalWatchers,
|
||||
disposeParkedTerminalWatchersForPtyIds,
|
||||
@@ -49,6 +54,29 @@ export {
|
||||
export type { ParkableTerminalTabModel } from './terminal-parked-watcher-reconciliation'
|
||||
export type ParkedTerminalPtyEligibility = (ptyId: string) => boolean
|
||||
|
||||
export function replayParkedTerminalViewportForTab(args: {
|
||||
manager: PaneManager
|
||||
paneByLeafId: ReadonlyMap<string, number>
|
||||
replayingPanesRef: ReplayingPanesRef
|
||||
tabId: string
|
||||
worktreeId: string
|
||||
}): number {
|
||||
const state = useAppStore.getState()
|
||||
const tab = state.tabsByWorktree[args.worktreeId]?.find(
|
||||
(candidate) => candidate.id === args.tabId
|
||||
)
|
||||
return replayParkedTerminalViewportFrames({
|
||||
manager: args.manager,
|
||||
frames: consumeParkedTerminalViewportFrames(
|
||||
args.tabId,
|
||||
state.terminalLayoutsByTabId[args.tabId]?.ptyIdsByLeafId ?? {},
|
||||
tab?.ptyId ?? null
|
||||
),
|
||||
paneByLeafId: args.paneByLeafId,
|
||||
replayingPanesRef: args.replayingPanesRef
|
||||
})
|
||||
}
|
||||
|
||||
const allowOrdinaryParkRestore = (ptyId: string): boolean =>
|
||||
isRemoteRuntimePtyId(ptyId) ||
|
||||
parseAppSshPtyId(ptyId) !== null ||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { ManagedPane, PaneManager } from '@/lib/pane-manager/pane-manager'
|
||||
import type { PtyTransport } from './pty-transport'
|
||||
import {
|
||||
captureParkedTerminalPanes,
|
||||
captureParkedTerminalViewportFrame,
|
||||
consumeParkedTerminalViewportFrameMarker,
|
||||
replayParkedTerminalViewportFrames
|
||||
} from './terminal-parked-viewport-frame'
|
||||
|
||||
const mocks = vi.hoisted(() => ({ replayIntoTerminal: vi.fn() }))
|
||||
|
||||
vi.mock('./replay-guard', () => ({ replayIntoTerminal: mocks.replayIntoTerminal }))
|
||||
|
||||
function makePane(
|
||||
id = 1,
|
||||
leafId = 'leaf-1'
|
||||
): {
|
||||
pane: ManagedPane
|
||||
serialize: ReturnType<typeof vi.fn>
|
||||
} {
|
||||
const serialize = vi.fn(() => 'cached viewport')
|
||||
return {
|
||||
pane: {
|
||||
id,
|
||||
leafId,
|
||||
terminal: {
|
||||
cols: 120,
|
||||
rows: 40,
|
||||
buffer: { active: { cursorX: 4, cursorY: 7 } }
|
||||
},
|
||||
container: { dataset: {} },
|
||||
serializeAddon: { serialize }
|
||||
} as unknown as ManagedPane,
|
||||
serialize
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.replayIntoTerminal.mockClear()
|
||||
})
|
||||
|
||||
describe('parked terminal viewport frames', () => {
|
||||
it('captures only the active viewport with an absolute cursor restore', () => {
|
||||
const { pane, serialize } = makePane()
|
||||
|
||||
const frame = captureParkedTerminalViewportFrame(pane)
|
||||
|
||||
expect(serialize).toHaveBeenCalledWith({ scrollback: 0 })
|
||||
expect(frame).toEqual({ data: 'cached viewport\x1b[8;5H', cols: 120, rows: 40 })
|
||||
})
|
||||
|
||||
it('captures viewport frames only for paired runtime panes', () => {
|
||||
const remote = makePane(1, 'remote-leaf')
|
||||
const local = makePane(2, 'local-leaf')
|
||||
const manager = {
|
||||
getActivePane: () => remote.pane,
|
||||
getPanes: () => [remote.pane, local.pane]
|
||||
} as unknown as PaneManager
|
||||
const transports = new Map<number, PtyTransport>([
|
||||
[1, { getPtyId: () => 'remote:windows-low-spec@@terminal-1' } as PtyTransport],
|
||||
[2, { getPtyId: () => 'local-pty' } as PtyTransport]
|
||||
])
|
||||
|
||||
const captures = captureParkedTerminalPanes(manager, transports)
|
||||
|
||||
expect(captures[0].viewportFrame?.data).toContain('cached viewport')
|
||||
expect(captures[1]).not.toHaveProperty('viewportFrame')
|
||||
expect(local.serialize).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('replays a matching-grid frame and skips a stale geometry', () => {
|
||||
const matching = makePane(1, 'matching')
|
||||
const stale = makePane(2, 'stale')
|
||||
const manager = {
|
||||
getPanes: () => [matching.pane, stale.pane],
|
||||
hasWebglRenderer: () => false
|
||||
} as unknown as PaneManager
|
||||
const replayingPanesRef = { current: new Map<number, number>() }
|
||||
|
||||
const replayed = replayParkedTerminalViewportFrames({
|
||||
manager,
|
||||
frames: [
|
||||
{ leafId: 'matching', frame: { data: 'matching frame', cols: 120, rows: 40 } },
|
||||
{ leafId: 'stale', frame: { data: 'stale frame', cols: 80, rows: 24 } }
|
||||
],
|
||||
paneByLeafId: new Map([
|
||||
['matching', 1],
|
||||
['stale', 2]
|
||||
]),
|
||||
replayingPanesRef
|
||||
})
|
||||
|
||||
expect(replayed).toBe(1)
|
||||
expect(mocks.replayIntoTerminal).toHaveBeenCalledOnce()
|
||||
expect(mocks.replayIntoTerminal).toHaveBeenCalledWith(
|
||||
matching.pane,
|
||||
replayingPanesRef,
|
||||
'matching frame',
|
||||
expect.any(Object)
|
||||
)
|
||||
expect(consumeParkedTerminalViewportFrameMarker(matching.pane)).toBe(true)
|
||||
expect(consumeParkedTerminalViewportFrameMarker(matching.pane)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,95 @@
|
||||
import type { ManagedPane, PaneManager } from '@/lib/pane-manager/pane-manager'
|
||||
import type { PtyTransport } from './pty-transport'
|
||||
import { isRemoteRuntimePtyId } from '@/runtime/runtime-terminal-inspection'
|
||||
import { serializeWithAbsoluteCursor } from '../../../../shared/terminal-serialize-absolute-cursor'
|
||||
import { replayIntoTerminal, type ReplayingPanesRef } from './replay-guard'
|
||||
|
||||
const MAX_PARKED_VIEWPORT_FRAME_CHARS = 256 * 1024
|
||||
const PARKED_VIEWPORT_FRAME_DATASET_KEY = 'parkedViewportFrame'
|
||||
|
||||
export type ParkedTerminalViewportFrame = {
|
||||
data: string
|
||||
cols: number
|
||||
rows: number
|
||||
}
|
||||
|
||||
export type ParkedTerminalViewportFrameByLeaf = {
|
||||
leafId: string
|
||||
frame: ParkedTerminalViewportFrame
|
||||
}
|
||||
|
||||
export function captureParkedTerminalPanes(
|
||||
manager: PaneManager,
|
||||
paneTransports: ReadonlyMap<number, PtyTransport>
|
||||
): {
|
||||
ptyId: string | null
|
||||
paneId: number
|
||||
leafId: string
|
||||
drivesTabTitle: boolean
|
||||
viewportFrame?: ParkedTerminalViewportFrame
|
||||
}[] {
|
||||
const activePaneId = manager.getActivePane()?.id
|
||||
return manager.getPanes().map((pane) => {
|
||||
const ptyId = paneTransports.get(pane.id)?.getPtyId() ?? null
|
||||
const viewportFrame =
|
||||
ptyId && isRemoteRuntimePtyId(ptyId) ? captureParkedTerminalViewportFrame(pane) : null
|
||||
return {
|
||||
ptyId,
|
||||
paneId: pane.id,
|
||||
leafId: pane.leafId,
|
||||
drivesTabTitle: activePaneId === pane.id,
|
||||
...(viewportFrame ? { viewportFrame } : {})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function captureParkedTerminalViewportFrame(
|
||||
pane: ManagedPane
|
||||
): ParkedTerminalViewportFrame | null {
|
||||
try {
|
||||
if (pane.terminal.cols < 1 || pane.terminal.rows < 1) {
|
||||
return null
|
||||
}
|
||||
const data = serializeWithAbsoluteCursor(pane.serializeAddon, pane.terminal, {
|
||||
scrollback: 0
|
||||
})
|
||||
if (!data || data.length > MAX_PARKED_VIEWPORT_FRAME_CHARS) {
|
||||
return null
|
||||
}
|
||||
return { data, cols: pane.terminal.cols, rows: pane.terminal.rows }
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function replayParkedTerminalViewportFrames(args: {
|
||||
manager: PaneManager
|
||||
frames: readonly ParkedTerminalViewportFrameByLeaf[]
|
||||
paneByLeafId: ReadonlyMap<string, number>
|
||||
replayingPanesRef: ReplayingPanesRef
|
||||
}): number {
|
||||
let replayed = 0
|
||||
for (const { leafId, frame } of args.frames) {
|
||||
const paneId = args.paneByLeafId.get(leafId)
|
||||
const pane = args.manager.getPanes().find((candidate) => candidate.id === paneId)
|
||||
if (!pane || pane.terminal.cols !== frame.cols || pane.terminal.rows !== frame.rows) {
|
||||
continue
|
||||
}
|
||||
replayIntoTerminal(pane, args.replayingPanesRef, frame.data, {
|
||||
shouldRefreshViewportSynchronously: () => !args.manager.hasWebglRenderer(pane.id)
|
||||
})
|
||||
pane.container.dataset[PARKED_VIEWPORT_FRAME_DATASET_KEY] = 'true'
|
||||
replayed += 1
|
||||
}
|
||||
return replayed
|
||||
}
|
||||
|
||||
export function consumeParkedTerminalViewportFrameMarker(
|
||||
pane: Pick<ManagedPane, 'container'>
|
||||
): boolean {
|
||||
if (pane.container.dataset[PARKED_VIEWPORT_FRAME_DATASET_KEY] !== 'true') {
|
||||
return false
|
||||
}
|
||||
delete pane.container.dataset[PARKED_VIEWPORT_FRAME_DATASET_KEY]
|
||||
return true
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from './pty-pre-handler-buffer'
|
||||
import {
|
||||
capturedPanesByTabId,
|
||||
consumeParkedTerminalViewportFrames,
|
||||
parkedWatchersByTabId,
|
||||
pruneParkedTerminalWatchers,
|
||||
terminalWatcherLiveWorkspaceIds
|
||||
@@ -90,4 +91,58 @@ describe('terminal parked watcher registry removal', () => {
|
||||
expect(capturedPanesByTabId.has(FLOATING_TAB_ID)).toBe(true)
|
||||
expect(floatingDispose).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('consumes a matching parked viewport frame exactly once', () => {
|
||||
const frame = { data: 'cached viewport', cols: 120, rows: 40 }
|
||||
capturedPanesByTabId.set(TAB_ID, {
|
||||
worktreeId: 'removed-worktree',
|
||||
panes: [
|
||||
{
|
||||
ptyId: PTY_ID,
|
||||
paneId: 1,
|
||||
leafId: 'leaf-1',
|
||||
drivesTabTitle: true,
|
||||
viewportFrame: frame
|
||||
}
|
||||
]
|
||||
})
|
||||
parkedWatchersByTabId.set(TAB_ID, {
|
||||
worktreeId: 'removed-worktree',
|
||||
tabPtyId: PTY_ID,
|
||||
paneIdByPtyId: new Map([[PTY_ID, 1]]),
|
||||
disposersByPtyId: new Map([[PTY_ID, vi.fn()]])
|
||||
})
|
||||
|
||||
expect(consumeParkedTerminalViewportFrames(TAB_ID, { 'leaf-1': PTY_ID }, null)).toEqual([
|
||||
{ leafId: 'leaf-1', frame }
|
||||
])
|
||||
expect(consumeParkedTerminalViewportFrames(TAB_ID, { 'leaf-1': PTY_ID }, null)).toEqual([])
|
||||
expect(capturedPanesByTabId.get(TAB_ID)?.panes[0]).not.toHaveProperty('viewportFrame')
|
||||
})
|
||||
|
||||
it('drops a cached viewport when the parked PTY identity changed', () => {
|
||||
capturedPanesByTabId.set(TAB_ID, {
|
||||
worktreeId: 'removed-worktree',
|
||||
panes: [
|
||||
{
|
||||
ptyId: PTY_ID,
|
||||
paneId: 1,
|
||||
leafId: 'leaf-1',
|
||||
drivesTabTitle: true,
|
||||
viewportFrame: { data: 'stale viewport', cols: 80, rows: 24 }
|
||||
}
|
||||
]
|
||||
})
|
||||
parkedWatchersByTabId.set(TAB_ID, {
|
||||
worktreeId: 'removed-worktree',
|
||||
tabPtyId: 'replacement-pty',
|
||||
paneIdByPtyId: new Map([['replacement-pty', 1]]),
|
||||
disposersByPtyId: new Map([['replacement-pty', vi.fn()]])
|
||||
})
|
||||
|
||||
expect(
|
||||
consumeParkedTerminalViewportFrames(TAB_ID, { 'leaf-1': 'replacement-pty' }, null)
|
||||
).toEqual([])
|
||||
expect(capturedPanesByTabId.get(TAB_ID)?.panes[0]).not.toHaveProperty('viewportFrame')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
*/
|
||||
import { discardPreHandlerPtyState } from './pty-pre-handler-buffer'
|
||||
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants'
|
||||
import type {
|
||||
ParkedTerminalViewportFrame,
|
||||
ParkedTerminalViewportFrameByLeaf
|
||||
} from './terminal-parked-viewport-frame'
|
||||
|
||||
export type ParkedTerminalPaneCapture = {
|
||||
ptyId: string | null
|
||||
@@ -17,6 +21,7 @@ export type ParkedTerminalPaneCapture = {
|
||||
/** Stable terminal-layout leaf UUID (paneKey attribution). */
|
||||
leafId: string
|
||||
drivesTabTitle: boolean
|
||||
viewportFrame?: ParkedTerminalViewportFrame
|
||||
}
|
||||
|
||||
export type CapturedTabPanes = { worktreeId: string; panes: ParkedTerminalPaneCapture[] }
|
||||
@@ -67,6 +72,32 @@ export function isTerminalTabParked(tabId: string): boolean {
|
||||
return (parkedWatchersByTabId.get(tabId)?.disposersByPtyId.size ?? 0) > 0
|
||||
}
|
||||
|
||||
export function consumeParkedTerminalViewportFrames(
|
||||
tabId: string,
|
||||
expectedPtyIdsByLeafId: Readonly<Record<string, string>>,
|
||||
fallbackPtyId: string | null
|
||||
): ParkedTerminalViewportFrameByLeaf[] {
|
||||
const capture = capturedPanesByTabId.get(tabId)
|
||||
if (!capture) {
|
||||
return []
|
||||
}
|
||||
const useFallback = capture.panes.length === 1
|
||||
const frames = isTerminalTabParked(tabId)
|
||||
? capture.panes.flatMap((pane) => {
|
||||
const expectedPtyId =
|
||||
expectedPtyIdsByLeafId[pane.leafId] ?? (useFallback ? fallbackPtyId : null)
|
||||
return pane.viewportFrame && expectedPtyId && pane.ptyId === expectedPtyId
|
||||
? [{ leafId: pane.leafId, frame: pane.viewportFrame }]
|
||||
: []
|
||||
})
|
||||
: []
|
||||
capturedPanesByTabId.set(tabId, {
|
||||
...capture,
|
||||
panes: capture.panes.map(({ viewportFrame: _viewportFrame, ...pane }) => pane)
|
||||
})
|
||||
return frames
|
||||
}
|
||||
|
||||
export function disposeParkedTabWatchers(tabId: string): void {
|
||||
const entry = parkedWatchersByTabId.get(tabId)
|
||||
if (!entry) {
|
||||
|
||||
@@ -122,7 +122,11 @@ import { fitAndFocusPanes, fitPanes } from './pane-helpers'
|
||||
import { markTerminalPinnedViewport } from '@/lib/pane-manager/terminal-scroll-intent'
|
||||
import { syncTerminalScrollIntentSoon } from '@/lib/pane-manager/terminal-scroll-intent-settle'
|
||||
import { registerRuntimeTerminalTab, scheduleRuntimeGraphSync } from '@/runtime/sync-runtime-graph'
|
||||
import { captureParkedTerminalPaneCandidates } from './terminal-parked-tab-watchers'
|
||||
import {
|
||||
captureParkedTerminalPaneCandidates,
|
||||
replayParkedTerminalViewportForTab
|
||||
} from './terminal-parked-tab-watchers'
|
||||
import { captureParkedTerminalPanes } from './terminal-parked-viewport-frame'
|
||||
import { e2eConfig } from '@/lib/e2e-config'
|
||||
import {
|
||||
PRIMARY_SELECTION_MAX_LENGTH,
|
||||
@@ -1500,6 +1504,13 @@ export function useTerminalPaneLifecycle({
|
||||
replayingPanesRef,
|
||||
restoredViewportBlankingPanesRef
|
||||
)
|
||||
replayParkedTerminalViewportForTab({
|
||||
manager,
|
||||
paneByLeafId: restoredPaneByLeafId,
|
||||
replayingPanesRef,
|
||||
tabId,
|
||||
worktreeId
|
||||
})
|
||||
const hasScrollbackRefs = Boolean(initialLayoutRef.current.scrollbackRefsByLeafId)
|
||||
if (
|
||||
restoredBuffers &&
|
||||
@@ -1745,12 +1756,7 @@ export function useTerminalPaneLifecycle({
|
||||
captureParkedTerminalPaneCandidates(
|
||||
tabId,
|
||||
worktreeId,
|
||||
manager.getPanes().map((capturedPane) => ({
|
||||
ptyId: paneTransports.get(capturedPane.id)?.getPtyId() ?? null,
|
||||
paneId: capturedPane.id,
|
||||
leafId: capturedPane.leafId,
|
||||
drivesTabTitle: manager.getActivePane()?.id === capturedPane.id
|
||||
}))
|
||||
captureParkedTerminalPanes(manager, paneTransports)
|
||||
)
|
||||
for (const transport of paneTransports.values()) {
|
||||
const ptyId = transport.getPtyId()
|
||||
|
||||
@@ -482,3 +482,76 @@ test('paired client keeps revealed remote terminals interactive', async ({
|
||||
await client.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
test('paired client fast-paints a parked terminal before the runtime responds', async ({
|
||||
orcaPage
|
||||
}, testInfo) => {
|
||||
test.setTimeout(180_000)
|
||||
const offer = await createRuntimeDesktopPairingOffer(orcaPage)
|
||||
const previousParkDelay = process.env.ORCA_E2E_TERMINAL_PARKING_DELAY_MS
|
||||
process.env.ORCA_E2E_TERMINAL_PARKING_DELAY_MS = String(PARK_DELAY_MS)
|
||||
const client = await launchPairedElectronClient(offer, testInfo, 'parked-fast-paint')
|
||||
const createdTerminals: string[] = []
|
||||
try {
|
||||
const worktreeId = await orcaPage.evaluate(() => {
|
||||
const id = window.__store?.getState().activeWorktreeId
|
||||
if (!id) {
|
||||
throw new Error('headed host has no active worktree')
|
||||
}
|
||||
return id
|
||||
})
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
client.page.evaluate(
|
||||
(id) =>
|
||||
window.__store
|
||||
?.getState()
|
||||
.allWorktrees()
|
||||
.some((worktree) => worktree.id === id) ?? false,
|
||||
worktreeId
|
||||
),
|
||||
{ timeout: 60_000, message: 'paired client never saw the host worktree' }
|
||||
)
|
||||
.toBe(true)
|
||||
await client.page.evaluate((id) => {
|
||||
const state = window.__store?.getState()
|
||||
state?.setActiveView('terminal')
|
||||
state?.setActiveWorktree(id)
|
||||
}, worktreeId)
|
||||
|
||||
const { target, decoys } = await seedScenario(client, worktreeId)
|
||||
createdTerminals.push(target.terminal, ...decoys.map((decoy) => decoy.terminal))
|
||||
await openClientTab(client.page, worktreeId, decoys[0].webTabId)
|
||||
await openClientTab(client.page, worktreeId, decoys[1].webTabId)
|
||||
await waitForTabParked(client.page, target.webTabId, { parkDelayMs: PARK_DELAY_MS })
|
||||
await client.page.evaluate(async (selector) => {
|
||||
await window.api.runtimeEnvironments.disconnect({ selector })
|
||||
}, client.environmentId)
|
||||
|
||||
const startedAt = Date.now()
|
||||
await openClientTab(client.page, worktreeId, target.webTabId)
|
||||
expect(
|
||||
await waitForPaneMarker(client.page, target.webTabId, 'READY:', 2_000),
|
||||
'cold-parked viewport stayed blank while the paired runtime was disconnected'
|
||||
).toBe(true)
|
||||
console.log(`[paired-reveal] cached viewport painted in ${Date.now() - startedAt}ms`)
|
||||
} finally {
|
||||
await client.page
|
||||
.evaluate(async (selector) => {
|
||||
await window.api.runtimeEnvironments.connect({ selector })
|
||||
}, client.environmentId)
|
||||
.catch(() => undefined)
|
||||
for (const terminal of createdTerminals) {
|
||||
await callEnvironment(client.page, client.environmentId, 'terminal.closeTab', {
|
||||
terminal
|
||||
}).catch(() => undefined)
|
||||
}
|
||||
if (previousParkDelay === undefined) {
|
||||
delete process.env.ORCA_E2E_TERMINAL_PARKING_DELAY_MS
|
||||
} else {
|
||||
process.env.ORCA_E2E_TERMINAL_PARKING_DELAY_MS = previousParkDelay
|
||||
}
|
||||
await client.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user