diff --git a/src/renderer/src/components/terminal-pane/pty-connection.test.ts b/src/renderer/src/components/terminal-pane/pty-connection.test.ts index 5d7fec5c1a0..c7aed37b482 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection.test.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection.test.ts @@ -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() }) diff --git a/src/renderer/src/components/terminal-pane/pty-connection.ts b/src/renderer/src/components/terminal-pane/pty-connection.ts index 809734e1a98..d5115ed73dc 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection.ts @@ -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)