fix(tiles): a tile that joined before its pane existed resends its size

A session Run makes while the grid is open joins as a tile right away, so
the tile connects and sends its size before Run starts the pane. The
server only records a resize for a session with no PTY and spawns the pane
at 120x40, and Run's own resize step measures the parked main terminal
(display: none, so nothing). Measured live for Shell and Claude: a 97x17
tile over a 120x40 pane, for good (#464).

The chrome refresh now remembers each tile's last-seen pid and calls
TerminalTile.paneStarted() when it appears or changes. paneStarted()
forgets the sent size and sends it; a hidden tile (a zoomed neighbour)
sends nothing and keeps it forgotten, so its next fit() sends it, which a
plain fit({ force: true }) would lose. Keyed on the sessions map, so a
handleInit after an SSE drop counts too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-10-06 19:46:04 +02:00
parent f1e5b82ecc
commit fa9d5879b3
8 changed files with 109 additions and 3 deletions
+33
View File
@@ -583,6 +583,39 @@ describe('TerminalTile geometry (#464: the pane and its PTY never disagree)', ()
expect(resizeFrames(ws).at(-1)).toEqual({ t: 'z', c: 28, r: 30, v: 'desktop' });
});
it('paneStarted() resends an unchanged size: the first one went out before there was a PTY', async () => {
const { tile, ws } = await connectTile(makeApp());
ws.open();
tile.paneStarted();
expect(resizeFrames(ws)).toEqual([
{ t: 'z', c: 80, r: 24, v: 'desktop' },
{ t: 'z', c: 80, r: 24, v: 'desktop' },
]);
// Only once: the size is recorded again, so a plain fit does not repeat it.
tile.fit();
expect(resizeFrames(ws)).toHaveLength(2);
});
it('paneStarted() on a hidden tile sends nothing, and its next fit sends the size', async () => {
const { tile, ws } = await connectTile(makeApp());
ws.open();
FakeFit.proposed = { cols: NaN, rows: NaN };
tile.paneStarted();
expect(resizeFrames(ws)).toHaveLength(1);
// Shown again (a zoom ends) at the same size it had: still sent.
FakeFit.proposed = { cols: 80, rows: 24 };
tile.fit();
expect(resizeFrames(ws)).toHaveLength(2);
});
it('paneStarted() before the socket opens sends nothing; the open sends the size once', async () => {
const { tile, ws } = await connectTile(makeApp());
tile.paneStarted();
expect(resizeFrames(ws)).toHaveLength(0);
ws.open();
expect(resizeFrames(ws)).toEqual([{ t: 'z', c: 80, r: 24, v: 'desktop' }]);
});
it('reports nothing while hidden (the fit addon measures NaN)', async () => {
const { tile, ws, term } = await connectTile(makeApp());
ws.open();