fix(terminal): keep desktop history when a hidden pane is revealed (#33534)

Main built its copy of a hidden pane from the renderer's xterm at the
phone's 1000-row limit, and the hidden-output reveal repaints the pane
from that copy, so a restored pane with more history lost all but its
last 1000 rows on first reveal.
This commit is contained in:
Neil
2026-10-25 05:45:30 -07:00
committed by GitHub
parent a886dcd1fe
commit 309f819653
3 changed files with 35 additions and 3 deletions
@@ -0,0 +1,31 @@
import { expect, it, vi } from 'vitest'
import { createHydrationRuntime, PTY_ID, SIZE } from './headless-hydration-ownership-test-fixture'
const LINES = Array.from({ length: 1500 }, (_, i) => `L${String(i + 1).padStart(4, '0')} line`)
it('hydrates main from the renderer at desktop depth so a hidden-output reveal keeps history', async () => {
const runtime = createHydrationRuntime()
const serialize = vi.fn(async (_ptyId: string, opts?: { scrollbackRows?: number }) => ({
data: LINES.slice(-((opts?.scrollbackRows ?? 0) + SIZE.rows)).join('\r\n'),
...SIZE
}))
runtime.setPtyController({
write: () => true,
kill: () => true,
getForegroundProcess: async () => null,
getSize: () => SIZE,
hasRendererSerializer: () => true,
serializeBuffer: serialize
})
runtime.onPtyData(PTY_ID, '\r\n$ ', 1)
await runtime.model().writeChain
expect(serialize).toHaveBeenCalledWith(PTY_ID, { scrollbackRows: 5000 })
const snapshot = await runtime.serializeHiddenOutputRecoveryBuffer(PTY_ID, {
scrollbackRows: 5000
})
const restored = `${snapshot?.scrollbackAnsi ?? ''}${snapshot?.data ?? ''}`
expect(restored.match(/L\d{4} line/g)).toHaveLength(1500)
expect(restored).toContain('L0001 line')
})
+1 -1
View File
@@ -1,3 +1,3 @@
// Bound desktop xterm hydration for phones: replaying the full 50k-row buffer across IPC/headless emulation is expensive (docs/mobile-prefer-renderer-scrollback.md).
// Bound phone snapshots: replaying the full 50k-row buffer across IPC/headless emulation is expensive (docs/mobile-prefer-renderer-scrollback.md).
export const MOBILE_SUBSCRIBE_SCROLLBACK_ROWS = 1000
export const MOBILE_SNAPSHOT_BYTE_BUDGET = 512 * 1024
@@ -3,7 +3,7 @@ import { splitFreebuffScreenUpdates } from '../../../../shared/freebuff-screen-s
import { OrcaRuntimeWithSerializeMainTerminalBuffer } from './terminal-buffer-operations'
import { observeFreebuffTerminalStatus } from '../../freebuff-terminal-status'
import { observeCodexCapacityTerminalStatus } from '../../codex-capacity-terminal-status'
import { MOBILE_SUBSCRIBE_SCROLLBACK_ROWS } from '../../scrollback-limits'
import { DESKTOP_TERMINAL_SCROLLBACK_ROWS_DEFAULT } from '../../../../shared/terminal-scrollback-policy'
import {
detectAgentStatusFromTitle,
normalizeTerminalTitle
@@ -57,8 +57,9 @@ export abstract class OrcaRuntimeWithMaybeHydrateHeadlessFromRenderer extends Or
}
try {
// Retain normal-buffer scrollback while a TUI is in alt-screen so pre-TUI history remains restorable (#6106).
// Desktop depth (this emulator's retention): a hidden-output reveal repaints the pane from this model.
const rendered = await controller.serializeBuffer!(ptyId, {
scrollbackRows: MOBILE_SUBSCRIBE_SCROLLBACK_ROWS
scrollbackRows: DESKTOP_TERMINAL_SCROLLBACK_ROWS_DEFAULT
})
if (
this.headlessTerminals.get(ptyId) !== state ||