test(terminal): follow the existing suites to the one geometry owner

The gate caught fourteen failures the focused tests could not: every harness
that builds a partial app out of cherry-picked mixin methods, and every source
guard that named `fitAddon.fit()` by hand.

Most are wiring — `syncTerminalGeometry`, `_refitAfterCellSizeChange` and
`_resizeTerminalTo` added to the fakes so the real chain runs rather than a
stub of it. `file-browser-search` is the one that shows why it matters: without
the method on the fake, selectSession's unconditional call threw into its own
catch and every later assertion in the file measured a load that never
happened.

Two are not wiring.

`detached-session-pane-sizing` pinned the behaviour this change deliberately
reverses. It asserted the LOCAL fit still runs for a session owned by its own
window — "withhold the send, never the reflow" — so the assertion is restated
rather than patched, with the reason beside it and in the file's docblock: a
reflow the PTY is never told about leaves this xterm rendering a CLI's frames
against a shape that does not exist, and the popup that owns the pane is
drawing for its own width regardless. The old rule bought a garbled frame, not
a correct one.

`mobile-prompt-composer` sliced `_cleanupSessionData` as a fixed 1200-character
window, so the assertion depended on how much unrelated code sat above the line
it cared about. It reads the whole method now.

`terminal-scroll-intent` records `syncTerminalGeometry` rather than `fit`,
under its own name: recording a bare fit there would name the very thing the
subject was changed to stop doing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rounak Datta
2026-09-22 13:22:28 +05:30
co-authored by Claude Opus 5
parent abf1d1f1ca
commit d9fa9ba1eb
6 changed files with 58 additions and 7 deletions
+22 -4
View File
@@ -14,6 +14,12 @@
* already stood aside on the same condition, so this follows a rule the code
* had already established.
*
* ⚠️ It returns early BEFORE the local fit, not after (issue #464). The earlier
* rule was "withhold the send, never the reflow", which leaves this window's
* xterm at a shape the PTY was never told about — and a CLI computes its
* repaints from the shape it was told, so that reflow bought a garbled frame
* rather than a correct one. Withhold both, or neither.
*
* Loaded via `vm` with a stubbed context (no jsdom — jsdom is broken on this
* box; see connection-indicator.test.ts), the same way terminal-buffer-flush
* extracts the real mixin methods from terminal-ui.js.
@@ -56,7 +62,13 @@ function makeApp(overrides: Record<string, unknown> = {}) {
currentFetch = fetchMock;
const app = {
sendResize: mixin.sendResize,
// The real chain: sendResize fits, floors and applies through one function
// now, so the harness must let it (#464).
syncTerminalGeometry: mixin.syncTerminalGeometry,
_resizeTerminalTo: mixin._resizeTerminalTo,
_onPtyGeometryReport: vi.fn(),
getTerminalDimensions: () => ({ cols: 120, rows: 40 }),
terminal: { cols: 120, rows: 40, resize: vi.fn() },
fitAddon: { fit: vi.fn() },
detachedSessions: new Set<string>(),
isSoloWindow: false,
@@ -77,10 +89,16 @@ describe('detached sessions own their pane size', () => {
expect(changed).toBe(false);
// No request: the popup's size stands on the server.
expect(fetchMock).not.toHaveBeenCalled();
// The LOCAL fit still runs, so the dashboard's own xterm stays correct and
// tab-rail-resize's single settle-time refit is not swallowed. Same line the
// mobile-keyboard guard draws: withhold the send, never the reflow.
expect((app.fitAddon as { fit: ReturnType<typeof vi.fn> }).fit).toHaveBeenCalled();
// ⚠️ REVERSED by issue #464, deliberately. This used to assert that the
// LOCAL fit still ran — "withhold the send, never the reflow" — on the
// reasoning that it keeps the dashboard's own xterm correct. It does not:
// it leaves this xterm at a shape the PTY was never told about, and Claude
// Code computes every repaint from the shape it WAS told, so the frames
// land on rows nothing erased. The popup that owns the PTY is drawing for
// its own width either way, so the dashboard's reflow was a reflow nothing
// was rendering for. Withholding the resize means withholding all of it.
expect((app.fitAddon as { fit: ReturnType<typeof vi.fn> }).fit).not.toHaveBeenCalled();
expect((app.terminal as { resize: ReturnType<typeof vi.fn> }).resize).not.toHaveBeenCalled();
});
it('the solo window still sizes the session it displays', async () => {