a73x

7ec78a84

docs: the local/remote split of a restored wall line

a73x   2026-08-25 19:32

Commit message
docs: the local/remote split of a restored wall line

README says what a user sees after a reboot, decisions.md records the
ruling and why the wire needed nothing, and the wall-file invariant in
CLAUDE.md gains the rule a future change would otherwise revert silently.

CLAUDE.md
Old New
@@ -91,7 +91,11 @@ real pty), `wsclient` (browser stand-in), `rawmode`, `delaypipe`, `render`.
91 `client.recordOnState` fires when the daemon's first state arrives and never 91 `client.recordOnState` fires when the daemon's first state arrives and never
92 on a successful dial, so a refused attach cannot strand a line — which is why 92 on a successful dial, so a refused attach cannot strand a line — which is why
93 there is no `unrecordTile`. `--via` and `muxa` record nothing; `mux wall`'s 93 there is no `unrecordTile`. `--via` and `muxa` record nothing; `mux wall`'s
94 argv is a view, not an attach. 94 argv is a view, not an attach. A RESTORED line attaches-or-creates when it
95 names the local socket and joins only when it is remote
96 (`wallview.hydratedCreates`): a daemon restart must not leave the user's own
97 wall a grid of `[refused]`, and a saved file must not spawn a shell on
98 another host.
95 - **The layout sidecar is derived convenience, not authored intent.** 99 - **The layout sidecar is derived convenience, not authored intent.**
96 `$XDG_STATE_HOME/mux/layout` stores the pane tree on last detach, hydrated 100 `$XDG_STATE_HOME/mux/layout` stores the pane tree on last detach, hydrated
97 walls only (no-argv `mux wall` or post-fold; argv walls and `mux TARGET` 101 walls only (no-argv `mux wall` or post-fold; argv walls and `mux TARGET`
README.md
Old New
@@ -309,6 +309,16 @@ That file is what `mux wall` opens on, and what `Ctrl-\` `w` folds in
309 one-shot — the saved wall with the tile you are standing on among them. 309 one-shot — the saved wall with the tile you are standing on among them.
310 There is one wall, and no way to end up looking at a second one by accident. 310 There is one wall, and no way to end up looking at a second one by accident.
311 311
312 A daemon restart takes every session the file names with it. A line on a
313 local socket (`--sock PATH#NAME`) comes back as a fresh session rather than
314 `[refused]`: recreating a shell on your own box is cheap, and a wall you have
315 to forget line by line after every reboot is not what attach history is for.
316 A remote line (`HOST#NAME`, `quic://HOST#NAME`) is joined only, and a refused
317 tile stays refused for as long as the wall is up: it says `[refused]` until you
318 reopen the wall with that session already there — a saved file must not spawn
319 shells on a machine you are not looking at. Spellings on `mux wall`'s own command line
320 are a view, so they join and never create.
321
312 `mux wall add`/`rm` are the same edit without an attach: file operations 322 `mux wall add`/`rm` are the same edit without an attach: file operations
313 only, so neither dials, and `rm` never touches the session. A wall or hub 323 only, so neither dials, and `rm` never touches the session. A wall or hub
314 that is already running does not notice the change until you reopen it. 324 that is already running does not notice the change until you reopen it.
docs/decisions.md
Old New
@@ -6714,3 +6714,44 @@ client only when its attach frame arrives, so simultaneous dials contend for
6714 `max_observers` (4), not `max_clients` (8) — 7 at once lost 11 of 31 fills to a 6714 `max_observers` (4), not `max_clients` (8) — 7 at once lost 11 of 31 fills to a
6715 closed connection, 3 at once lost none. `muxd stop` on a full table of `/bin/sh` 6715 closed connection, 3 at once lost none. `muxd stop` on a full table of `/bin/sh`
6716 took 51ms, which is the grace-cost claim above, measured. 6716 took 51ms, which is the grace-cost claim above, measured.
6717
6718 ## 2026-08-25 — a saved local line attaches-or-creates
6719
6720 `mux wall` with no argv reads `$XDG_STATE_HOME/mux/wall` and puts up a tile per
6721 line. Until now every one of those tiles attached at 0×0, which the daemon reads
6722 as JOIN — so the wall could only ever show sessions that already existed. The
6723 daemon that held them dies on every reboot, and the wall that came back was a
6724 grid of `[refused]` stripes the user had to forget by hand, one `Ctrl-\ x` at a
6725 time. The marker fix (bf18c8d) is what made that visible rather than merely
6726 true: the focus marker now moves across tiles with no pump, so a wall of dead
6727 tiles is something you sit in and step around instead of something you leave.
6728
6729 Ruled: a wall-file line whose target is the LOCAL daemon (`--sock PATH[#NAME]`)
6730 attaches-or-creates; a remote line (`HOST`, `quic://…`) still joins only.
6731 Recreating a shell on your own box is cheap and expected — it is the workspace
6732 the file is a record of. Spawning one on another host out of a file the user
6733 last edited by attaching once is not: the saved line is history, not standing
6734 permission to run something over there.
6735
6736 One consequence worth stating, because nothing else says it: a wall tile
6737 redials for as long as the wall is up, and a redial carries the tile's
6738 `creates`. So a restored local tile whose daemon is restarted UNDER a running
6739 wall comes back as a fresh session on its next redial rather than landing
6740 `[refused]` — the same ruling, one step further along. It does not auto-start a
6741 daemon: a pump only dials, and auto-start lives in `mux TARGET`'s path alone, so
6742 a saved line whose socket is gone still sits reconnecting exactly as before.
6743
6744 Two exceptions stay as they were. `mux wall SPELLING...` is a view rather than
6745 an attach, so an argv wall joins only whatever it names — the same reason argv
6746 walls record nothing and save no layout sidecar. And the `Ctrl-\ w` fold reads
6747 the same file through the same rule, because a fold IS the saved wall arriving
6748 late.
6749
6750 The wire needed nothing. Create-vs-join was already encoded in the attach's size
6751 claim (`server.resolveSession`), so the change is one predicate on the client
6752 side — `wallview.hydratedCreates`, used by the fold row of the birth table and
6753 by the startup tile loop — deciding whether `sendAttach` puts the tile's rect or
6754 0×0 on the frame. A tile that flips to creating and finds the session ALREADY
6755 there is unaffected either way: it joins, and its rect arrives in the attach
6756 instead of in the resize doorbell one frame later. Nothing about a cross-version
6757 attach changes: an old daemon reads the same size claim.
src/server.zig
Old New
@@ -967,7 +967,7 @@ pub const Server = struct {
967 967
968 /// Attach-or-create. Creation demands a size the session can live at, the 968 /// Attach-or-create. Creation demands a size the session can live at, the
969 /// SAME threshold `applySize` enforces. A 0x0 attach makes no size claim 969 /// SAME threshold `applySize` enforces. A 0x0 attach makes no size claim
970 /// at all — muxa, the wall's stripes, an unzoomed browser tile — and a 970 /// at all — muxa, the wall's view stripes, an unzoomed browser tile — and a
971 /// client with no size must never be the reason a shell spawns. 971 /// client with no size must never be the reason a shell spawns.
972 /// 972 ///
973 /// 1x1 is a size a client genuinely sends, so gating on merely nonzero 973 /// 1x1 is a size a client genuinely sends, so gating on merely nonzero