d88f3df5
docs: README and CLAUDE.md catch up — the wall is the product now
a73x 2026-08-20 08:26
Commit message
CLAUDE.md
| Old | New | ||
|---|---|---|---|
| @@ -20,10 +20,11 @@ First build after a clean checkout fetches ~30MB of QUIC deps (minutes, once). | |||
| 20 | Files are large and comment-dense (~44% of Zig bytes are `//`). Reading the repo | 20 | Files are large and comment-dense (~44% of Zig bytes are `//`). Reading the repo |
| 21 | costs ~800k tokens; every token stays in context and is re-billed each turn. | 21 | costs ~800k tokens; every token stays in context and is re-billed each turn. |
| 22 | 22 | ||
| 23 | - **Never `cat` these:** `src/server.zig` (9.1k lines, ~110k tok), | 23 | - **Never `cat` these:** `src/server.zig` (9.6k lines, ~110k tok), |
| 24 | `test/e2e.sh` (4.2k), `docs/decisions.md` (3.7k), `src/client.zig` (2.8k), | 24 | `test/e2e.sh` (7.0k), `docs/decisions.md` (4.7k), `src/wallview.zig` (3.0k), |
| 25 | `src/interact.zig` (2.2k). | 25 | `src/interact.zig` (2.9k). |
| 26 | Use `grep -n` for the symbol, then `sed -n 'A,Bp'` for a window. | 26 | Use `grep -n` for the symbol, then `sed -n 'A,Bp'` for a window. |
| 27 | `src/client.zig` is 1.9k now — cheap enough to read. | ||
| 27 | - Every module has a `//!` header stating its contract. `head -12 src/X.zig` | 28 | - Every module has a `//!` header stating its contract. `head -12 src/X.zig` |
| 28 | answers most "what is this" questions for ~200 tokens. | 29 | answers most "what is this" questions for ~200 tokens. |
| 29 | - Pipe Bash output: `| tail -30`, `2>/dev/null`, `grep -c`. `make test` full | 30 | - Pipe Bash output: `| tail -30`, `2>/dev/null`, `grep -c`. `make test` full |
| @@ -37,7 +38,7 @@ Layers are enforced in `build.zig`'s module table (grep `.layer =` for the graph | |||
| 37 | | Layer | Modules | | 38 | | Layer | Modules | |
| 38 | |---|---| | 39 | |---|---| |
| 39 | | 0 | `protocol` `engine` `pty` `quic` `keymap` `xdg` `sockpath` `proxy` `testtmp` | | 40 | | 0 | `protocol` `engine` `pty` `quic` `keymap` `xdg` `sockpath` `proxy` `testtmp` | |
| 40 | | 1 | `quic_server` `quic_client` `predict` `spawn` `handoff` `delta` `cmd` `shellint` `replica` `paint` | | 41 | | 1 | `client_core` `quic_server` `quic_client` `predict` `spawn` `handoff` `delta` `cmd` `wall` `shellint` `replica` `paint` | |
| 41 | | 2 | `server` `muxa` `interact` | | 42 | | 2 | `server` `muxa` `interact` | |
| 42 | | 3 | `client` `main`(muxd) | | 43 | | 3 | `client` `main`(muxd) | |
| 43 | | 4 | `webhub` `wallview` | | 44 | | 4 | `webhub` `wallview` | |
| @@ -65,6 +66,11 @@ real pty), `wsclient` (browser stand-in), `rawmode`, `delaypipe`, `render`. | |||
| 65 | falls back to `pgid`/`settle` and there is no real exit code. Every `muxa` | 66 | falls back to `pgid`/`settle` and there is no real exit code. Every `muxa` |
| 66 | reply names the `mechanism` that answered it. | 67 | reply names the `mechanism` that answered it. |
| 67 | - **OSC 52 clipboard READ is refused deliberately.** Not a gap. | 68 | - **OSC 52 clipboard READ is refused deliberately.** Not a gap. |
| 69 | - **The wall file is attach history, written on first state.** | ||
| 70 | `client.recordOnState` fires when the daemon's first state arrives and never | ||
| 71 | on a successful dial, so a refused attach cannot strand a line — which is why | ||
| 72 | there is no `unrecordTile`. `--via` and `muxa` record nothing; `mux wall`'s | ||
| 73 | argv is a view, not an attach. | ||
| 68 | - No socket stealing: `muxd run` refuses a path another daemon owns. | 74 | - No socket stealing: `muxd run` refuses a path another daemon owns. |
| 69 | 75 | ||
| 70 | ## Working rules | 76 | ## Working rules |
| @@ -81,9 +87,10 @@ real pty), `wsclient` (browser stand-in), `rawmode`, `delaypipe`, `render`. | |||
| 81 | 87 | ||
| 82 | ## Where the answers live | 88 | ## Where the answers live |
| 83 | 89 | ||
| 84 | `docs/roadmap.md` (579 ln) what's next · `docs/decisions.md` (3.7k ln, grep only) | 90 | `docs/roadmap.md` (595 ln) the ranked queue, but stale past 2026-08-16 — |
| 85 | every decision + measurement · `docs/handoff.md` (199 ln) original design · | 91 | `git-collab issue list` is the live order · `docs/decisions.md` (4.7k ln, grep |
| 86 | `README.md` user-facing usage. | 92 | only) every decision + measurement · `docs/handoff.md` (199 ln) the original |
| 93 | design, historical · `README.md` user-facing usage. | ||
| 87 | 94 | ||
| 88 | ## Session hygiene | 95 | ## Session hygiene |
| 89 | 96 | ||
README.md
| Old | New | ||
|---|---|---|---|
| @@ -15,6 +15,7 @@ dependency). The Makefile points at a pinned toolchain path; override with | |||
| 15 | ```sh | 15 | ```sh |
| 16 | make build # first build fetches + compiles the QUIC deps: ~30MB, a few minutes, once | 16 | make build # first build fetches + compiles the QUIC deps: ~30MB, a few minutes, once |
| 17 | make test && make e2e # verify | 17 | make test && make e2e # verify |
| 18 | make install # the four binaries to ~/.local/bin (override BINDIR) | ||
| 18 | ``` | 19 | ``` |
| 19 | 20 | ||
| 20 | Binaries land in `zig-out/bin/`. For remote machines, build a static binary | 21 | Binaries land in `zig-out/bin/`. For remote machines, build a static binary |
| @@ -57,13 +58,15 @@ Zoomed out — the wall reads the keys and nothing reaches a session: | |||
| 57 | 58 | ||
| 58 | | Key | Effect | | 59 | | Key | Effect | |
| 59 | |---|---| | 60 | |---|---| |
| 60 | | `j` / `k`, `1`-`9` | select a tile | | 61 | | `j` / `k` (or `n` / `p`), `1`-`9` | select a tile | |
| 61 | | `Enter` | zoom the selected tile | | 62 | | `Enter` | zoom the selected tile | |
| 62 | | `x` | forget the selected tile (off the wall file; the session keeps running) | | 63 | | `x` | forget the selected tile (off the wall file; the session keeps running) | |
| 63 | | `q` (or `Ctrl-\`) | leave mux | | 64 | | `q` (or `Ctrl-\`) | leave mux | |
| 64 | 65 | ||
| 65 | `n`, `p` and `c` are about SESSIONS, not tiles: they walk the daemon's own | 66 | Behind the `Ctrl-\` prefix — which is to say, zoomed in — `n`, `p` and `c` |
| 66 | session ring. A session that already has a tile is an instant zoom move; one | 67 | are about SESSIONS, not tiles: they walk the daemon's own |
| 68 | session ring. (Bare `n`/`p` on the wall are just aliases of `j`/`k`, because | ||
| 69 | there is no session to leave.) A session that already has a tile is an instant zoom move; one | ||
| 67 | that does not gets a tile, because visiting it is attaching to it and every | 70 | that does not gets a tile, because visiting it is attaching to it and every |
| 68 | attach is recorded (see "the wall is your attach history" below). The wall's | 71 | attach is recorded (see "the wall is your attach history" below). The wall's |
| 69 | `j`/`k` and the digits are what walk tiles. | 72 | `j`/`k` and the digits are what walk tiles. |
| @@ -107,7 +110,8 @@ really want the loop. | |||
| 107 | 110 | ||
| 108 | ## Quick start, remote over ssh | 111 | ## Quick start, remote over ssh |
| 109 | 112 | ||
| 110 | Works anywhere ssh works. On the remote host: put `muxd` on PATH, then: | 113 | Works anywhere ssh works. On the remote host: put `muxd` on PATH (`make |
| 114 | install` puts it in `~/.local/bin`), then: | ||
| 111 | 115 | ||
| 112 | ```sh | 116 | ```sh |
| 113 | ssh HOST 'muxd start' | 117 | ssh HOST 'muxd start' |
| @@ -198,14 +202,24 @@ spelled literally `add` or `rm` has to reach the wall some other way (the | |||
| 198 | `Ctrl-\` chords, `muxweb`, or an editor on the file). Naming neither a | 202 | `Ctrl-\` chords, `muxweb`, or an editor on the file). Naming neither a |
| 199 | target nor a spelling is a usage error, not an empty edit. | 203 | target nor a spelling is a usage error, not an empty edit. |
| 200 | 204 | ||
| 201 | **The wall is your attach history.** Every `mux` attach writes its own tile | 205 | Unlike `muxweb`, `mux wall TILE...` does not add what you name to the file: |
| 206 | spellings on this command line are a view of some tiles for this run, not an | ||
| 207 | attach of yours, so they are gone when you leave. `mux wall add` is how you | ||
| 208 | make one stick. | ||
| 209 | |||
| 210 | **The wall is your attach history**, kept in `~/.local/state/mux/wall`. | ||
| 211 | Every `mux` attach writes its own tile | ||
| 202 | to that file, so a bare `mux` leaves `--sock <default>#0` behind and | 212 | to that file, so a bare `mux` leaves `--sock <default>#0` behind and |
| 203 | `mux HOST --session build` leaves `HOST#build` — as does every session you | 213 | `mux HOST --session build` leaves `HOST#build` — as does every session you |
| 204 | reach with `Ctrl-\` `c`/`n`/`p`, because visiting a session is attaching to | 214 | reach with `Ctrl-\` `c`/`n`/`p`, because visiting a session is attaching to |
| 205 | it. Tiles are deduped by spelling, byte for byte: the same session reached | 215 | it. Tiles are deduped by spelling, byte for byte: the same session reached |
| 206 | as `HOST#build` and as `quic://HOST#build` is two tiles, deliberately. | 216 | as `HOST#build` and as `quic://HOST#build` is two tiles, deliberately. |
| 207 | `muxa` never adds one (it attaches at 0×0 and claims nothing), and neither | 217 | `muxa` never adds one (it attaches at 0×0 and claims nothing); `--via` never |
| 208 | do the wall's own stripes. Writing the file is best effort — if it cannot be | 218 | adds one either, because an arbitrary command is not an address and the wall |
| 219 | grammar has no line to write for it — the session is real, it is just not | ||
| 220 | addressable as a tile, and `Ctrl-\` `w` labels it `--via CMD#NAME` on a bar | ||
| 221 | that `x` will find nothing in the file to forget; and neither do the wall's | ||
| 222 | own stripes. Writing the file is best effort — if it cannot be | ||
| 209 | written you get one warning line and the attach happens anyway. | 223 | written you get one warning line and the attach happens anyway. |
| 210 | 224 | ||
| 211 | That file is what `Ctrl-\` `w` shows you: zooming out from any session puts | 225 | That file is what `Ctrl-\` `w` shows you: zooming out from any session puts |
| @@ -227,9 +241,8 @@ you have recorded. Forgetting stays explicit: the page's `×`, the wall's | |||
| 227 | 241 | ||
| 228 | Watching is read-only: each stripe is a live session (label bar + the rows | 242 | Watching is read-only: each stripe is a live session (label bar + the rows |
| 229 | around its cursor), attached the way `muxa` attaches — no size claim, so | 243 | around its cursor), attached the way `muxa` attaches — no size claim, so |
| 230 | watching never resizes anyone. The stripes are cut once, from the terminal's | 244 | watching never resizes anyone. Resize the terminal and the stripes are |
| 231 | size when the wall opened, so resizing that terminal while the wall is up | 245 | re-cut to it. |
| 232 | misplaces them until you leave and reopen it. | ||
| 233 | 246 | ||
| 234 | The keys are the ones listed under "Quick start, local" — `mux wall` and | 247 | The keys are the ones listed under "Quick start, local" — `mux wall` and |
| 235 | `mux` are the same program, entered through different doors, so there is | 248 | `mux` are the same program, entered through different doors, so there is |
| @@ -250,8 +263,18 @@ reconnect, no snapshot, no flash. Zooming out stops the keystrokes and puts | |||
| 250 | the stripes back; the session keeps the grid the zoom claimed, so nothing | 263 | the stripes back; the session keeps the grid the zoom claimed, so nothing |
| 251 | reflows when you look away. | 264 | reflows when you look away. |
| 252 | 265 | ||
| 253 | Typed input at a zoomed tile is predicted locally, as in a plain client. | 266 | Typed input at a zoomed tile is predicted locally, and the wheel and the |
| 254 | The mouse is not: a zoomed tile is keyboard-only for now. | 267 | mouse work exactly as they do in a plain client, because a zoomed tile *is* |
| 268 | a plain client's loop. Unzoomed, the wall asks its own terminal for no | ||
| 269 | mouse mode at all, so nothing a stripe's session armed can report while you | ||
| 270 | are looking at the wall. | ||
| 271 | |||
| 272 | A tile whose host is not answering says `reconnecting` and keeps retrying, | ||
| 273 | forever and on purpose — a wall is a thing you leave up, and a laptop that | ||
| 274 | wakes an hour later should find its sessions. The cost is that a tile | ||
| 275 | pointing at a daemon that is never coming back looks identical to one whose | ||
| 276 | network is merely down. Nothing times it out; `x`, or `mux wall rm | ||
| 277 | SPELLING`, is how a line leaves. | ||
| 255 | 278 | ||
| 256 | `x` forgets the selected tile: its line leaves the wall file, its stripe | 279 | `x` forgets the selected tile: its line leaves the wall file, its stripe |
| 257 | leaves the screen, and the remaining stripes are re-cut over the space. | 280 | leaves the screen, and the remaining stripes are re-cut over the space. |
| @@ -318,7 +341,7 @@ Design: `docs/handoff.md`. Every decision and measurement: | |||
| 318 | 341 | ||
| 319 | ## Status | 342 | ## Status |
| 320 | 343 | ||
| 321 | Prototype, nine milestones in, first trial cut tagged `v0.0.1-1`. All | 344 | Prototype, eighteen milestones in, current cut tagged `v0.0.1-10`. All |
| 322 | founding kill criteria cleared, measured on real networks rather than | 345 | founding kill criteria cleared, measured on real networks rather than |
| 323 | loopback: reattach into a live full-screen `nvim` in ~5ms from one | 346 | loopback: reattach into a live full-screen `nvim` in ~5ms from one |
| 324 | snapshot; steady-state delta traffic ~1% of snapshot-equivalent cost; the | 347 | snapshot; steady-state delta traffic ~1% of snapshot-equivalent cost; the |
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -23,8 +23,8 @@ MUXA="$9" | |||
| 23 | 23 | ||
| 24 | # The one thing this suite needs that it does not build: a real editor, for | 24 | # The one thing this suite needs that it does not build: a real editor, for |
| 25 | # the paste scenario far below. Checked HERE rather than there because there | 25 | # the paste scenario far below. Checked HERE rather than there because there |
| 26 | # is 25 scenarios, several daemons and a QUIC leg away — minutes of work to | 26 | # is the whole suite, several daemons and a QUIC leg away — minutes of work |
| 27 | # report a missing package. Required rather than guarded; that trade, and why | 27 | # to report a missing package. Required rather than guarded; that trade, and why |
| 28 | # a `command -v` skip would be worse than a hard failure, is argued at the | 28 | # a `command -v` skip would be worse than a hard failure, is argued at the |
| 29 | # scenario itself. | 29 | # scenario itself. |
| 30 | command -v nvim > /dev/null 2>&1 || { | 30 | command -v nvim > /dev/null 2>&1 || { |
| @@ -35,8 +35,8 @@ command -v nvim > /dev/null 2>&1 || { | |||
| 35 | # The same trade for the second thing this suite does not build: the hub's | 35 | # The same trade for the second thing this suite does not build: the hub's |
| 36 | # HTTP verbs are asserted through curl, and a `command -v` SKIP around them | 36 | # HTTP verbs are asserted through curl, and a `command -v` SKIP around them |
| 37 | # would let a box quietly not test the wall's whole mutation surface and | 37 | # would let a box quietly not test the wall's whole mutation surface and |
| 38 | # still print a green line. Required, and said here rather than 4000 lines | 38 | # still print a green line. Required, and said here rather than far down the |
| 39 | # down, for the reason nvim is. | 39 | # file, for the reason nvim is. |
| 40 | command -v curl > /dev/null 2>&1 || { | 40 | command -v curl > /dev/null 2>&1 || { |
| 41 | echo "e2e FAIL: this suite needs curl (the dynamic-wall leg asserts on the" | 41 | echo "e2e FAIL: this suite needs curl (the dynamic-wall leg asserts on the" |
| 42 | echo " hub's POST/DELETE/PUT answers); install it, or lose every" | 42 | echo " hub's POST/DELETE/PUT answers); install it, or lose every" |