Preserve terminal scrollback when restoring a hidden alternate-screen pane

On Windows, an alternate-screen TUI pane (Claude Code) could lose scroll-up
after being hidden (tab/worktree switch or app backgrounded) and shown again.

Root cause: on hidden->visible restore, applyMainBufferSnapshot
unconditionally wrote a destructive clear (\x1b[2J\x1b[3J\x1b[H) before
replaying the snapshot, wiping xterm's scrollback. The attach/reattach path
already guards this same clear behind !isAlternateScreen (pty-transport.ts);
the snapshot path did not.

Thread an alternateScreen flag from the backend headless serialization
through the pty:getMainBufferSnapshot IPC + preload types into
applyMainBufferSnapshot, and skip the destructive clear when restoring an
alternate-screen buffer. Add a regression test asserting the clear is NOT
emitted for an alt-screen restore (and the existing normal-buffer test still
asserts it IS).

Fixes #5723
This commit is contained in:
brennanb2025
2026-06-27 21:51:07 -07:00
parent e40ad6a93a
commit a0de430397
7 changed files with 81 additions and 3 deletions
+12 -2
View File
@@ -5262,6 +5262,7 @@ export class OrcaRuntimeService {
seq?: number
source?: 'headless' | 'renderer'
oscLinks?: TerminalOscLinkRange[]
alternateScreen?: boolean
} | null> {
return this.serializeTerminalBufferFromAvailableState(ptyId, opts)
}
@@ -5278,6 +5279,7 @@ export class OrcaRuntimeService {
seq?: number
source?: 'headless' | 'renderer'
oscLinks?: TerminalOscLinkRange[]
alternateScreen?: boolean
} | null> {
return this.serializeHeadlessTerminalBuffer(ptyId, { ...opts, includeEmpty: true })
}
@@ -5294,6 +5296,7 @@ export class OrcaRuntimeService {
seq?: number
source?: 'headless' | 'renderer'
oscLinks?: TerminalOscLinkRange[]
alternateScreen?: boolean
} | null> {
const headlessSnapshot = await this.serializeHeadlessTerminalBuffer(ptyId, {
...opts,
@@ -5540,6 +5543,7 @@ export class OrcaRuntimeService {
seq?: number
source?: 'headless' | 'renderer'
oscLinks?: TerminalOscLinkRange[]
alternateScreen?: boolean
} | null> {
const headlessSnapshot = await this.serializeHeadlessTerminalBuffer(ptyId, opts)
if (headlessSnapshot) {
@@ -5650,6 +5654,7 @@ export class OrcaRuntimeService {
seq?: number
source?: 'headless'
oscLinks?: TerminalOscLinkRange[]
alternateScreen?: boolean
} | null> {
const state = this.headlessTerminals.get(ptyId)
if (!state) {
@@ -5664,7 +5669,8 @@ export class OrcaRuntimeService {
// caller can request scrollback so the user can scroll up to see prior
// agent output.
const requested = opts.scrollbackRows ?? 0
const scrollbackRows = state.emulator.isAlternateScreen ? 0 : requested
const isAlternateScreen = state.emulator.isAlternateScreen
const scrollbackRows = isAlternateScreen ? 0 : requested
const snapshot = state.emulator.getSnapshot({ scrollbackRows })
const data = snapshot.rehydrateSequences + snapshot.snapshotAnsi
return data.length > 0 || opts.includeEmpty === true
@@ -5676,7 +5682,11 @@ export class OrcaRuntimeService {
lastTitle: snapshot.lastTitle,
seq: state.outputSequence,
source: 'headless',
oscLinks: snapshot.oscLinks
oscLinks: snapshot.oscLinks,
// Why: lets the renderer skip the destructive scrollback clear when
// restoring an alt-screen snapshot — clearing wipes xterm's own
// history that the TUI relies on for scroll-up after a tab return.
alternateScreen: isAlternateScreen
}
: null
}