fix(cloud): WAKE-SCROLLBACK a pane remounted on wake paints its kept output at once (the blank frames were the fresh xterm before the snapshot)

This commit is contained in:
Neil
2026-11-11 09:53:09 -08:00
parent 1ed60a9774
commit a07ca09b41
3 changed files with 29 additions and 0 deletions
@@ -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 &&
@@ -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 })
@@ -124,6 +124,15 @@ export function armWakeScrollback(leafIds: Iterable<string>): 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