fix(terminal): reset state when remote restore re-arms

This commit is contained in:
Brennan Benson
2026-08-13 02:08:09 -07:00
parent 6dd2bbfd38
commit 952234a8ff
2 changed files with 46 additions and 1 deletions
@@ -15898,6 +15898,50 @@ describe('connectPanePty', () => {
disposable.dispose()
})
it('grounds byte-gap state before a remote restore re-arms', async () => {
const { connectPanePty } = await import('./pty-connection')
const remotePtyId = 'remote:env-1@@terminal-rearm'
const transport = createMockTransport(remotePtyId)
const capturedDataCallback: {
current: ((data: string, meta?: { seq?: number; rawLength?: number }) => void) | null
} = { current: null }
transport.connect.mockImplementation(async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return remotePtyId
})
const snapshot = createDeferred<{ data: string; cols: number; rows: number; seq: number }>()
transport.serializeBuffer = vi.fn().mockReturnValue(snapshot.promise)
transportFactoryQueue.push(transport)
const hidden = 'x'.repeat(2 * 1024 * 1024 + 1)
const live = 'remote-live-after-rearm\r\n'
const pane = createPane(1)
const deps = createDeps({ isVisibleRef: { current: false } })
const disposable = connectPanePty(pane as never, createManager(1) as never, deps as never)
await flushAsyncTicks(6)
vi.useFakeTimers()
capturedDataCallback.current?.(hidden, { seq: hidden.length, rawLength: hidden.length })
;(deps.isVisibleRef as { current: boolean }).current = true
capturedDataCallback.current?.(live, {
seq: hidden.length + live.length,
rawLength: live.length
})
await flushAsyncTicks(4)
vi.advanceTimersByTime(750)
await flushAsyncTicks(10)
const written = pane.terminal.write.mock.calls.map(([data]) => data as string)
const resetIndex = written.indexOf(RESET_AFTER_BYTE_GAP)
const liveIndex = written.indexOf(live)
expect(resetIndex).toBeGreaterThanOrEqual(0)
expect(liveIndex).toBeGreaterThan(resetIndex)
expect(written.join('')).not.toContain('main recovery was unavailable')
disposable.dispose()
})
it('falls back after repeated null hidden restore retries and drains blocked foreground', async () => {
const { connectPanePty } = await import('./pty-connection')
const transport = createMockTransport('pty-id')
@@ -7014,6 +7014,7 @@ export function connectPanePty(
cycle: hiddenOutputRestoreRemoteAbandonCycles
})
noteHiddenOutputRestoreFloodBackpressure()
writePtyOutputToXterm(RESET_AFTER_BYTE_GAP, true)
return true
}
@@ -7066,7 +7067,7 @@ export function connectPanePty(
// backpressure must not outlive it — it would re-open recovery and banner a second time.
clearHiddenOutputRestoreFloodRepaintTimer()
writeRestoreUnavailableWarning()
} else {
} else if (!rearmedRemoteRestore) {
writePtyOutputToXterm(RESET_AFTER_BYTE_GAP, true)
}
if (hadPendingOverflow) {