fix(terminal): report restored remote replay grid

This commit is contained in:
Brennan Benson
2026-08-14 17:15:30 -07:00
parent db34fa92e5
commit 305a4aaabc
2 changed files with 80 additions and 15 deletions
@@ -17256,13 +17256,20 @@ describe('connectPanePty', () => {
disposable.dispose()
})
it('paints remote replay at its snapshot grid before returning to the live pane grid', async () => {
it('paints remote replay at its snapshot grid and reports the live grid from a measurable hidden pane', async () => {
const { connectPanePty } = await import('./pty-connection')
enableActiveRuntimeEnvironment()
const transport = createMockTransport('remote:env-1@@terminal-grid')
const capturedReplayCallback: {
current:
| ((data: string, meta?: { snapshotCols?: number; snapshotRows?: number }) => void)
| ((
data: string,
meta?: {
snapshotCols?: number
snapshotRows?: number
pendingEscapeTailAnsi?: string
}
) => void)
| null
} = { current: null }
transport.connect.mockImplementation(async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
@@ -17285,9 +17292,10 @@ describe('connectPanePty', () => {
callback?.()
}) as typeof pane.terminal.write
const manager = createManager(1)
const deps = createDeps()
const deps = createDeps({ isVisibleRef: { current: false } })
const disposable = connectPanePty(pane as never, manager as never, deps as never)
await flushAsyncTicks(6)
transport.resize.mockClear()
capturedReplayCallback.current?.('snapshot payload', {
snapshotCols: 60,
@@ -17304,6 +17312,40 @@ describe('connectPanePty', () => {
)
expect(pane.terminal.cols).toBe(180)
expect(pane.terminal.rows).toBe(50)
expect(transport.resize).toHaveBeenCalledWith(180, 50)
vi.mocked(pane.terminal.resize).mockClear()
capturedReplayCallback.current?.('unknown-size payload', {
snapshotCols: 0,
snapshotRows: 0
})
await flushAsyncTicks(12)
expect(pane.terminal.resize).not.toHaveBeenCalledWith(0, 0)
vi.mocked(pane.terminal.resize).mockClear()
capturedReplayCallback.current?.('', {
snapshotCols: 30,
snapshotRows: 10,
pendingEscapeTailAnsi: '\x1b[3'
})
await flushAsyncTicks(12)
expect(pane.terminal.resize).not.toHaveBeenCalledWith(30, 10)
const { setDriverForPty } = await import('@/lib/pane-manager/mobile-driver-state')
setDriverForPty('remote:env-1@@terminal-grid', { kind: 'mobile', clientId: 'phone-1' })
transport.resize.mockClear()
try {
capturedReplayCallback.current?.('mobile-owned payload', {
snapshotCols: 60,
snapshotRows: 20
})
await flushAsyncTicks(12)
expect(pane.terminal.cols).toBe(180)
expect(pane.terminal.rows).toBe(50)
expect(transport.resize).not.toHaveBeenCalled()
} finally {
setDriverForPty('remote:env-1@@terminal-grid', { kind: 'idle' })
}
disposable.dispose()
})
@@ -25807,6 +25849,7 @@ describe('connectPanePty', () => {
expect(getSize).not.toHaveBeenCalled()
expect(pane.fitAddon.proposeDimensions).toHaveBeenCalled()
expect(pane.fitAddon.fit).not.toHaveBeenCalled()
expect(transport.resize).toHaveBeenCalledWith(120, 40)
})
@@ -4350,8 +4350,29 @@ export function connectPanePty(
}
}
}
const reportRemoteViewportAfterFit = (
ptyId: string | null,
expectedStreamGeneration: number
): void => {
if (
!ptyId ||
!isRemoteRuntimePtyId(ptyId) ||
disposed ||
transport.getPtyId() !== ptyId ||
transportStreamGeneration !== expectedStreamGeneration ||
shouldSuppressDesktopPtyResize()
) {
return
}
const cols = pane.terminal.cols
const rows = pane.terminal.rows
if (Number.isFinite(cols) && Number.isFinite(rows) && cols > 0 && rows > 0) {
transport.resize(cols, rows)
}
}
const reportRemoteViewportOnReveal = (): void => {
const revealedPtyId = transport.getPtyId()
const revealedStreamGeneration = transportStreamGeneration
if (
!revealedPtyId ||
!isRemoteRuntimePtyId(revealedPtyId) ||
@@ -4362,14 +4383,7 @@ export function connectPanePty(
return
}
safeFitAndThen(pane, 'remote-reveal-viewport', () => {
if (disposed || transport.getPtyId() !== revealedPtyId || shouldSuppressDesktopPtyResize()) {
return
}
const cols = pane.terminal.cols
const rows = pane.terminal.rows
if (Number.isFinite(cols) && Number.isFinite(rows) && cols > 0 && rows > 0) {
transport.resize(cols, rows)
}
reportRemoteViewportAfterFit(revealedPtyId, revealedStreamGeneration)
})
}
let pendingForegroundGridDriftCheckRaf: number | null = null
@@ -5649,7 +5663,8 @@ export function connectPanePty(
let replayDrainQueued = false
const drainReplayDataQueue = async (
expectedPtyId: string | null,
expectedStreamGeneration: number
expectedStreamGeneration: number,
noteSnapshotGridReplay: () => void
): Promise<boolean> => {
let appliedCurrentPayload = false
while (pendingReplayData !== null) {
@@ -5692,9 +5707,11 @@ export function connectPanePty(
)
if (
snapshotDimensions &&
data.length > 0 &&
(pane.terminal.cols !== snapshotDimensions.cols ||
pane.terminal.rows !== snapshotDimensions.rows)
) {
noteSnapshotGridReplay()
suppressStructuralReplayPtyResize = true
try {
pane.terminal.resize(snapshotDimensions.cols, snapshotDimensions.rows)
@@ -5760,6 +5777,7 @@ export function connectPanePty(
pendingReplayData?.streamGeneration ?? transportStreamGeneration
beginReattachLiveDataDeferral(scheduledStreamGeneration)
let replayCompleted = false
let replayUsedSnapshotGrid = false
replayWriteQueue = replayWriteQueue
.catch(() => undefined)
.then(() =>
@@ -5767,7 +5785,10 @@ export function connectPanePty(
async () => {
replayCompleted = await drainReplayDataQueue(
scheduledPtyId,
scheduledStreamGeneration
scheduledStreamGeneration,
() => {
replayUsedSnapshotGrid = true
}
)
},
{
@@ -5776,8 +5797,9 @@ export function connectPanePty(
transport.getPtyId() === scheduledPtyId &&
transportStreamGeneration === scheduledStreamGeneration,
afterRestore: () => {
if (replayCompleted) {
safeFit(pane)
if (replayCompleted && replayUsedSnapshotGrid && safeFit(pane)) {
// Why: a measurable hidden pane is authoritative; only presence ownership gates this report (#12768).
reportRemoteViewportAfterFit(scheduledPtyId, scheduledStreamGeneration)
}
}
}