From 32eb139c19bd48739ce072704b06e1eb81f13f37 Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 23 Jul 2026 00:19:00 +0200 Subject: [PATCH] test(tui): model mux pane growth in the render-stress shadow ledger - The shadow oracle floored windowTop at the committed seam unconditionally, predicting a top-anchored view with a blank bottom row when a tmux pane grows taller. Real tmux (screen_resize_y) and the harness's emulated terminal pull rows back out of pane history into the grid, and the renderer already rebases its commit seam accordingly (tui.ts pane-growth path); repainting top-anchored there would overwrite the resurrected row. The ledger now mirrors the seam rebase on geometry frames where frameLength - height < committed, keeping the invariant an exact-view check. - Deterministic repro: darwin-normal-tmux-small seed=0x0ddc0ffe op 24 (resizeHeight 3->4); pre-existing on 7b141199d5, previously masked because the stress suite is skipIf(CI). --- packages/tui/test/render-stress-harness.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/tui/test/render-stress-harness.ts b/packages/tui/test/render-stress-harness.ts index 4c9a02778..7962af8e2 100644 --- a/packages/tui/test/render-stress-harness.ts +++ b/packages/tui/test/render-stress-harness.ts @@ -2590,15 +2590,29 @@ class StressDriver { } // Audit and shrink re-anchoring are mirrored at render time (they can // fire on zero-byte frames); the write hook only applies commits. - const windowTop = Math.max(this.#shadowCommitted, length - height, 0); - this.#shadowWindowTop = windowTop; + const tail = Math.max(0, length - height); // Overlays and multiplexer geometry frames freeze commits; a geometry // frame also re-bases the raw prefix at the new width (accepted wrap // drift, mirrored from the engine). if (this.#shadowFrameGeometryChanged) { + if (tail < this.#shadowCommitted) { + // Pane growth pulls committed rows back out of multiplexer + // scrollback into the grid (tmux screen_resize_y takes lines + // from pane history when the screen grows; the emulated + // terminal does the same). The engine mirrors that by rebasing + // its commit seam to the exposed frame tail and force-rewriting + // the whole window (tui.ts geometry-growth branch) — un-commit + // the resurrected rows here so the ledger agrees. + const pulled = this.#shadowCommitted - tail; + this.#shadowTape.length = Math.max(0, this.#shadowTape.length - pulled); + this.#shadowCommitted = tail; + } + this.#shadowWindowTop = Math.max(this.#shadowCommitted, tail); this.#shadowRawPrefix = raw.slice(0, this.#shadowCommitted); return; } + const windowTop = Math.max(this.#shadowCommitted, tail); + this.#shadowWindowTop = windowTop; if (this.#shadowFrameOverlay) return; const chunkTo = Math.max(this.#shadowCommitted, Math.min(length, windowTop)); for (let i = this.#shadowCommitted; i < chunkTo; i++) {