fix(terminal): report the replay destination grid from measured hidden panes

Reverts the visibility gate added in the previous commit; the remaining
changes there stand.

Gating the destination-grid report on pane visibility strands the host. A
hidden pane only reaches this report when its fit measured a real box, which
means the background measure lease deliberately laid it out full size for
~3s — an authoritative grid, not hidden-layout churn. Withholding the report
there has no recovery: opacity does not change getBoundingClientRect, so the
pane's pixels are identical at reveal, fitRevealedPane therefore neither
re-fits nor re-reports, and remote PTYs are excluded from the size
reassertion that would otherwise repair the drift. The host would stay
pinned at the snapshot grid permanently.

The measurability precheck already covers the case the gate was reaching
for: a display:none pane now skips the fit entirely rather than reporting
from it. Skipping an impossible fit leaves xterm on the source grid, so
reveal sees a real dimension change and reports through the normal guard
chain; withholding a report from a fit that succeeded leaves nothing for
reveal to notice. The presence lock still applies, and this path never
signals SIGWINCH.

The measure-lease test now asserts the report goes out, so re-adding the
gate fails it.
This commit is contained in:
Brennan Benson
2026-08-06 16:35:57 -07:00
parent 6a032296b3
commit 390ae5ce52
2 changed files with 12 additions and 9 deletions
@@ -9094,7 +9094,10 @@ describe('connectPanePty', () => {
expect(transport.resize.mock.calls).toEqual([])
})
it('refits but does not report the destination grid from a background measure-lease pane', async () => {
it('reports the destination grid from a background measure-lease pane', async () => {
// Why this test: the report is deliberately NOT gated on visibility here. A hidden
// pane that still measures is under the background measure lease, so its grid is
// authoritative — and reveal never re-sends it, because the pixels never changed.
const { pane, transport, replayCallback } = await connectRemoteSnapshotGridPane(
'remote:web-env-1@@pty-replay-grid-hidden',
{ visible: false }
@@ -9103,11 +9106,9 @@ describe('connectPanePty', () => {
replayCallback.current?.('remote snapshot bytes', { snapshotCols: 80, snapshotRows: 24 })
await flushAsyncTicks(20)
// A measure-lease pane is fully sized, so the fit lands — but a resize from a
// non-visible pane would SIGWINCH the host TUI and persist as the next claim.
expect(pane.fitAddon.fit).toHaveBeenCalled()
expect(pane.terminal.cols).toBe(120)
expect(transport.resize).not.toHaveBeenCalled()
expect(transport.resize).toHaveBeenCalledWith(120, 40)
})
it('arms no fit retry after replaying into an unmeasurable pane', async () => {
@@ -5813,14 +5813,16 @@ export function connectPanePty(
}
const destinationCols = pane.terminal.cols
const destinationRows = pane.terminal.rows
// Why guarded but still fitted: xterm must leave the source grid, yet
// a background measure-lease pane is fully sized (so the fit lands)
// while not visible, and even a non-claiming resize is remembered and
// replayed as a claim on the next subscribe.
// Why NOT gated on visibility, unlike the sibling fits: reaching here
// means the fit measured a real box, which for a hidden pane means the
// background measure lease deliberately laid it out full size — an
// authoritative grid, not hidden-layout churn. Reveal will not re-send
// it either: the pixels never changed, so fitRevealedPane skips, and
// remote PTYs are excluded from ptySizeReassertion. This is the only
// chance to report. (This path never signals SIGWINCH.)
if (
destinationCols > 0 &&
destinationRows > 0 &&
isRendererPtyResizeAuthoritative() &&
!shouldSuppressDesktopPtyResize()
) {
transport.resize(destinationCols, destinationRows)