fix: address pr-bug-scan validated finding from #6532

For alt-screen restore, emit viewport erase \x1b[2J\x1b[H (no \x1b[3J) before replay so stale pre-hide cells are wiped while scrollback is preserved.
This commit is contained in:
orca-bug-scan-bot
2026-06-28 13:13:01 -07:00
parent e47b3364d9
commit a10db2f8be
2 changed files with 16 additions and 2 deletions
@@ -6158,12 +6158,17 @@ describe('connectPanePty', () => {
await flushAsyncTicks(20)
expect(getMainBufferSnapshot).toHaveBeenCalledWith('pty-id', { scrollbackRows: 5000 })
// Why: the destructive clear wipes xterm's scrollback. Alt-screen TUIs keep
// their history in xterm, so restoring one must NOT emit the clear.
// Why: \x1b[3J wipes xterm's scrollback. Alt-screen TUIs keep their history
// in xterm, so restoring one must NOT emit the scrollback-clearing form.
expect(pane.terminal.write).not.toHaveBeenCalledWith(
'\x1b[2J\x1b[3J\x1b[H',
expect.any(Function)
)
// Why: but the visible viewport must still be erased before replay.
// The SerializeAddon repaint is minimal (non-erasing moves for blank cells,
// omitted blank rows), so without \x1b[2J, cells the TUI blanked while the
// pane was hidden keep their stale pre-hide glyphs behind the restored frame.
expect(pane.terminal.write).toHaveBeenCalledWith('\x1b[2J\x1b[H', expect.any(Function))
expect(pane.terminal.write).toHaveBeenCalledWith('altscreen-snapshot\r\n', expect.any(Function))
disposable.dispose()
})
@@ -3242,6 +3242,15 @@ export function connectPanePty(
// clearing on restore loses scroll-up after a hidden->visible return.
// Mirrors the attach-time guard in pty-transport.ts.
writeReplayData('\x1b[2J\x1b[3J\x1b[H')
} else {
// Why: skip only \x1b[3J (the scrollback wipe) for alt-screen, but
// still erase the visible viewport with \x1b[2J before replay. The
// SerializeAddon repaint is minimal — it emits non-erasing cursor
// moves for default-background blanks and omits fully-blank rows — and
// \x1b[?1049h is a no-op when xterm is already on the alt buffer, so
// without this erase, cells the TUI blanked while hidden keep their
// stale pre-hide glyphs behind the restored frame.
writeReplayData('\x1b[2J\x1b[H')
}
writeReplayData(snapshot.data)
writeReplayData(POST_REPLAY_LIVE_SNAPSHOT_RESET)