diff --git a/src/renderer/src/components/terminal-pane/layout/terminal-pane-layout-restore.ts b/src/renderer/src/components/terminal-pane/layout/terminal-pane-layout-restore.ts index 09518d91f8d..5713ce708c4 100644 --- a/src/renderer/src/components/terminal-pane/layout/terminal-pane-layout-restore.ts +++ b/src/renderer/src/components/terminal-pane/layout/terminal-pane-layout-restore.ts @@ -1,5 +1,6 @@ import type { PaneManager } from '@/lib/pane-manager/pane-manager' import { useAppStore } from '@/store/index' +import { wakeScrollbackPreview } from '@/lib/orca-cloud/cloud-wake-scrollback' import { applyExpandedLayoutTo } from './expand/expanded-pane-layout' import { replayTerminalLayout } from './terminal-layout-replay' import { restoreScrollbackBuffers } from '../snapshots/saved-terminal-output-replay' @@ -48,6 +49,14 @@ export function restoreTerminalPaneLayout(args: { refs.restoredViewportBlankingPanesRef, liveAltScreenLeafIds ) + // WAKE-SCROLLBACK: waking remounts a cloud pane with a fresh, empty xterm; paint its kept pre-sleep + // output now (client-side, text + SGR) so it never shows blank before the woken snapshot's restore. + for (const pane of manager.getPanes()) { + const preview = wakeScrollbackPreview(pane.leafId) + if (preview !== null) { + pane.terminal.write(preview) + } + } const hasScrollbackRefs = Boolean(initialLayoutRef.current.scrollbackRefsByLeafId) if ( restoredBuffers && diff --git a/src/renderer/src/lib/orca-cloud/cloud-wake-scrollback.test.ts b/src/renderer/src/lib/orca-cloud/cloud-wake-scrollback.test.ts index daab28205e0..8dcb1d7a63b 100644 --- a/src/renderer/src/lib/orca-cloud/cloud-wake-scrollback.test.ts +++ b/src/renderer/src/lib/orca-cloud/cloud-wake-scrollback.test.ts @@ -7,6 +7,7 @@ import { finishWakeScrollbackRestore, forgetWakeScrollback, hasPendingWakeScrollback, + wakeScrollbackPreview, resetWakeScrollbackForTests } from './cloud-wake-scrollback' @@ -243,6 +244,16 @@ describe('WAKE-SCROLLBACK', () => { expect(beginWakeScrollbackRestore('leaf-1', terminal)?.block).toContain('new') }) + it('a pane remounted on wake (fresh, empty xterm) is painted with its kept output at once', async () => { + const before = pane(6) + await write(before, '\x1b[32m$ ls\x1b[0m\r\nREADME\r\n$ ') + capture(before, 'leaf-1') + const remounted = pane(6) + await write(remounted, wakeScrollbackPreview('leaf-1')!) + expect(text(remounted)).toEqual(['$ ls', 'README', '$']) + expect(wakeScrollbackPreview('leaf-none')).toBeNull() + }) + it('reports a pending restore only when there is output to put back', async () => { const empty = pane(4) captureWakeScrollback('leaf-e', empty, { sleptAt: SLEPT_AT, agentPane: false, maxRows: 5000 }) diff --git a/src/renderer/src/lib/orca-cloud/cloud-wake-scrollback.ts b/src/renderer/src/lib/orca-cloud/cloud-wake-scrollback.ts index 5029062f552..473f1ac8e0d 100644 --- a/src/renderer/src/lib/orca-cloud/cloud-wake-scrollback.ts +++ b/src/renderer/src/lib/orca-cloud/cloud-wake-scrollback.ts @@ -124,6 +124,15 @@ export function armWakeScrollback(leafIds: Iterable): void { } } +/** + * What a freshly mounted pane shows until its woken snapshot's restore: the kept output alone + * (text + SGR only), or null when there is none. + */ +export function wakeScrollbackPreview(leafId: string): string | null { + const text = pending.get(leafId)?.text ?? null + return text === null ? null : `\x1b[0m${sanitizeRestoredText(text)}\x1b[0m` +} + /** The pane has pre-sleep output waiting to be put back after its wake. */ export function hasPendingWakeScrollback(leafId: string): boolean { return (pending.get(leafId)?.text ?? null) !== null