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 cd806774737..211e6c1c819 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection.test.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection.test.ts @@ -7316,7 +7316,7 @@ describe('connectPanePty', () => { disposable.dispose() }) - it('preserves scrollback by skipping the destructive clear when restoring an alternate-screen snapshot', async () => { + it('clears only the alternate screen when restoring an alternate-screen snapshot', async () => { const { connectPanePty } = await import('./pty-connection') const transport = createMockTransport('pty-id') const capturedDataCallback: { @@ -7365,6 +7365,19 @@ describe('connectPanePty', () => { '\x1b[2J\x1b[3J\x1b[H', expect.any(Function) ) + // Why: the snapshot's ?1049h no-ops on an already-alt pane and serialized + // frames skip blank cells, so the pre-hide frame bleeds through unless the + // alt screen is cleared (without \x1b[3J) before the snapshot paints. + expect(pane.terminal.write).toHaveBeenCalledWith( + '\x1b[?1049h\x1b[2J\x1b[H', + expect.any(Function) + ) + const writes = (pane.terminal.write as ReturnType).mock.calls.map( + (call) => call[0] + ) + expect(writes.indexOf('\x1b[?1049h\x1b[2J\x1b[H')).toBeLessThan( + writes.indexOf('altscreen-snapshot\r\n') + ) 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 85c50f6fe8a..01f0132c3fc 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection.ts @@ -4393,6 +4393,13 @@ 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: the snapshot's own ?1049h is a no-op when the pane is already on + // the alternate screen, and the serialized frame skips blank cells — so + // without clearing the alt screen the pre-hide frame bleeds through + // every cell the final frame leaves blank. \x1b[2J on the alt buffer + // does not touch the normal buffer's scrollback the TUI returns to. + writeReplayData('\x1b[?1049h\x1b[2J\x1b[H') } writeReplayData(snapshot.data) writeReplayData(POST_REPLAY_LIVE_SNAPSHOT_RESET)