perf(resize): a window resize fits each grid tile once, not twice

A window resize reached every grid tile twice: the grid's own
ResizeObserver refits them (tile-grid.js _scheduleTileGridRefit, 150 ms
trailing), then the main terminal's trailing resize timer (terminal-ui.js
throttledResize, 300 ms) ran _forEachTile(fit) over them again. The second
pass re-measured six panes and sent nothing (_lastSentDims dedupes the PTY
side). The timer now refits the split's Pane B only ({ grid: false }); grid
tiles exist only while the grid owns the terminal, and then its observer
already covers them.

Measured (6 tiles, 20-step window resize and back, headless, tileperf):
fit() 12 -> 6 per resize burst; PTY resizes 6 -> 6; browser layouts
unchanged within noise (85/75 -> 92/71), so this removes wasted calls only.

Live, this code (6 tiles, tileperf): a 20-step window resize, and the resize
back, each ran fit() 6 times and sent 6 PTY resizes (12 and 6 before).

Test: the timer's one _forEachTile call passes { grid: false } (it lives
inside initTerminal, so read from source like the #464 geometry tests);
mutation-checked.

Scope: PR 2. The line is in terminal-ui.js (a PR 1 seam), but on PR 1 alone
_forEachTile reaches only the split's Pane B: the double refit needs the grid.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-10-07 06:30:09 +02:00
parent 5650be5200
commit 409fd658f2
2 changed files with 23 additions and 2 deletions
+18
View File
@@ -29,6 +29,8 @@
* tile-grid.js in one `vm` context, with the shared fake DOM and fake
* TerminalTile (test/mocks/tile-grid-vm.ts). Port: N/A.
*/
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
FakeTile,
@@ -357,6 +359,22 @@ describe('panes and fonts while the grid is open', () => {
expect(pane.sessionId).toBe('s-a');
});
it("the main terminal's window-resize timer leaves grid tiles to the grid's own observer", () => {
// A window resize fires both: the grid's ResizeObserver refits every tile
// (tile-grid.js _scheduleTileGridRefit) and the main terminal's trailing
// timer used to refit them again, twelve fit() calls for six tiles and
// nothing more sent (measured). The timer lives inside initTerminal(), so
// this reads its source; the split's Pane B is still refitted there.
const src = readFileSync(resolve(import.meta.dirname, '../src/web/public/terminal-ui.js'), 'utf8');
const start = src.indexOf('const throttledResize = () => {');
const end = src.indexOf("window.addEventListener('resize', throttledResize)", start);
expect(start).toBeGreaterThan(-1);
expect(end).toBeGreaterThan(start);
const calls = src.slice(start, end).match(/this\._forEachTile\?\.\([^;]*;/g) ?? [];
expect(calls).toHaveLength(1);
expect(calls[0]).toContain('{ grid: false }');
});
it('_forEachTile reaches every grid tile; { grid: false } skips them', () => {
const app = appWithGrid(true);
const seen: string[] = [];