fix(terminal): unblock collapsed hidden restores

This commit is contained in:
Neil
2026-07-29 06:19:28 -07:00
parent 739ed32a71
commit d7c9b5d5aa
12 changed files with 323 additions and 93 deletions
@@ -44,6 +44,30 @@ describe('hidden output restore scheduler', () => {
expect(secondRestore).toHaveBeenCalledTimes(1)
})
it('promotes queued work whose priority becomes active before the drain', async () => {
let isActive = false
let settleActive: (() => void) | undefined
const inactiveRestore = vi.fn()
const promotedRestore = vi.fn(
() =>
new Promise<void>((resolve) => {
settleActive = resolve
})
)
scheduleHiddenOutputRestore({}, inactiveRestore, 'inactive')
scheduleHiddenOutputRestore({}, promotedRestore, () => (isActive ? 'active' : 'inactive'))
isActive = true
await vi.advanceTimersByTimeAsync(16)
expect(promotedRestore).toHaveBeenCalledTimes(1)
expect(inactiveRestore).not.toHaveBeenCalled()
settleActive?.()
await vi.advanceTimersByTimeAsync(16)
expect(inactiveRestore).toHaveBeenCalledTimes(1)
})
it('waits for an inactive restore to finish before starting the next', async () => {
let settleFirst: (() => void) | undefined
const firstRestore = vi.fn(
@@ -1,9 +1,12 @@
type HiddenOutputRestorePriority = 'active' | 'inactive'
type HiddenOutputRestorePriorityResolver = () => HiddenOutputRestorePriority
type HiddenOutputRestoreRequest = () => void | Promise<void>
type HiddenOutputRestoreEntry = {
requestRestore: HiddenOutputRestoreRequest
priority: HiddenOutputRestorePriority | HiddenOutputRestorePriorityResolver
}
type InactiveHiddenOutputRestore = {
@@ -95,31 +98,66 @@ function runInactiveRestore(target: object, requestRestore: HiddenOutputRestoreR
finishInactiveRestore(target, token)
}
function resolveRestorePriority(
priority: HiddenOutputRestorePriority | HiddenOutputRestorePriorityResolver
): HiddenOutputRestorePriority {
return typeof priority === 'function' ? priority() : priority
}
function takeNextScheduledRestore(): {
target: object
entry: HiddenOutputRestoreEntry
priority: HiddenOutputRestorePriority
} | null {
let fallback:
| {
target: object
entry: HiddenOutputRestoreEntry
priority: HiddenOutputRestorePriority
}
| undefined
for (const [target, entry] of inactiveRestoreQueue) {
const priority = resolveRestorePriority(entry.priority)
fallback ??= { target, entry, priority }
if (priority === 'active') {
inactiveRestoreQueue.delete(target)
return { target, entry, priority }
}
}
if (!fallback) {
return null
}
inactiveRestoreQueue.delete(fallback.target)
return fallback
}
function drainInactiveRestoreQueue(): void {
inactiveRestoreTimer = null
if (inactiveRestore !== null || activeRestoreTokensByTarget.size > 0) {
return
}
const next = inactiveRestoreQueue.entries().next()
if (next.done) {
const next = takeNextScheduledRestore()
if (!next) {
return
}
const [target, entry] = next.value
inactiveRestoreQueue.delete(target)
runInactiveRestore(target, entry.requestRestore)
if (next.priority === 'active') {
runActiveRestore(next.target, next.entry.requestRestore)
return
}
runInactiveRestore(next.target, next.entry.requestRestore)
}
export function scheduleHiddenOutputRestore(
target: object,
requestRestore: HiddenOutputRestoreRequest,
priority: HiddenOutputRestorePriority
priority: HiddenOutputRestorePriority | HiddenOutputRestorePriorityResolver
): void {
if (priority === 'active') {
if (resolveRestorePriority(priority) === 'active') {
cancelScheduledHiddenOutputRestore(target)
runActiveRestore(target, requestRestore)
return
}
inactiveRestoreQueue.set(target, { requestRestore })
inactiveRestoreQueue.set(target, { requestRestore, priority })
scheduleInactiveRestoreDrain()
}
@@ -11104,6 +11104,54 @@ describe('connectPanePty', () => {
)
})
it('releases post-snapshot live bytes when the active split stays layout-collapsed', async () => {
enableMainAuthority()
const deps = createDeps({ isVisibleRef: { current: false } })
const { pane, transport, dataCallback } = await connectHiddenPane(deps)
vi.useFakeTimers()
Object.defineProperties(pane.container, {
isConnected: { configurable: true, value: true },
offsetParent: { configurable: true, value: {} },
getBoundingClientRect: {
configurable: true,
value: () => ({ width: 0, height: 0 })
}
})
const getMainBufferSnapshot = window.api.pty.getMainBufferSnapshot as unknown as ReturnType<
typeof vi.fn
>
getMainBufferSnapshot.mockResolvedValue({
data: 'source-grid hidden snapshot\r\n',
cols: 80,
rows: 24,
seq: 64
})
pane.fitAddon.proposeDimensions = vi.fn(() => ({ cols: 2, rows: 1 })) as never
transport.resize.mockClear()
dataCallback('hidden output\r\n', { seq: 16, rawLength: 16 })
const { _dispatchPtyModelRestoreNeededForTest } = await import('./pty-model-restore-channel')
_dispatchPtyModelRestoreNeededForTest({ id: 'pty-id', reason: 'hidden-drop', markerSeq: 64 })
;(deps.isVisibleRef as { current: boolean }).current = true
const { requestTerminalBacklogRecovery } =
await import('@/lib/pane-manager/pane-terminal-output-scheduler')
requestTerminalBacklogRecovery(pane.terminal as never)
await flushAsyncTicks(20)
dataCallback('live-after-collapsed-restore', { seq: 92, rawLength: 28 })
expect(pane.terminal.write.mock.calls.map((call) => String(call[0])).join('')).not.toContain(
'live-after-collapsed-restore'
)
await vi.advanceTimersByTimeAsync(128)
await flushAsyncTicks(20)
expect(transport.resize).not.toHaveBeenCalled()
expect(pane.terminal.write.mock.calls.map((call) => String(call[0])).join('')).toContain(
'live-after-collapsed-restore'
)
})
it('kicks pane recovery when reveal finds the write pipeline certified dead', async () => {
// 2026-07-13 fossil-pane incident: bytes drop while hidden, pipeline certified dead, cert recovery empty — reveal must re-kick it.
enableMainAuthority()
@@ -13935,6 +13983,69 @@ describe('connectPanePty', () => {
}
})
it('promotes a deferred hidden restore when its pane becomes active before the drain', async () => {
const { resetHiddenOutputRestoreSchedulerForTests, scheduleHiddenOutputRestore } =
await import('./hidden-output-restore-scheduler')
let disposable: { dispose: () => void } | null = null
try {
const { connectPanePty } = await import('./pty-connection')
const transport = createMockTransport('pty-id')
const capturedDataCallback: {
current: ((data: string, meta?: { seq?: number; rawLength?: number }) => void) | null
} = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const getMainBufferSnapshot = window.api.pty.getMainBufferSnapshot as unknown as ReturnType<
typeof vi.fn
>
getMainBufferSnapshot.mockResolvedValue({
data: 'promoted snapshot\r\n',
cols: 100,
rows: 30,
seq: 64
})
const pane = createPane(1)
const manager = createManager(1, 1)
const deps = createDeps({
isActiveRef: { current: false },
isVisibleRef: { current: false }
})
disposable = connectPanePty(pane as never, manager as never, deps as never)
await flushAsyncTicks(6)
const blockingRestore = vi.fn(() => new Promise<void>(() => undefined))
scheduleHiddenOutputRestore({}, blockingRestore, 'inactive')
const hidden = 'x'.repeat(2 * 1024 * 1024 + 1)
const live = 'visible promoted output\r\n'
capturedDataCallback.current?.(hidden, { seq: hidden.length, rawLength: hidden.length })
;(deps.isVisibleRef as { current: boolean }).current = true
capturedDataCallback.current?.(live, {
seq: hidden.length + live.length,
rawLength: live.length
})
;(deps.isActiveRef as { current: boolean }).current = true
await new Promise((resolve) => setTimeout(resolve, 30))
await flushAsyncTicks(20)
expect(blockingRestore).not.toHaveBeenCalled()
expect(getMainBufferSnapshot).toHaveBeenCalledWith('pty-id', { scrollbackRows: 5000 })
expect(pane.terminal.write).toHaveBeenCalledWith(
expect.stringContaining('promoted snapshot'),
expect.any(Function)
)
} finally {
disposable?.dispose()
resetHiddenOutputRestoreSchedulerForTests()
}
})
it('drops a deferred inactive hidden restore when the pane is hidden again', async () => {
const { resetHiddenOutputRestoreSchedulerForTests } =
await import('./hidden-output-restore-scheduler')
@@ -504,10 +504,6 @@ type E2eTerminalPtyOutputDebugSnapshot = {
hiddenRendererSkipCount: number
hiddenRendererSkippedChars: number
hiddenRendererMode2031ReplyCount: number
activeHiddenRestoreCount: number
activeHiddenRestoreSnapshotMs: number
activeHiddenRestoreReplayMs: number
activeHiddenRestoreTotalMs: number
}
type E2eTerminalPtyOutputDebugApi = {
@@ -541,21 +537,13 @@ type ColdRestoreAgentResumeStartup = PendingStartupCommand & {
const e2eTerminalPtyOutputDebugState: E2eTerminalPtyOutputDebugSnapshot = {
hiddenRendererSkipCount: 0,
hiddenRendererSkippedChars: 0,
hiddenRendererMode2031ReplyCount: 0,
activeHiddenRestoreCount: 0,
activeHiddenRestoreSnapshotMs: 0,
activeHiddenRestoreReplayMs: 0,
activeHiddenRestoreTotalMs: 0
hiddenRendererMode2031ReplyCount: 0
}
function resetE2eTerminalPtyOutputDebug(): void {
e2eTerminalPtyOutputDebugState.hiddenRendererSkipCount = 0
e2eTerminalPtyOutputDebugState.hiddenRendererSkippedChars = 0
e2eTerminalPtyOutputDebugState.hiddenRendererMode2031ReplyCount = 0
e2eTerminalPtyOutputDebugState.activeHiddenRestoreCount = 0
e2eTerminalPtyOutputDebugState.activeHiddenRestoreSnapshotMs = 0
e2eTerminalPtyOutputDebugState.activeHiddenRestoreReplayMs = 0
e2eTerminalPtyOutputDebugState.activeHiddenRestoreTotalMs = 0
}
function exposeE2eTerminalPtyOutputDebug(): void {
@@ -586,22 +574,6 @@ function recordHiddenMode2031Reply(): void {
e2eTerminalPtyOutputDebugState.hiddenRendererMode2031ReplyCount += 1
}
function recordActiveHiddenRestoreTiming(
priority: 'active' | 'inactive' | undefined,
snapshotMs: number,
replayMs: number,
totalMs: number
): void {
if (!e2eConfig.exposeStore || priority !== 'active') {
return
}
exposeE2eTerminalPtyOutputDebug()
e2eTerminalPtyOutputDebugState.activeHiddenRestoreCount += 1
e2eTerminalPtyOutputDebugState.activeHiddenRestoreSnapshotMs = snapshotMs
e2eTerminalPtyOutputDebugState.activeHiddenRestoreReplayMs = replayMs
e2eTerminalPtyOutputDebugState.activeHiddenRestoreTotalMs = totalMs
}
function exposeE2eTerminalPtyDataInjection(): void {
if (!e2eConfig.exposeStore || typeof window === 'undefined') {
return
@@ -7014,10 +6986,7 @@ export function connectPanePty(
}
}
function requestHiddenOutputRestoreIfNeeded(opts?: {
bypassScheduler?: boolean
debugPriority?: 'active' | 'inactive'
}): boolean {
function requestHiddenOutputRestoreIfNeeded(opts?: { bypassScheduler?: boolean }): boolean {
// Why: once the write pipeline is probe-certified dead a restore can never parse; recovery owns the pane and the remount gets a fresh xterm + restore.
if (isTerminalWritePipelineCertifiedDead(pane.terminal)) {
// Why the re-kick: certification's recovery request can be budget-declined or cancelled by a sibling remount; without this, a revealed dead pane keeps the stale frame forever.
@@ -7048,7 +7017,9 @@ export function connectPanePty(
return true
}
if (!opts?.bypassScheduler) {
const priority = isActiveSplitPane() ? 'active' : 'inactive'
const resolvePriority = (): 'active' | 'inactive' =>
isActiveSplitPane() ? 'active' : 'inactive'
const priority = resolvePriority()
if (priority === 'inactive') {
if (!hiddenOutputRestoreScheduled) {
hiddenOutputRestoreScheduled = true
@@ -7071,12 +7042,11 @@ export function connectPanePty(
return
}
const restoreRequested = requestHiddenOutputRestoreIfNeeded({
bypassScheduler: true,
debugPriority: priority
bypassScheduler: true
})
return restoreRequested ? (hiddenOutputRestoreInFlight ?? undefined) : undefined
},
priority
resolvePriority
)
}
return true
@@ -7087,10 +7057,7 @@ export function connectPanePty(
scheduleHiddenOutputRestore(
pane.terminal,
() => {
restoreRequested = requestHiddenOutputRestoreIfNeeded({
bypassScheduler: true,
debugPriority: priority
})
restoreRequested = requestHiddenOutputRestoreIfNeeded({ bypassScheduler: true })
return hiddenOutputRestoreInFlight ?? undefined
},
priority
@@ -7099,9 +7066,6 @@ export function connectPanePty(
}
clearHiddenOutputRestoreDeferredRetryTimer()
hiddenOutputRestoreRetryDeferred = false
const restoreStartedAt = performance.now()
let restoreSnapshotMs = 0
let restoreReplayMs = 0
hiddenOutputRestoreInFlight = (async () => {
// Backstop (rc.7.perf loop): bound how many snapshot fetch+replay rounds one task burns before yielding to the live stream.
@@ -7128,15 +7092,12 @@ export function connectPanePty(
const restoreGeneration = hiddenOutputRestoreGeneration
hiddenOutputRestoreNeeded = false
let snapshot: PtyBufferSnapshot | null = null
const snapshotStartedAt = performance.now()
try {
snapshot = await serializeHiddenOutputSnapshot(currentPtyId, {
scrollbackRows: resolveHiddenRestoreScrollbackRows(pane.terminal.options.scrollback)
})
} catch {
snapshot = null
} finally {
restoreSnapshotMs += performance.now() - snapshotStartedAt
}
if (disposed) {
return
@@ -7160,12 +7121,7 @@ export function connectPanePty(
}
hiddenOutputRestoreDeferredRetryAttempts = 0
restoreIterations += 1
const replayStartedAt = performance.now()
try {
await applyMainBufferSnapshot(snapshot)
} finally {
restoreReplayMs += performance.now() - replayStartedAt
}
await applyMainBufferSnapshot(snapshot)
if (
disposed ||
hiddenOutputRestoreGeneration !== restoreGeneration ||
@@ -7217,12 +7173,6 @@ export function connectPanePty(
const hiddenOutputRestoreTask = hiddenOutputRestoreInFlight
let trackedHiddenOutputRestore: Promise<void>
trackedHiddenOutputRestore = hiddenOutputRestoreTask.finally(() => {
recordActiveHiddenRestoreTiming(
opts?.debugPriority,
restoreSnapshotMs,
restoreReplayMs,
performance.now() - restoreStartedAt
)
if (hiddenOutputRestoreInFlight === trackedHiddenOutputRestore) {
hiddenOutputRestoreInFlight = null
}
@@ -3,12 +3,14 @@ import { getLivePaneCensus } from './pane-manager-registry'
import type { ManagedPane } from './pane-manager-types'
const MAX_RETRY_FRAMES = 40
const COLLAPSED_LAYOUT_RETRY_FRAMES = 4
const LAYOUT_SETTLE_MS = 16
type RetrySchedule = { cancel: () => void }
type RetryState = {
attempts: number
collapsedLayoutAttempts: number
schedule: RetrySchedule | null
retry: () => boolean
onExhausted: () => void
@@ -16,6 +18,15 @@ type RetryState = {
const retryByPane = new WeakMap<ManagedPane, RetryState>()
function isConnectedCollapsedPane(pane: ManagedPane): boolean {
const container = pane.container
if (container.isConnected !== true || container.offsetParent == null) {
return false
}
const rect = container.getBoundingClientRect?.()
return Boolean(rect && (rect.width === 0 || rect.height === 0))
}
function scheduleRetryTick(run: () => void): RetrySchedule {
if (typeof requestAnimationFrame === 'function') {
let cancelled = false
@@ -59,6 +70,7 @@ export function armPaneFitContinuationRetry(
): void {
const state = retryByPane.get(pane) ?? {
attempts: 0,
collapsedLayoutAttempts: 0,
schedule: null,
...callbacks
}
@@ -75,6 +87,15 @@ export function armPaneFitContinuationRetry(
return
}
state.attempts += 1
state.collapsedLayoutAttempts = isConnectedCollapsedPane(pane)
? state.collapsedLayoutAttempts + 1
: 0
if (state.collapsedLayoutAttempts >= COLLAPSED_LAYOUT_RETRY_FRAMES) {
// Why: a connected 0×0 split has no destination grid; waiting the full reveal budget only stalls replay and live output.
clearPaneFitContinuationRetry(pane)
state.onExhausted()
return
}
if (state.attempts >= MAX_RETRY_FRAMES) {
// Why leafId + census: `pane.id` restarts at 1 per PaneManager and there
// is one manager per tab, so a burst of identical `paneId: 1` crumbs
@@ -22,6 +22,7 @@ function flushAnimationFrames(timestamp = 16): void {
type TestPane = ManagedPane & {
setRect: (rect: { width: number; height: number }) => void
setXtermRect: (rect: { width: number; height: number }) => void
setLayoutConnection: (connected: boolean, hasOffsetParent: boolean) => void
}
function createPane(options: {
@@ -31,6 +32,8 @@ function createPane(options: {
let rect = options.rect
// Why: the reveal gate measures the inner xterm host, which can differ from the outer .pane.
let xtermRect: { width: number; height: number } | null = null
let layoutConnected = false
let layoutHasOffsetParent = false
const leafId = '22222222-2222-4222-8222-222222222222'
const pane = {
id: 7,
@@ -39,6 +42,12 @@ function createPane(options: {
terminal: { cols: 80, rows: 24 },
container: {
dataset: {},
get isConnected() {
return layoutConnected
},
get offsetParent() {
return layoutHasOffsetParent ? {} : null
},
getBoundingClientRect: () => ({ width: rect.width, height: rect.height })
},
xtermContainer: {
@@ -59,6 +68,10 @@ function createPane(options: {
},
setXtermRect: (next: { width: number; height: number }) => {
xtermRect = next
},
setLayoutConnection: (connected: boolean, hasOffsetParent: boolean) => {
layoutConnected = connected
layoutHasOffsetParent = hasOffsetParent
}
}
return pane as unknown as TestPane
@@ -155,6 +168,65 @@ describe('safeFitAndThen unmeasurable-pane retry', () => {
await expect(handle.completion).resolves.toBe(true)
})
it('settles a persistently collapsed connected split without the hidden-pane retry delay', async () => {
const pane = createPane({ rect: { width: 0, height: 0 } })
const continuation = vi.fn()
pane.setLayoutConnection(true, true)
const handle = safeFitAndThen(pane, 'hidden-snapshot-pty-resize', continuation, {
retryIfUnmeasurable: true
})
for (let frame = 0; frame < 4; frame += 1) {
flushAnimationFrames(frame * 16)
vi.advanceTimersByTime(16)
}
expect(continuation).not.toHaveBeenCalled()
expect(recordRendererCrashBreadcrumb).not.toHaveBeenCalled()
await expect(handle.completion).resolves.toBe(false)
})
it('keeps retrying a connected pane hidden by an ancestor', async () => {
const pane = createPane({ rect: { width: 0, height: 0 } })
const continuation = vi.fn()
pane.setLayoutConnection(true, false)
const handle = safeFitAndThen(pane, 'hidden-snapshot-pty-resize', continuation, {
retryIfUnmeasurable: true
})
for (let frame = 0; frame < 4; frame += 1) {
flushAnimationFrames(frame * 16)
vi.advanceTimersByTime(16)
}
pane.setRect({ width: 800, height: 600 })
pane.setLayoutConnection(true, true)
flushAnimationFrames(64)
vi.advanceTimersByTime(16)
expect(continuation).toHaveBeenCalledTimes(1)
await expect(handle.completion).resolves.toBe(true)
})
it('still accepts a connected split that gains layout within the collapsed retry window', async () => {
const pane = createPane({ rect: { width: 0, height: 0 } })
const continuation = vi.fn()
pane.setLayoutConnection(true, true)
const handle = safeFitAndThen(pane, 'hidden-snapshot-pty-resize', continuation, {
retryIfUnmeasurable: true
})
for (let frame = 0; frame < 3; frame += 1) {
flushAnimationFrames(frame * 16)
vi.advanceTimersByTime(16)
}
pane.setRect({ width: 800, height: 600 })
flushAnimationFrames(64)
vi.advanceTimersByTime(16)
expect(continuation).toHaveBeenCalledTimes(1)
await expect(handle.completion).resolves.toBe(true)
})
it('resolves failure after the bounded frame budget instead of hanging reattach', async () => {
const pane = createPane({ rect: { width: 0, height: 0 } })
const continuation = vi.fn()
@@ -1332,14 +1332,14 @@ describe('pane terminal output scheduler', () => {
expect(terminal.write).not.toHaveBeenCalled()
// Why 12: promoted backlogs use the parse-clocked high-priority budget
// Why 8: promoted backlogs use the parse-clocked high-priority budget
// (HIGH_PRIORITY_MAX_WRITES_PER_DRAIN) so a visible flood drains at the
// parser's pace instead of a fixed 2-write drip.
vi.advanceTimersByTime(0)
expect(terminal.write).toHaveBeenCalledTimes(12)
expect(terminal.write).toHaveBeenCalledTimes(8)
vi.advanceTimersByTime(4)
expect(terminal.write).toHaveBeenCalledTimes(24)
expect(terminal.write).toHaveBeenCalledTimes(16)
})
it('yields high-priority backlog drains when writes spend the frame budget', async () => {
@@ -92,8 +92,8 @@ const BACKGROUND_DRAIN_INTERVAL_MS = 16
const HIGH_PRIORITY_DRAIN_INTERVAL_MS = 4
const BACKGROUND_CHUNK_CHARS = 16 * 1024
const MAX_WRITES_PER_DRAIN = 2
// Why 12: visible split-pane fan-in needs 192KB/tick; the 8ms budget still bounds slow parsers.
const HIGH_PRIORITY_MAX_WRITES_PER_DRAIN = 12
// Why 8: per-tick volume (8 x 16KB = 128KB ≈ 1.3ms parse) sets the sustained ceiling (~30MB/s) within DRAIN_TIME_BUDGET_MS; at 2 it was only 8MB/s against a ~100MB/s parser (see throughput bench).
const HIGH_PRIORITY_MAX_WRITES_PER_DRAIN = 8
const DRAIN_TIME_BUDGET_MS = 8
const LARGE_BACKLOG_CHARS = 512 * 1024
const SYNC_FOREGROUND_FLUSH_CHARS = 256 * 1024
@@ -0,0 +1,28 @@
import type { Page } from '@stablyai/playwright-test'
import { expect } from '@stablyai/playwright-test'
export async function waitForHiddenPressureCompletion(
orcaPage: Page,
hiddenPanes: { ptyId: string }[],
runId: string
): Promise<void> {
await expect
.poll(
() =>
orcaPage.evaluate(
async ({ panes, markerPrefix }) => {
const snapshots = await Promise.all(
panes.map((pane) =>
window.api.pty.getMainBufferSnapshot(pane.ptyId, { scrollbackRows: 50 })
)
)
return snapshots.filter((snapshot, index) =>
snapshot?.data.includes(`${markerPrefix}${index}`)
).length
},
{ panes: hiddenPanes, markerPrefix: `OPENCODE_PRESSURE_DONE_${runId}_` }
),
{ timeout: 60_000, message: 'Hidden PTY pressure generators did not finish' }
)
.toBe(hiddenPanes.length)
}
@@ -7,6 +7,7 @@ import {
type HiddenPressureOutputMode,
writePressureOutputScript
} from './artificial-opencode-hidden-pressure-script'
import { waitForHiddenPressureCompletion } from './artificial-opencode-hidden-pressure-completion'
import { waitForMarkerLatency } from './artificial-opencode-pane-interactions'
import {
ensureTerminalVisible,
@@ -174,6 +175,8 @@ export async function runHiddenRealPtyPressureScenario<
typingPtyId,
runId
)
// Why: typing must overlap live pressure, but restore must start from a complete hidden model rather than timing the remaining generators.
await waitForHiddenPressureCompletion(orcaPage, hiddenPanes, runId)
const debug = await deps.readTerminalPtyOutputDebug(orcaPage)
const scheduler = await deps.readTerminalOutputSchedulerDebug(orcaPage)
const mainPressure = await deps.readMainPtyPressureDebug(orcaPage)
@@ -249,14 +252,14 @@ export async function runHiddenRealPtyPressureScenario<
async function waitForMainHiddenDeliveryDrops<TMainPressure extends HiddenPressureMainSnapshot>(
orcaPage: Page,
deps: { readMainPtyPressureDebug: (page: Page) => Promise<TMainPressure | null> },
pressureOutputChars: number
minimumDroppedChars: number
): Promise<void> {
await expect
.poll(
async () => (await deps.readMainPtyPressureDebug(orcaPage))?.hiddenDeliveryDroppedChars ?? 0,
{ timeout: 30_000, message: 'Main hidden-delivery gate did not drop hidden PTY output' }
)
.toBeGreaterThanOrEqual(pressureOutputChars)
.toBeGreaterThanOrEqual(minimumDroppedChars)
}
async function measureHiddenOutputRestoreLatency(
@@ -28,13 +28,7 @@ type RevisitPressureMeasurement = {
// Why: the renderer hidden-skip counters were deleted with the skip grammar;
// only the mode-2031 fact-reply counter still exists renderer-side.
type RevisitPressureDebug = {
hiddenRendererMode2031ReplyCount: number
activeHiddenRestoreCount: number
activeHiddenRestoreSnapshotMs: number
activeHiddenRestoreReplayMs: number
activeHiddenRestoreTotalMs: number
}
type RevisitPressureDebug = { hiddenRendererMode2031ReplyCount: number }
type RevisitPressureSchedulerSnapshot = {
peakQueuedChars: number
@@ -207,18 +201,11 @@ export async function runRendererBackpressureRevisitScenario<
await deps.focusPane(orcaPage, revisitPane.paneKey)
await sendToTerminal(orcaPage, revisitPane.ptyId, `printf '\\n${revisitMarker}\\n'\r`)
const revisitLatencyMs = await waitForMarkerLatency(orcaPage, revisitMarker, 10_000)
const revisitDebug = await deps.readTerminalPtyOutputDebug(orcaPage)
testInfo.annotations.push({
type: 'opencode-main-pressure-worktree-revisit-marker',
description: `panes=${panes.length + 1} revisit=${revisitLatencyMs.toFixed(
1
)}ms heldAckChars=${ackGate?.heldAckChars ?? 0} activeRestores=${
revisitDebug?.activeHiddenRestoreCount ?? 0
} activeRestoreSnapshot=${(revisitDebug?.activeHiddenRestoreSnapshotMs ?? 0).toFixed(
1
)}ms activeRestoreReplay=${(revisitDebug?.activeHiddenRestoreReplayMs ?? 0).toFixed(
1
)}ms activeRestoreTotal=${(revisitDebug?.activeHiddenRestoreTotalMs ?? 0).toFixed(1)}ms`
)}ms heldAckChars=${ackGate?.heldAckChars ?? 0}`
})
// Why: this printf is measured after a worktree switch/focus while the
// background panes are still ACK-gate-held, so it gets its own under-load
@@ -61,10 +61,6 @@ type SyntheticOpenCodeWindow = Window & {
// counter still has a renderer-side producer.
type TerminalPtyOutputDebugSnapshot = {
hiddenRendererMode2031ReplyCount: number
activeHiddenRestoreCount: number
activeHiddenRestoreSnapshotMs: number
activeHiddenRestoreReplayMs: number
activeHiddenRestoreTotalMs: number
}
type TerminalOutputSchedulerDebugSnapshot = {