b81fd513
feat: the browser's word for a refused attach is 'refused'
a73x 2026-08-26 06:51
Commit message
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 | } |