feat(tiles): zoom a tile (button, Alt+Shift+Enter), and auto-zoom when the window is too small

⤢ in a tile's header, or Alt+Shift+Enter (registry entry zoom-tile, applies
only while the grid is open and is swallowed before the Shift+Enter newline
gate), makes that tile fill the grid like tmux zoom. The other tiles stay
connected but hidden, so they measure nothing and send no resize; pressing it
again restores the grid and refits every tile, since the hidden ones have a
stale size. Zooming a tile that is not focused focuses it first (a human
selection).

As in tmux, moving focus to another tile restores the grid, and so does
removing the zoomed tile or adding one while a tile is zoomed by hand.

When the grid area cannot fit the tiles' minimum size, the grid zooms the
focused tile itself with a hint; that zoom follows focus and lifts once the
window fits again. A zoom the user chose is left alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-10-06 17:49:23 +02:00
parent 83d0caa209
commit 6fedbd1b09
5 changed files with 314 additions and 9 deletions
+7
View File
@@ -323,6 +323,13 @@ describe('xterm key handlers swallow a chord that applies', () => {
expect(handler(chord(RIGHT))).toBe(true);
});
it('Alt+Shift+Enter (zoom) never becomes a Shift+Enter newline in the tile session', async () => {
const handler = await tileHandler(true);
fetchMock.mockClear();
expect(handler(chord({ key: 'Enter', code: 'Enter', altKey: true, shiftKey: true }))).toBe(false);
expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/send-key'))).toBe(false);
});
it('returns false for the toggle chord', async () => {
const handler = await tileHandler(true);
expect(handler(chord(TOGGLE))).toBe(false);