a73x

b81fd513

feat: the browser's word for a refused attach is 'refused'

a73x   2026-08-26 06:51

Commit message
feat: the browser's word for a refused attach is 'refused'

'session full' was a guess at WHY, and the daemon never says why. It was
wrong most of the time — a missing session refuses with the same
exit_status a full table does.

web/index.html
Old New
@@ -38,7 +38,7 @@
38 .badge.connecting, .badge.reconnecting { background: #4a3b12; color: #e8c35a; } 38 .badge.connecting, .badge.reconnecting { background: #4a3b12; color: #e8c35a; }
39 .badge.up { background: #16351f; color: #6fce8a; } 39 .badge.up { background: #16351f; color: #6fce8a; }
40 /* .stuck: the tile gave up replaying and is sending nothing further. */ 40 /* .stuck: the tile gave up replaying and is sending nothing further. */
41 .badge.gone, .badge.exited, .badge.full, .badge.stuck { background: #3d1a1a; color: #e07a7a; } 41 .badge.gone, .badge.exited, .badge.refused, .badge.stuck { background: #3d1a1a; color: #e07a7a; }
42 .badge.scroll { background: #1a2c3d; color: #6ab0e0; } 42 .badge.scroll { background: #1a2c3d; color: #6ab0e0; }
43 /* mux.js sets width/height in CSS pixels and sizes the backing store to 43 /* mux.js sets width/height in CSS pixels and sizes the backing store to
44 that times devicePixelRatio, so glyphs rasterize at device resolution 44 that times devicePixelRatio, so glyphs rasterize at device resolution
web/mux.js
Old New
@@ -56,7 +56,7 @@ const nextBackoffMs = (prev) => (prev === 0 ? 200 : Math.min(prev * 2, 2000));
56 // alone (see setStatus). Everything else in the badge vocabulary — 56 // alone (see setStatus). Everything else in the badge vocabulary —
57 // connecting, up, reconnecting, gone — is narration ABOUT the link, 57 // connecting, up, reconnecting, gone — is narration ABOUT the link,
58 // from this socket's own lifecycle or from the hub's control messages. 58 // from this socket's own lifecycle or from the hub's control messages.
59 const TERMINAL = new Set(['stuck', 'exited', 'full']); 59 const TERMINAL = new Set(['stuck', 'exited', 'refused']);
60 60
61 // How many straight failures to OPEN before a tile reads 'gone' rather 61 // How many straight failures to OPEN before a tile reads 'gone' rather
62 // than 'reconnecting'. Four is where the schedule hits its cap 62 // than 'reconnecting'. Four is where the schedule hits its cap
@@ -474,7 +474,7 @@ class Tile {
474 // A genuinely new socket is a genuinely new chance: the hub may 474 // A genuinely new socket is a genuinely new chance: the hub may
475 // have been restarted onto a different session, so the frame this 475 // have been restarted onto a different session, so the frame this
476 // tile choked on may simply not exist any more, and the session 476 // tile choked on may simply not exist any more, and the session
477 // that was full may have a slot. Without this a tile that gave up 477 // that refused may exist by now. Without this a tile that gave up
478 // once would stay dead until the page reloaded. 478 // once would stay dead until the page reloaded.
479 // The hub narrates from here on: connecting → up. 479 // The hub narrates from here on: connecting → up.
480 this.revive('connecting', 'connecting'); 480 this.revive('connecting', 'connecting');
@@ -541,9 +541,9 @@ class Tile {
541 } 541 }
542 // A frame that applies cleanly is the recovery every terminal state was 542 // A frame that applies cleanly is the recovery every terminal state was
543 // waiting for — the session shrank and its snapshot fits again, the 543 // waiting for — the session shrank and its snapshot fits again, the
544 // full session had a slot by the time we re-attached — so it revives 544 // attach that was refused was admitted by the time we re-attached — so
545 // unconditionally. This is the clearing the old code spelled twice 545 // it revives unconditionally. This is the clearing the old code spelled
546 // (stuck→up here, full→up at the call site) and still missed once. 546 // twice (stuck→up here, refused→up at the call site) and still missed once.
547 replaySucceeded() { this.revive('up', 'up'); } 547 replaySucceeded() { this.revive('up', 'up'); }
548 548
549 // The core is unusable — re-init and re-attach from nothing. Everything 549 // The core is unusable — re-init and re-attach from nothing. Everything
@@ -747,8 +747,8 @@ class Tile {
747 // replica.zig's pinned subtlety, mirrored: a DELTA's arrival alone 747 // replica.zig's pinned subtlety, mirrored: a DELTA's arrival alone
748 // proves the attach was admitted — decodable or not — while a 748 // proves the attach was admitted — decodable or not — while a
749 // short snapshot proves nothing. Without this an undecodable 749 // short snapshot proves nothing. Without this an undecodable
750 // delta followed by exit_status read as "session full" instead of 750 // delta followed by exit_status read as a refusal instead of as
751 // as the shell exiting. 751 // the shell exiting.
752 if (type === MSG.delta) this.gotState = true; 752 if (type === MSG.delta) this.gotState = true;
753 if (!this.stage(payload)) { 753 if (!this.stage(payload)) {
754 // Over the core's 256 KiB staging cap. Silently returning left 754 // Over the core's 256 KiB staging cap. Silently returning left
@@ -778,9 +778,11 @@ class Tile {
778 case MSG.exit_status: { 778 case MSG.exit_status: {
779 this.cancelScrollRequest(); 779 this.cancelScrollRequest();
780 this.clearSelection(); 780 this.clearSelection();
781 // Before any state this is the daemon refusing the attach 781 // Before any state this is the daemon refusing the attach — the
782 // (session full) — the CLI's own discriminator, mirrored. 782 // CLI's own discriminator, mirrored, and its own word for it. WHY
783 if (!this.gotState) this.setStatus('full', 'session full'); 783 // it refused is the daemon's business and it does not say; a
784 // guess ("session full") was wrong more often than not.
785 if (!this.gotState) this.setStatus('refused', 'refused');
784 else this.setStatus('exited', `exited ${payload[0] ?? 0}`); 786 else this.setStatus('exited', `exited ${payload[0] ?? 0}`);
785 return; 787 return;
786 } 788 }