fix(terminal): replay a pane capture at the geometry it was taken at

A visible-frame capture repaints each row at an absolute position, counting up
to the pane's height. A terminal shorter than that clamps every address past its
own height onto its last line. The overflow rows then overwrite one another, and
the rows underneath are lost. Replaying a real 50-row capture into a 30-row
terminal rendered 28 lines of a 45-line command and drew the frame twice.

Nothing in the response said what height the frame was built for, so the client
could not detect this. A capture now reports the geometry it was really taken at
through `capturedGeometry` on `PaneCaptureOptions`, and the terminal response
carries it as `captureCols` and `captureRows`. When the captured pane is taller
than the terminal, or the size that produced the capture did not survive the
load, `selectSession` replays once at the size that stuck. `resizeRetry` caps
that at one attempt, so two competing fits cannot trade replays forever.

The retry re-arms the full-history flag only when the pass that ran had consumed
it. A tab switch takes the bounded tail, so its retry takes the tail too:
clearing the flag unconditionally would upgrade that switch into a fresh
scrollback capture the user never asked for, which the route's own comments put
at tens of megabytes.

What this repairs is a capture that won a race against the resize meant to
precede it. It does not repair a capture whose pane was too tall because
`Session.resize` declined the resize outright, which it does for a small
viewport while a desktop viewport's size claim is live. The retry re-sends the
same declined resize and captures the same pane, and `resizeRetry` then stops
it. Repairing that means changing who owns the pane size, which is a policy
question this does not touch. The reported geometry still helps there, because
the client can see the mismatch at all rather than being blind to it.

Follows #395, #396 and #397, which fixed the other ways the replayed frame and
the terminal could disagree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michael Grundberg
2026-09-19 10:44:35 +02:00
co-authored by Claude Opus 5
parent 3cdb4bf42e
commit 3edf9aae2f
11 changed files with 462 additions and 14 deletions
+9
View File
@@ -159,6 +159,15 @@ export interface PaneCaptureOptions {
* the 1MB execSync default (ENOBUFS).
*/
maxCaptureBytes?: number;
/**
* Filled in by the implementation with the pane geometry the capture was
* really taken at, which is not always the geometry the caller last asked
* for: a resize and a capture can race, and a pane whose size a desktop
* viewport has claimed ignores a smaller client's resize outright. A
* visible-frame capture addresses every row absolutely, so a consumer
* rendering it needs the real height to know the frame fits.
*/
capturedGeometry?: { cols: number; rows: number };
}
/**