fix(run,terminal): merge-time fixes for the Instance count stepper and capture geometry

#454: the behaviour the PR adds had no test, so a regression test drives
runGrok() at tabCount 3 and asserts three quick-start POSTs with sequential
w<n>-<case> names (verified to fail against master's session-ui.js). Each
caller now reads the count BEFORE its opening banner and announces it there,
the way runClaude() already did, so a launch no longer prints two headers and
a launch with another session already active still says how many are starting.
runClaude() calls the shared _readTabCount() instead of its own copy of the
1..20 clamp, and that helper optional-chains the element read, since hoisting
it above each caller's try block would otherwise let a missing #tabCount throw
where the launch-error path cannot report it.

#435: sizeMovedUnderLoad derived from data.source alone. `mux-visible` is not
sufficient: a failed display-message cursor query makes capturePaneBuffer skip
the snapshot repaint and return the raw capture, which the route still labels
mux-visible, so a size that moved during such a load bought a full forced
reload to repair a frame that was never positioned. It now tests
Number.isFinite(data.captureRows) like its two siblings.

Plus the invariants and CLAUDE.md lines promised on #435: a visible capture
reports its geometry and omits it when nothing was positioned, the comparison
runs on mux-visible only, and the replay is capped at one attempt and latches
per session when it cannot converge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-09-19 12:18:39 +02:00
parent 2c3ccdf030
commit c376534a50
7 changed files with 134 additions and 64 deletions
+9
View File
@@ -6577,8 +6577,17 @@ class CodemanApp {
// of every non-shell session per page takes the full-history path, an
// ungated comparison fires most often on the one response it cannot help.
const framePositionsRowsAbsolutely = data.source === 'mux-visible';
// `mux-visible` is necessary but not sufficient: when the `display-message`
// cursor query fails, `capturePaneBuffer` skips the snapshot repaint and
// returns the raw capture, and the route still labels a non-empty body
// `mux-visible`. That body positions nothing and reports no geometry, so a
// size that moved during such a load has nothing to repair, and replaying
// would buy a second capture, a reset plus chunked rewrite, a dropped
// WebSocket and a discarded xterm snapshot for it. The two comparisons
// below already stand down on an absent field; this one has to as well.
const sizeMovedUnderLoad =
framePositionsRowsAbsolutely &&
Number.isFinite(data.captureRows) &&
!!dimsAtCapture &&
!!dimsAfterLoad &&
(dimsAfterLoad.cols !== dimsAtCapture.cols || dimsAfterLoad.rows !== dimsAtCapture.rows);