354981fc
docs: the wall is your hosts file, in the browser too
a73x 2026-08-29 12:14
Commit message
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. | ||