a73x

354981fc

docs: the wall is your hosts file, in the browser too

a73x   2026-08-29 12:14

Commit message
docs: the wall is your hosts file, in the browser too

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017wi2HnuF1EK8HgViU11YLV

CLAUDE.md
Old New
@@ -51,7 +51,7 @@ engine and a client can link those folders and paint its own way:
51 |---|---| 51 |---|---|
52 | `src/engine/` | `protocol` `engine` `delta` `replica` `predict` | 52 | `src/engine/` | `protocol` `engine` `delta` `replica` `predict` |
53 | `src/server/` | `server`(+`_agent` `_sessions` `_test_*`) `pty` `quic_server` `cmd` `shellint` `upgrade` | 53 | `src/server/` | `server`(+`_agent` `_sessions` `_test_*`) `pty` `quic_server` `cmd` `shellint` `upgrade` |
54 | `src/client/` | `client` `client_core` `hosts` `handoff` `layout` `wall` `webhub` `keymap` `wasm_core` | 54 | `src/client/` | `client` `client_core` `hosts` `handoff` `layout` `webhub` `keymap` `wasm_core` |
55 | `src/tui/` | `wallview`(+`wall_host` `wall_picker` `wall_pump` `wall_layout` `wall_test_*`) `interact` `paint` `select` | 55 | `src/tui/` | `wallview`(+`wall_host` `wall_picker` `wall_pump` `wall_layout` `wall_test_*`) `interact` `paint` `select` |
56 | `src/cli/` | `mux`(dispatch) `main`(daemon) `mux_main`(client) `muxa`(agent) `webhub_main`(hub) `flags` `spawn` | 56 | `src/cli/` | `mux`(dispatch) `main`(daemon) `mux_main`(client) `muxa`(agent) `webhub_main`(hub) `flags` `spawn` |
57 | `src/` | `xdg` `sockpath` `proxy` `quic` `testtmp` — what both sides link | 57 | `src/` | `xdg` `sockpath` `proxy` `quic` `testtmp` — what both sides link |
@@ -77,8 +77,8 @@ Layers are enforced in the same module table (grep `.layer =` for the graph).
77 | Layer | Modules | 77 | Layer | Modules |
78 |---|---| 78 |---|---|
79 | 0 | `protocol` `engine` `pty` `quic` `keymap` `xdg` `sockpath` `proxy` `cliflags` `testtmp` | 79 | 0 | `protocol` `engine` `pty` `quic` `keymap` `xdg` `sockpath` `proxy` `cliflags` `testtmp` |
80 | 1 | `client_core` `quic_server` `predict` `spawn`(cli) `handoff` `delta` `cmd` `wall` `shellint` `replica` `paint` `layout` `select` `upgrade` | 80 | 1 | `client_core` `quic_server` `predict` `spawn`(cli) `handoff` `delta` `cmd` `hosts` `shellint` `replica` `paint` `layout` `select` `upgrade` |
81 | 2 | `server` `agent_main` `interact` `hosts` | 81 | 2 | `server` `agent_main` `interact` |
82 | 3 | `client` `daemon_main` | 82 | 3 | `client` `daemon_main` |
83 | 4 | `webhub` `wallview` | 83 | 4 | `webhub` `wallview` |
84 | 5 | `client_main` `hub_main` | 84 | 5 | `client_main` `hub_main` |
@@ -143,9 +143,12 @@ paths (`src/cli/main.zig` is the daemon). Test fixtures in `test/`:
143 now has, which for a fresh daemon is session `0`. 143 now has, which for a fresh daemon is session `0`.
144 `hosts.load` is strict — a bad line refuses `mux` with rc 2 and prints 144 `hosts.load` is strict — a bad line refuses `mux` with rc 2 and prints
145 it — while `mux hosts` lists and `mux hosts rm` edits VERBATIM, so the 145 it — while `mux hosts` lists and `mux hosts rm` edits VERBATIM, so the
146 repair tool can read what the loader will not. The hub is phase 2: it 146 repair tool can read what the loader will not. The BROWSER hub is the
147 still reads the old `wall` file (`client.hydratedCreates`, the `ended` 147 same wall: `mux web [HOST ...]` records its argv into the file and then
148 latch), which nothing on the CLI writes any more. 148 serves the file, one `client.SessionPoll` per daemon, `/tiles` in host
149 order then daemon order. Ids are birth order and are never reused. The
150 page authors nothing — its `+` births on that tile's daemon, and POST
151 /tiles, PUT and DELETE answer 405. The old `wall` file is retired.
149 - **Hosts live in the picker, not on the wall.** `Ctrl-\ s` is a MODE of 152 - **Hosts live in the picker, not on the wall.** `Ctrl-\ s` is a MODE of
150 `interact.PrefixFilter` (`picking`), like the spelling editor it now 153 `interact.PrefixFilter` (`picking`), like the spelling editor it now
151 contains: every byte is the popup's, so a key can never reach a session, 154 contains: every byte is the popup's, so a key can never reach a session,
README.md
Old New
@@ -301,38 +301,30 @@ there is no unauthenticated mode to fall back to.
301 ## Quick start, a wall of sessions in a browser 301 ## Quick start, a wall of sessions in a browser
302 302
303 ```sh 303 ```sh
304 mux web # restores the wall you left; opens on 127.0.0.1:7681 304 mux web # serves your hosts file on 127.0.0.1:7681
305 mux web HOST quic://OTHER --sock /tmp/s.sock#b # ...or state it: shows these, ADDS them to the saved wall 305 mux web HOST quic://OTHER --sock /tmp/s.sock # ...adds those daemons first, then serves
306 ``` 306 ```
307 307
308 Each tile is a live session, dialed the way `mux` dials it — the same 308 **The wall is your hosts file, in the browser exactly as in the terminal.**
309 `HOST`, `quic://HOST[:PORT]` and `--sock PATH` spellings, with `#SESSION` 309 Each line of `$XDG_STATE_HOME/mux/hosts` is a DAEMON — `HOST`,
310 naming the session on that daemon. Click a tile to focus it and type in it; 310 `quic://HOST[:PORT]` or `--sock PATH` — and the tiles are whatever those
311 click the shade to come back. The wall itself is editable from the page: the 311 daemons have live, polled once a second. A session started anywhere, by
312 box adds a tile, `×` removes one (which detaches — the session and 312 anyone, appears; one that ends disappears. A `HOST` on the command line is
313 everything in it keep running), `+` starts a new session on that tile's host, 313 added to the file (the same thing `mux HOST` does) and then the file is the
314 and tiles drag into whatever order you want. The result is saved to 314 wall; `#SESSION` is refused, because a host line may not name something that
315 `$XDG_STATE_HOME/mux/wall` (`~/.local/state/mux/wall`), one spelling per 315 could be resurrected.
316 line, so the next bare `mux web` comes back to the same wall. That saved 316
317 spelling — `--sock PATH` in one piece — is also accepted on the command 317 Click a tile to focus it and type in it; click the shade to come back. `+`
318 line, so a line from the file can be pasted back verbatim. 318 on a tile starts a new session on that tile's daemon and drops you into it.
319 319 That is the whole of what the page can do to the wall: adding and removing
320 A daemon restart is survived here, and this is where the hub still differs 320 DAEMONS is `mux hosts add|rm` or the CLI picker's `a`/`x`, and ending a
321 from the terminal: a saved line on a local socket (`--sock PATH#NAME`) comes 321 session is `Ctrl-\ x` in the terminal or `exit` in the shell.
322 back as a fresh session, a remote one (`HOST#NAME`, `quic://HOST#NAME`) is
323 joined only and its tile reads `refused`. The CLI wall re-creates nothing at
324 all, because its file names no session to re-create.
325 322
326 Localhost only, deliberately: to see it from another machine, forward it — 323 Localhost only, deliberately: to see it from another machine, forward it —
327 `ssh -L 7681:127.0.0.1:7681 HOST`. 324 `ssh -L 7681:127.0.0.1:7681 HOST`.
328 325
329 **The hub and the CLI wall have parted company for now.** `mux web` still 326 An older `~/.local/state/mux/wall` is ignored and can be deleted; the hub
330 keeps its own list of *sessions* in `~/.local/state/mux/wall`, and nothing 327 kept its own list of sessions there until this version.
331 on the command line writes that file any more — so on a fresh machine the
332 hub opens empty until you name tiles on its command line or in the page, and
333 a host you add with `mux hosts add` does not appear there. Moving the hub
334 onto the `hosts` file below is the next piece of work, not a gap you can
335 configure around.
336 328
337 ## The wall is your hosts 329 ## The wall is your hosts
338 330
@@ -607,6 +599,5 @@ runs. Auto-start is here for local unix-socket attaches — `mux` and `mux
607 --sock PATH` start a daemon when nothing is serving that path — and the wall 599 --sock PATH` start a daemon when nothing is serving that path — and the wall
608 starts the one on the default socket for the same reason. A remote daemon 600 starts the one on the default socket for the same reason. A remote daemon
609 is started only by the dial a user ASKED for — `mux HOST`, a picker Enter — 601 is started only by the dial a user ASKED for — `mux HOST`, a picker Enter —
610 and never by a dial nobody asked for. Not yet here: the browser hub on the 602 and never by a dial nobody asked for. Not yet here: reconnect backoff
611 `hosts` file, reconnect backoff tuning, prediction for backspace and for 603 tuning, prediction for backspace and for multi-byte input.
612 multi-byte input.
docs/decisions.md
Old New
@@ -7483,3 +7483,24 @@ spells `d run` now, like every other exec here.
7483 7483
7484 `mux d upgrade` still translates a `version: output mismatch` from that 7484 `mux d upgrade` still translates a `version: output mismatch` from that
7485 daemon into the one-time `mux d stop` / `mux d start`. 7485 daemon into the one-time `mux d stop` / `mux d start`.
7486
7487 ## 2026-08-29 — the browser hub reads the hosts file; the `wall` file is retired
7488
7489 Two answers to "what is on the wall" — a CLI list of DAEMONS and a browser
7490 list of `TARGET#SESSION` spellings the page authored — meant the two fronts
7491 could disagree about a machine. `mux web [HOST ...]` now records its argv
7492 into `$XDG_STATE_HOME/mux/hosts` and serves that file: one
7493 `client.SessionPoll` per daemon, the same loop `wall_host.pollHost` runs, and
7494 a tile is a live session on a listed daemon. `wall.zig` is gone; its grammar,
7495 its argv collector and the one atomic writer are `hosts.zig`'s.
7496
7497 What the browser LOSES, deliberately: authoring. No add box, no `×`, no
7498 drag-to-reorder, no `#SESSION`; POST /tiles, PUT and DELETE answer 405, and
7499 daemons are managed by `mux hosts add|rm` or the picker. `+` still births on
7500 that tile's daemon. Two consecutive missing lists drop a tile; an
7501 unreachable answer drops nothing and clears nothing.
7502
7503 Found by the rewritten e2e leg: a `POST` carrying no body and no
7504 `content-length` — what `curl -X POST` sends — reached std's `discardBody`
7505 assert and ABORTED the hub, every tile with it. Any answer to a
7506 body-capable request that declared no length now closes its connection.