a73x

aab60216

docs: what 32 sessions buys is names, not tiles on screen

a73x   2026-08-26 14:14

Commit message
docs: what 32 sessions buys is names, not tiles on screen

The session cap moved to 32 to match `wallview.max_tiles`, but every tile
is its own attach and `max_clients` is still 8 — a 32-tile wall against one
daemon refuses tiles nine and up. Read as written, the rationale promises a
32-tile wall and the next person to try one files a regression.

Also drops the stale "the table is four entries" from `pumpOnce`; the
reasoning holds at 32, the number does not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

docs/decisions.md
Old New
@@ -6691,11 +6691,16 @@ it.
6691 The 4 was "purpose bounds the surface" from the multi-session design: a number 6691 The 4 was "purpose bounds the surface" from the multi-session design: a number
6692 picked to keep the new thing small, never a measured limit. Multipane then grew 6692 picked to keep the new thing small, never a measured limit. Multipane then grew
6693 the wall to `wallview.max_tiles = 32` and the daemon cap did not move with it, 6693 the wall to `wallview.max_tiles = 32` and the daemon cap did not move with it,
6694 so a wall could ask for more sessions than the daemon would ever hold. It 6694 so a wall could name more sessions than the daemon would ever hold. It
6695 surfaced as a silently refused `Ctrl-\ c` — the client does say "cannot create a 6695 surfaced as a silently refused `Ctrl-\ c` — the client does say "cannot create a
6696 new session", but the relayout repaint that follows eats the notice, which is a 6696 new session", but the relayout repaint that follows eats the notice, which is a
6697 separate follow-up. 6697 separate follow-up.
6698 6698
6699 What the 32 buys is nameable sessions, not visible ones. Every tile is its own
6700 attach and therefore its own client slot, and `max_clients` is still 8, so a
6701 32-tile wall against one daemon still refuses tiles nine and up. Cycling 32
6702 sessions through eight tiles is the shape this enables.
6703
6699 Nothing about the 4 was performance. A session costs one engine, one pty and one 6704 Nothing about the 4 was performance. A session costs one engine, one pty and one
6700 shell, slots are filled lazily, and an empty slot polls fd -1. The two places 6705 shell, slots are filled lazily, and an empty slot polls fd -1. The two places
6701 the count does scale are worst cases that need a misbehaving peer to bite: the 6706 the count does scale are worst cases that need a misbehaving peer to bite: the
@@ -6703,10 +6708,11 @@ per-death drain is `max_sessions × 250ms` only when the whole table dies into
6703 stalled clients in one pass, and `pty.term_grace_ms` is paid only by a child 6708 stalled clients in one pass, and `pty.term_grace_ms` is paid only by a child
6704 that survives the master's close and then ignores SIGTERM. 6709 that survives the master's close and then ignores SIGTERM.
6705 6710
6706 `stats_text_len` is now derived from `max_sessions` rather than a literal with a 6711 `stats_text_len` is derived from `max_sessions` rather than a literal, so no
6707 comptime assert beside it, so the next bump cannot truncate a stats reply — the 6712 future bump to the table can truncate a stats reply. (Corrected 2026-08-26: the
6708 assert would have caught it, but only by failing the build, and deriving the 6713 per-line widths inside that derivation were still hand-counted and both
6709 number means there is nothing to catch. 6714 undercounted — 256 for a main line 375 wide, 44 for a session segment 63 wide.
6715 The bound is now read off the format strings themselves.)
6710 6716
6711 Filling a 32-slot table from a shell script found one thing worth writing down: 6717 Filling a 32-slot table from a shell script found one thing worth writing down:
6712 `acceptConn` parks every new connection in an OBSERVER slot and promotes it to a 6718 `acceptConn` parks every new connection in an OBSERVER slot and promotes it to a
src/server.zig
Old New
@@ -255,8 +255,10 @@ const AwaitState = struct {
255 }; 255 };
256 256
257 // Matches `wallview.max_tiles`: the 4 was surface-bounding (decisions.md, 257 // Matches `wallview.max_tiles`: the 4 was surface-bounding (decisions.md,
258 // 2026-08-25), never a measured limit, and a wall of 32 tiles could not 258 // 2026-08-25), never a measured limit. What it buys is 32 NAMEABLE
259 // fill it. 259 // sessions to cycle through, not 32 watched at once — every tile is its
260 // own attach and so its own client slot, and `max_clients` still refuses
261 // the ninth.
260 pub const max_sessions = 32; 262 pub const max_sessions = 32;
261 263
262 /// The smallest grid a session may exist at. Two owners used to decide 264 /// The smallest grid a session may exist at. Two owners used to decide
@@ -1079,8 +1081,8 @@ pub const Server = struct {
1079 // createSession: a session can therefore be BORN inside this loop, 1081 // createSession: a session can therefore be BORN inside this loop,
1080 // and if it lands in a slot the loop has already walked, an 1082 // and if it lands in a slot the loop has already walked, an
1081 // incremental counter never sees it. The daemon would then exit 1083 // incremental counter never sees it. The daemon would then exit
1082 // with a shell it forked moments earlier still running. The table 1084 // with a shell it forked moments earlier still running. Re-reading
1083 // is four entries; re-reading it costs nothing and cannot be wrong. 1085 // the table costs nothing and cannot be wrong.
1084 if (self.liveSessions() == 0) { 1086 if (self.liveSessions() == 0) {
1085 // Nested rather than one `and`: no live sessions and no code 1087 // Nested rather than one `and`: no live sessions and no code
1086 // means there were never any — unreachable after init, but this 1088 // means there were never any — unreachable after init, but this