a73x

968eb544

fix: the refusal and the usage page say what a host line is under the new model

a73x   2026-09-03 05:20

Commit message
fix: the refusal and the usage page say what a host line is under the new model

hosts.reason(HasSession) told the user a '#' was refused "because the wall
lists daemons and shows every session they have" — the old model, and no
longer a reason for anything. It is the sentence a user reads when their
hosts file will not load, so it has to name the fix: a host line names a
daemon; the layout names sessions. Shorter than what it replaces, which
matters because it is rendered through the 96-byte notice buffer.

mux --help's wall paragraph went the same way: bare mux is the panes the
layout names, Ctrl-\ x takes a pane off the wall rather than ending a
session, and Ctrl-\ s is described as the two-level picker a session
actually joins the wall through.

Two pins moved with the string — hosts.zig's own parse test and
wall_test_host's addHost test both matched on the word "daemons"; they now
assert the sentence names a daemon and the layout, which is the claim.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

src/cli/mux_main.zig
Old New
@@ -44,28 +44,30 @@ const usage =
44 \\ whoever typed last is whose agent signs, and only while attached 44 \\ whoever typed last is whose agent signs, and only while attached
45 \\ --version prints the version, --help prints this 45 \\ --version prints the version, --help prints this
46 \\ 46 \\
47 \\ mux the wall: every session every listed daemon has live 47 \\ mux the wall: the panes your layout file names
48 \\ mux -A the same wall, entered on the local session with the 48 \\ mux -A the same wall, entered on the local session with the
49 \\ agent armed — bare `mux` carries no agent 49 \\ agent armed — bare `mux` carries no agent
50 \\ mux hosts list the daemons on the wall, with their live session counts 50 \\ mux hosts list the daemons you can browse, with their live session counts
51 \\ mux hosts add SPELLING put a daemon on the wall without opening it 51 \\ mux hosts add SPELLING record a daemon without opening it
52 \\ mux hosts rm SPELLING take one off (its sessions keep running) 52 \\ mux hosts rm SPELLING take one off (its sessions keep running)
53 \\ 53 \\
54 \\ SPELLING names a DAEMON — HOST, quic://HOST[:PORT], or --sock PATH 54 \\ SPELLING names a DAEMON — HOST, quic://HOST[:PORT], or --sock PATH
55 \\ (one argument, or two as in `mux web`) — and never a session: `#NAME` 55 \\ (one argument, or two as in `mux web`) — and never a session: `#NAME`
56 \\ is refused, because the wall shows every session a daemon has. 56 \\ is refused, because a host line names a machine and the layout file
57 \\ is what names sessions.
57 \\ 58 \\
58 \\ On the wall, `Ctrl-\ 1-9` focuses a tile and types into it, 59 \\ On the wall, `Ctrl-\ 1-9` focuses a pane and types into it,
59 \\ `Ctrl-\ n/p` walk the tiles, `Ctrl-\ h/j/k/l` moves between panes, 60 \\ `Ctrl-\ n/p` walk the panes, `Ctrl-\ h/j/k/l` moves between them,
60 \\ `Ctrl-\ c` and `Ctrl-\ |/-` create a session on the focused tile's 61 \\ `Ctrl-\ c` and `Ctrl-\ |/-` create a session on the focused pane's
61 \\ daemon, `Ctrl-\ x` ends the focused session (twice when others are 62 \\ daemon, `Ctrl-\ x` takes the focused pane off the wall (the session
62 \\ attached), `Ctrl-\ f` fullscreen, `Ctrl-\ r` resize mode, 63 \\ keeps running), `Ctrl-\ f` fullscreen, `Ctrl-\ r` resize mode,
63 \\ `Ctrl-\ w` zooms out, `Ctrl-\ d` leaves. 64 \\ `Ctrl-\ w` zooms out, `Ctrl-\ d` leaves.
64 \\ 65 \\
65 \\ `Ctrl-\ s` opens the host picker: every DAEMON on the wall with what 66 \\ `Ctrl-\ s` opens the picker, which is how a session joins the wall:
66 \\ its last poll said. j/k or the arrows move, 1-9 pick a row, Enter or 67 \\ every DAEMON with what its last poll said, Enter for that daemon's
67 \\ c starts a session on the selected machine, x forgets it (the 68 \\ sessions, Enter again to add one as a pane. j/k or the arrows move,
68 \\ sessions keep running), a adds one by spelling, Esc closes. 69 \\ 1-9 pick a row, c starts a new session, x forgets a host or ends a
70 \\ session, a adds a host by spelling, Esc backs out.
69 \\ 71 \\
70 ; 72 ;
71 73
@@ -858,7 +860,7 @@ test "hosts list: a line longer than the row buffer is still shown, because rm m
858 try std.testing.expect(std.mem.endsWith( 860 try std.testing.expect(std.mem.endsWith(
859 u8, 861 u8,
860 text, 862 text,
861 "\t[bad host: names a session after '#': the wall lists daemons and shows every session they have]\n", 863 "\t[bad host: names a session after '#': a host line names a daemon; the layout names sessions]\n",
862 )); 864 ));
863 } 865 }
864 866
src/client/hosts.zig
Old New
@@ -89,7 +89,7 @@ pub fn isParse(err: anyerror) bool {
89 89
90 pub fn reason(err: anyerror) []const u8 { 90 pub fn reason(err: anyerror) []const u8 {
91 return switch (err) { 91 return switch (err) {
92 error.HasSession => "names a session after '#': the wall lists daemons and shows every session they have", 92 error.HasSession => "names a session after '#': a host line names a daemon; the layout names sessions",
93 error.EmptySpec => "empty host", 93 error.EmptySpec => "empty host",
94 error.BadByte => "control byte in host", 94 error.BadByte => "control byte in host",
95 error.BadSpelling => "punctuation in host: a host line names a machine, not a command", 95 error.BadSpelling => "punctuation in host: a host line names a machine, not a command",
@@ -306,7 +306,10 @@ test "hosts.parse: three spellings classify; a '#' is refused by name" {
306 try std.testing.expectError(error.EmptySpec, parse("")); 306 try std.testing.expectError(error.EmptySpec, parse(""));
307 try std.testing.expectError(error.EmptySpec, parse("--sock ")); 307 try std.testing.expectError(error.EmptySpec, parse("--sock "));
308 try std.testing.expectError(error.BadByte, parse("bo\x01x")); 308 try std.testing.expectError(error.BadByte, parse("bo\x01x"));
309 try std.testing.expect(std.mem.indexOf(u8, reason(error.HasSession), "daemons") != null); 309 // The sentence has to say what a host line IS, not merely that this one
310 // is wrong: "names a session" alone leaves the user with no next move.
311 try std.testing.expect(std.mem.indexOf(u8, reason(error.HasSession), "daemon") != null);
312 try std.testing.expect(std.mem.indexOf(u8, reason(error.HasSession), "layout") != null);
310 } 313 }
311 314
312 test "hosts.parse: a HOST spelling is one word — the file's grammar is one host per line" { 315 test "hosts.parse: a HOST spelling is one word — the file's grammar is one host per line" {
src/tui/wall_test_host.zig
Old New
@@ -548,7 +548,7 @@ test "addHost: the prompt adds a DAEMON — a flag, a session and a host already
548 // daemon now, and the refusal says which half was one too many. 548 // daemon now, and the refusal says which half was one too many.
549 try std.testing.expect(wall_host.addHost(alloc, &shared, &table, &n, "--sock /c#work", null, 30_000, path) == .refused); 549 try std.testing.expect(wall_host.addHost(alloc, &shared, &table, &n, "--sock /c#work", null, 30_000, path) == .refused);
550 try std.testing.expectEqual(@as(usize, 2), n); 550 try std.testing.expectEqual(@as(usize, 2), n);
551 try std.testing.expect(std.mem.indexOf(u8, wv.takeNotice(&shared, &buf), "daemons") != null); 551 try std.testing.expect(std.mem.indexOf(u8, wv.takeNotice(&shared, &buf), "names a daemon") != null);
552 552
553 // A real one: it lands in the table AND in the file, because the wall 553 // A real one: it lands in the table AND in the file, because the wall
554 // the user is looking at and the wall they get back are the same wall. 554 // the user is looking at and the wall they get back are the same wall.