a73x

3827724d

refactor: layers count components, and the test order bands by touch

a73x   2026-08-30 13:19

Commit message
refactor: layers count components, and the test order bands by touch

The strata are four deep now because the components are: leaves at 0, the
daemon, the client and the agent at 1, the two fronts at 2, the dispatcher
at 3. The comptime check is the verifier — a mutation of the wall's rank
still fatals on `wall imports client`.

Test registration is banded by what a suite touches (no fd, then a pty or a
fixture binary, then the socket binders), seconds-long last inside a band:
a wedged step prints nothing, so what runs before it is the only catch.

build.zig
Old New
@@ -42,7 +42,7 @@ fn quicDeps(b: *std.Build, target: std.Build.ResolvedTarget) struct {
42 42
43 /// One wasm-side twin of a native module: same source, the wasm32 target, 43 /// One wasm-side twin of a native module: same source, the wasm32 target,
44 /// and ReleaseSmall — never `optimize`, because the artifact is embedded 44 /// and ReleaseSmall — never `optimize`, because the artifact is embedded
45 /// into muxweb and its Debug build is 3.7MB against ReleaseSmall's 345KB. 45 /// into the one binary and its Debug build is 3.7MB against ReleaseSmall's 345KB.
46 /// They differ only in path, and the optimize mode is the one field that 46 /// They differ only in path, and the optimize mode is the one field that
47 /// must not drift between them. 47 /// must not drift between them.
48 fn wasmMod(b: *std.Build, wasm_target: std.Build.ResolvedTarget, path: []const u8) *std.Build.Module { 48 fn wasmMod(b: *std.Build, wasm_target: std.Build.ResolvedTarget, path: []const u8) *std.Build.Module {
@@ -59,7 +59,7 @@ fn wasmMod(b: *std.Build, wasm_target: std.Build.ResolvedTarget, path: []const u
59 /// Linking an archive whose symbols nothing references pulls in no objects, 59 /// Linking an archive whose symbols nothing references pulls in no objects,
60 /// which is why this is safe to apply before any QUIC code exists. Measured 60 /// which is why this is safe to apply before any QUIC code exists. Measured
61 /// rather than assumed at 2b: `nm` finds **zero** ngtcp2 or wolfSSL symbols 61 /// rather than assumed at 2b: `nm` finds **zero** ngtcp2 or wolfSSL symbols
62 /// in the resulting muxd. The binary does grow by 80 bytes — deterministic 62 /// in the resulting binary. The binary does grow by 80 bytes — deterministic
63 /// across rebuilds, and it is link metadata, not code, since no object from 63 /// across rebuilds, and it is link metadata, not code, since no object from
64 /// those archives is present. Stated exactly because "unchanged" would have 64 /// those archives is present. Stated exactly because "unchanged" would have
65 /// been the easy sentence and it would have been false. 65 /// been the easy sentence and it would have been false.
@@ -87,11 +87,12 @@ fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
87 /// are impossible, not detected. (Dependency edges — ghostty — and 87 /// are impossible, not detected. (Dependency edges — ghostty — and
88 /// build_options are explicitly outside the table's jurisdiction and 88 /// build_options are explicitly outside the table's jurisdiction and
89 /// stay wired by hand.) Layers are the topological strata of the production 89 /// stay wired by hand.) Layers are the topological strata of the production
90 /// graph, computed 2026-08-14, re-stratified 2026-08-20 (the interact 90 /// graph, computed 2026-08-14 and re-stratified 2026-08-30, when the table
91 /// extraction pushed client to 3 and everything above it up one), and 91 /// became one row per owned COMPONENT rather than one per file: the strata
92 /// FROZEN: a new import that would flatten or invert a stratum fails at 92 /// are four deep because the components are, and a row's children no longer
93 /// comptime, and re-stratifying requires editing this table, which is 93 /// buy it a layer. FROZEN either way — a new import that would flatten or
94 /// the point. 94 /// invert a stratum fails at comptime, and re-stratifying requires editing
95 /// this table, which is the point.
95 const ModSpec = struct { 96 const ModSpec = struct {
96 name: []const u8, 97 name: []const u8,
97 path: []const u8, 98 path: []const u8,
@@ -105,7 +106,7 @@ const ModSpec = struct {
105 /// compile error, not a convention. Excluded from the strata computation. 106 /// compile error, not a convention. Excluded from the strata computation.
106 test_imports: []const []const u8 = &.{}, 107 test_imports: []const []const u8 = &.{},
107 link_libc: bool = false, 108 link_libc: bool = false,
108 /// Also instantiated against wasm32 (the muxweb core's twins). 109 /// Also instantiated against wasm32 (the browser core's twins).
109 wasm: bool = false, 110 wasm: bool = false,
110 /// This module's test binary needs the QUIC archives. 111 /// This module's test binary needs the QUIC archives.
111 quic_tests: bool = false, 112 quic_tests: bool = false,
@@ -159,13 +160,13 @@ const mod_table = [_]ModSpec{
159 // Reflection over a caller's options struct, so it imports nothing: the 160 // Reflection over a caller's options struct, so it imports nothing: the
160 // struct is the flag table and the parser learns it at comptime. 161 // struct is the flag table and the parser learns it at comptime.
161 .{ .name = "cliflags", .path = "src/cli/flags.zig", .layer = 0 }, 162 .{ .name = "cliflags", .path = "src/cli/flags.zig", .layer = 0 },
162 // ---- layer 1: single-hop over the leaves ----
163 // This image, as a path something can exec. Under src/cli/ because it 163 // This image, as a path something can exec. Under src/cli/ because it
164 // asks the OS about the process it is in — a question no headless 164 // asks the OS about the process it is in — a question no headless
165 // client may spell. 165 // client may spell.
166 .{ .name = "spawn", .path = "src/cli/spawn.zig", .layer = 1, .link_libc = true }, 166 .{ .name = "spawn", .path = "src/cli/spawn.zig", .layer = 0, .link_libc = true },
167 // ---- layer 1: single-hop over the leaves ----
167 // Replays a captured client stdout stream and prints the final grid in 168 // Replays a captured client stdout stream and prints the final grid in
168 // `muxd dump`'s formats — the client half of the M11 render-vs-dump 169 // `mux d dump`'s formats — the client half of the M11 render-vs-dump
169 // convergence check. Imports term so both sides of the diff go through 170 // convergence check. Imports term so both sides of the diff go through
170 // the same ghostty-vt and the same formatter. 171 // the same ghostty-vt and the same formatter.
171 .{ .name = "render", .path = "test/render.zig", .layer = 1, .imports = &.{"term"} }, 172 .{ .name = "render", .path = "test/render.zig", .layer = 1, .imports = &.{"term"} },
@@ -173,7 +174,6 @@ const mod_table = [_]ModSpec{
173 // from stdin (M12). Imports pty so the product's own module is the one 174 // from stdin (M12). Imports pty so the product's own module is the one
174 // under it. 175 // under it.
175 .{ .name = "ptyclient", .path = "test/ptyclient.zig", .layer = 1, .link_libc = true, .imports = &.{ "pty", "script" } }, 176 .{ .name = "ptyclient", .path = "test/ptyclient.zig", .layer = 1, .link_libc = true, .imports = &.{ "pty", "script" } },
176 // ---- layer 2 ----
177 // The command state machine, shell integration, the upgrade vocabulary 177 // The command state machine, shell integration, the upgrade vocabulary
178 // and the QUIC listener are CHILD FILES of this root rather than modules 178 // and the QUIC listener are CHILD FILES of this root rather than modules
179 // of their own, so nothing outside src/server/ can name one: a second 179 // of their own, so nothing outside src/server/ can name one: a second
@@ -185,7 +185,7 @@ const mod_table = [_]ModSpec{
185 // daemon itself when nobody handed it a --key — and for the shim 185 // daemon itself when nobody handed it a --key — and for the shim
186 // directory shell integration writes under the same 0700 policy. 186 // directory shell integration writes under the same 0700 policy.
187 // `pty` stays a row of its own: the ptyclient fixture consumes it. 187 // `pty` stays a row of its own: the ptyclient fixture consumes it.
188 .{ .name = "daemon", .path = "src/server/server.zig", .layer = 2, .link_libc = true, .imports = &.{ "term", "pty", "sockpath", "quic", "xdg", "proxy" }, .test_imports = &.{"testtmp"}, .quic_tests = true }, 188 .{ .name = "daemon", .path = "src/server/server.zig", .layer = 1, .link_libc = true, .imports = &.{ "term", "pty", "sockpath", "quic", "xdg", "proxy" }, .test_imports = &.{"testtmp"}, .quic_tests = true },
189 // The agent-facing client. It speaks frames and owns no terminal, which 189 // The agent-facing client. It speaks frames and owns no terminal, which
190 // is the whole point — it attaches at 0x0 and never claims the grid. 190 // is the whole point — it attaches at 0x0 and never claims the grid.
191 // The transport modules are the CLI client's, minus everything that 191 // The transport modules are the CLI client's, minus everything that
@@ -194,9 +194,8 @@ const mod_table = [_]ModSpec{
194 // wire contract and nothing else — no engine and no replica, muxa having 194 // wire contract and nothing else — no engine and no replica, muxa having
195 // nothing to draw: a fact of muxa.zig itself, which the one-row component 195 // nothing to draw: a fact of muxa.zig itself, which the one-row component
196 // no longer refuses on its behalf. 196 // no longer refuses on its behalf.
197 .{ .name = "agent", .path = "src/cli/muxa.zig", .layer = 2, .link_libc = true, .imports = &.{ "term", "sockpath", "quic", "xdg", "cliflags" }, .quic_tests = true }, 197 .{ .name = "agent", .path = "src/cli/muxa.zig", .layer = 1, .link_libc = true, .imports = &.{ "term", "sockpath", "quic", "xdg", "cliflags" }, .quic_tests = true },
198 .{ .name = "wsclient", .path = "test/wsclient.zig", .layer = 2, .link_libc = true, .imports = &.{ "term", "script" } }, 198 .{ .name = "wsclient", .path = "test/wsclient.zig", .layer = 1, .link_libc = true, .imports = &.{ "term", "script" } },
199 // ---- layer 3 ----
200 // Dialling, and what a chord means. The client is the only thing that 199 // Dialling, and what a chord means. The client is the only thing that
201 // predicts — the overlay is a local display decision and never becomes 200 // predicts — the overlay is a local display decision and never becomes
202 // state anybody else can see — but the predicting itself is interact's 201 // state anybody else can see — but the predicting itself is interact's
@@ -210,16 +209,16 @@ const mod_table = [_]ModSpec{
210 // sits here rather than in either front so the CLI wall and the browser 209 // sits here rather than in either front so the CLI wall and the browser
211 // hub resolve a host line the same way. Nothing here WRITES that file — 210 // hub resolve a host line the same way. Nothing here WRITES that file —
212 // `wall_host.recordHost` and `webhub_main` do. 211 // `wall_host.recordHost` and `webhub_main` do.
213 .{ .name = "client", .path = "src/client/client.zig", .layer = 3, .link_libc = true, .imports = &.{ "term", "quic", "xdg", "sockpath" }, .test_imports = &.{"testtmp"}, .quic_tests = true }, 212 .{ .name = "client", .path = "src/client/client.zig", .layer = 1, .link_libc = true, .imports = &.{ "term", "quic", "xdg", "sockpath" }, .test_imports = &.{"testtmp"}, .quic_tests = true },
214 // ---- layer 4 ---- 213 // ---- layer 2 ----
215 // The muxweb hub's HTTP/WebSocket decisions: Origin gate, route table, 214 // The browser hub's HTTP/WebSocket decisions: Origin gate, route table,
216 // WS endpoint naming. Assets are injected (the exe root @embedFiles 215 // WS endpoint naming. Assets are injected (the exe root @embedFiles
217 // them), so its tests build no artifacts. It stays a row rather than 216 // them), so its tests build no artifacts. It stays a row rather than
218 // becoming a child file of the dispatcher that @embedFiles for it: a 217 // becoming a child file of the dispatcher that @embedFiles for it: a
219 // module's root directory is the dirname of its root file, and 218 // module's root directory is the dirname of its root file, and
220 // `@import("../client/webhub.zig")` from src/cli is "import of file 219 // `@import("../client/webhub.zig")` from src/cli is "import of file
221 // outside module path" — a compiler rule, not a table choice. 220 // outside module path" — a compiler rule, not a table choice.
222 .{ .name = "webhub", .path = "src/client/webhub.zig", .layer = 4, .imports = &.{ "term", "client" }, .quic_tests = true }, 221 .{ .name = "webhub", .path = "src/client/webhub.zig", .layer = 2, .imports = &.{ "term", "client" }, .quic_tests = true },
223 // The CLI wall (`mux wall`): multiattach stripes in one terminal, one of 222 // The CLI wall (`mux wall`): multiattach stripes in one terminal, one of
224 // which can be ZOOMED — promoted to the terminal's size and typed 223 // which can be ZOOMED — promoted to the terminal's size and typed
225 // through. Same layer as webhub for the same reason — both sit on 224 // through. Same layer as webhub for the same reason — both sit on
@@ -229,11 +228,12 @@ const mod_table = [_]ModSpec{
229 // overlay are CHILD FILES of this root rather than modules of their own, 228 // overlay are CHILD FILES of this root rather than modules of their own,
230 // so nothing outside src/tui/ can name one: a second module claiming any 229 // so nothing outside src/tui/ can name one: a second module claiming any
231 // of those files is a file-in-multiple-modules compile error. `term` is 230 // of those files is a file-in-multiple-modules compile error. `term` is
232 // the children's as much as the root's: `term.replica` is what a painter 231 // the children's as much as the root's: `term.replica` is the keyboard
233 // and a keyboard loop want and the wall itself never spells; the decoder 232 // loop's alone, which the root never spells, and the painter takes
234 // and the key table they also want reach them through `client`'s seams. 233 // `term.engine` and `term.protocol`; the decoder and the key table they
235 .{ .name = "wall", .path = "src/tui/wallview.zig", .layer = 4, .link_libc = true, .imports = &.{ "term", "client", "proxy", "spawn" }, .test_imports = &.{"testtmp"}, .quic_tests = true }, 234 // also want reach them through `client`'s seams.
236 // ---- layer 6: the one binary ---- 235 .{ .name = "wall", .path = "src/tui/wallview.zig", .layer = 2, .link_libc = true, .imports = &.{ "term", "client", "proxy", "spawn" }, .test_imports = &.{"testtmp"}, .quic_tests = true },
236 // ---- layer 3: the one binary ----
237 // Four words, one image — and one row: the daemon's entrypoint, the 237 // Four words, one image — and one row: the daemon's entrypoint, the
238 // client's and the hub's are CHILD FILES of the dispatcher, so a second 238 // client's and the hub's are CHILD FILES of the dispatcher, so a second
239 // module claiming any of them is a file-in-multiple-modules compile 239 // module claiming any of them is a file-in-multiple-modules compile
@@ -251,7 +251,7 @@ const mod_table = [_]ModSpec{
251 // downstream must refuse) and `sockpath` for the sun_path bound every verb 251 // downstream must refuse) and `sockpath` for the sun_path bound every verb
252 // checks before acting on a path. `testtmp` is the keygen round-trip's: 252 // checks before acting on a path. `testtmp` is the keygen round-trip's:
253 // it needs a directory to generate into, which the daemon never touches. 253 // it needs a directory to generate into, which the daemon never touches.
254 .{ .name = "mux", .path = "src/cli/mux.zig", .layer = 6, .link_libc = true, .imports = &.{ "daemon", "client", "wall", "agent", "webhub", "term", "proxy", "quic", "xdg", "spawn", "sockpath", "cliflags" }, .test_imports = &.{"testtmp"}, .quic_tests = true }, 254 .{ .name = "mux", .path = "src/cli/mux.zig", .layer = 3, .link_libc = true, .imports = &.{ "daemon", "client", "wall", "agent", "webhub", "term", "proxy", "quic", "xdg", "spawn", "sockpath", "cliflags" }, .test_imports = &.{"testtmp"}, .quic_tests = true },
255 }; 255 };
256 256
257 /// Comptime row lookup. Every hand-written module name in this file goes 257 /// Comptime row lookup. Every hand-written module name in this file goes
@@ -270,7 +270,8 @@ fn layerOf(comptime name: []const u8) u8 {
270 270
271 comptime { 271 comptime {
272 // Both checks are O(edges x rows) name comparisons; the default 1000 272 // Both checks are O(edges x rows) name comparisons; the default 1000
273 // backwards branches does not cover a 32-row, 76-edge table. 273 // backwards branches does not cover a 21-row, 45-edge table (38
274 // production imports and 7 test-only ones).
274 @setEvalBranchQuota(20_000); 275 @setEvalBranchQuota(20_000);
275 for (mod_table) |m| { 276 for (mod_table) |m| {
276 for (m.imports) |dep| { 277 for (m.imports) |dep| {
@@ -711,7 +712,7 @@ fn zigFilesIn(b: *std.Build, sub: []const u8, paths: *std.ArrayList([]const u8))
711 /// 712 ///
712 /// The tool is a build tool, not part of the program, so it stays out of the 713 /// The tool is a build tool, not part of the program, so it stays out of the
713 /// module table: that table is the program's import graph, and a row there 714 /// module table: that table is the program's import graph, and a row there
714 /// would claim docscheck is something muxd links. 715 /// would claim docscheck is something `mux` links.
715 /// 716 ///
716 /// Nothing about this step can silently skip, which is the whole hazard with 717 /// Nothing about this step can silently skip, which is the whole hazard with
717 /// a gate: the tool is built from source in this repo, so "not installed" is a 718 /// a gate: the tool is built from source in this repo, so "not installed" is a
@@ -783,20 +784,23 @@ fn docGate(b: *std.Build, target: std.Build.ResolvedTarget, check_step: *std.Bui
783 } 784 }
784 } 785 }
785 786
786 /// Test registration order. Doctrine-laden and deliberately NOT derived 787 /// Test registration order — the order failures ARRIVE in, and deliberately
787 /// from the layers: term and sockpath run BEFORE daemon because their 788 /// NOT the layers'. A suite that waits on a socket can wedge, and a wedged
788 /// tests are seconds-long and socket-free, while a regression in either 789 /// step prints nothing at all; whatever runs before it is the only legible
789 /// can wedge a daemon test that waits on a client forever — and a wedged 790 /// catch. So the order is three bands by what a suite TOUCHES, cheapest
790 /// step prints nothing at all. Failing first is what makes the catch 791 /// touch first: no fd at all, then a pty or a fixture binary, then the
791 /// legible. script leads for the same reason: instant, allocation-only, 792 /// suites that bind a unix socket (the `testtmp` set) and can wait forever.
792 /// and both fixtures inherit its escape pins. `mux` runs last and carries 793 /// Inside a band the seconds-long one goes last — `pty` at 5s, `daemon` at
793 /// every argument parser but muxa's, its mains being child files — a test 794 /// 40s, everything else in the milliseconds (`zig build test --summary all`,
794 /// that is never built is not a test (decisions.md). 795 /// Debug, 2026-08-30). `script` leads its band because both fixtures inherit
796 /// its escape pins, and `mux` runs last of all: it carries every argument
797 /// parser but muxa's, its mains being child files — a test that is never
798 /// built is not a test (decisions.md).
795 const test_order = [_][]const u8{ 799 const test_order = [_][]const u8{
796 "script", "term", "pty", "webhub", "wall", 800 "script", "cliflags", "testtmp", "spawn", "quic",
797 "sockpath", "agent", "daemon", "client", "proxy", 801 "webhub", "agent", "term", "rawmode", "delaypipe",
798 "quic", "testtmp", "rawmode", "delaypipe", "xdg", 802 "render", "wsclient", "ptyclient", "pty", "sockpath",
799 "spawn", "render", "ptyclient", "wsclient", "cliflags", 803 "xdg", "proxy", "wall", "client", "daemon",
800 "mux", 804 "mux",
801 }; 805 };
802 806
@@ -957,9 +961,9 @@ pub fn build(b: *std.Build) void {
957 // ---- The wasm core (M-web Task 4) ---- 961 // ---- The wasm core (M-web Task 4) ----
958 // A SECOND resolved target: modules are target-bound, so the wasm-clean 962 // A SECOND resolved target: modules are target-bound, so the wasm-clean
959 // row — the term component — and the ghostty dependency are 963 // row — the term component — and the ghostty dependency are
960 // instantiated again against wasm32. Always 964 // instantiated again against wasm32. Always ReleaseSmall — the artifact
961 // ReleaseSmall — the artifact is @embedFile'd into muxweb, and its 965 // is @embedFile'd into the one binary, and its Debug build is 3.7MB
962 // Debug build is 3.7MB against ReleaseSmall's 345KB (spike-measured). 966 // against ReleaseSmall's 345KB (spike-measured).
963 // Safety checks are the price; the native test suite runs the same 967 // Safety checks are the price; the native test suite runs the same
964 // code checked. 968 // code checked.
965 const wasm_target = b.resolveTargetQuery(.{ 969 const wasm_target = b.resolveTargetQuery(.{
@@ -1012,7 +1016,7 @@ pub fn build(b: *std.Build) void {
1012 wasm_exe.rdynamic = true; 1016 wasm_exe.rdynamic = true;
1013 b.installArtifact(wasm_exe); 1017 b.installArtifact(wasm_exe);
1014 1018
1015 // ---- muxweb, the hub binary (M-web Task 7) ---- 1019 // ---- the hub's browser stand-in ----
1016 const wsclient_exe = b.addExecutable(.{ .name = "wsclient", .root_module = wsclient_mod }); 1020 const wsclient_exe = b.addExecutable(.{ .name = "wsclient", .root_module = wsclient_mod });
1017 wsclient_exe.use_llvm = true; 1021 wsclient_exe.use_llvm = true;
1018 wsclient_exe.use_lld = true; 1022 wsclient_exe.use_lld = true;
@@ -1039,7 +1043,7 @@ pub fn build(b: *std.Build) void {
1039 t.use_lld = true; 1043 t.use_lld = true;
1040 // quic_tests is also what makes `make test` build the QUIC deps on 1044 // quic_tests is also what makes `make test` build the QUIC deps on
1041 // a clean checkout — the dependency must reach the test binaries, 1045 // a clean checkout — the dependency must reach the test binaries,
1042 // not only muxd (decisions.md, M8). 1046 // not only the binary (decisions.md, M8).
1043 if (mod_table[i].quic_tests) linkQuic(b, t, quic); 1047 if (mod_table[i].quic_tests) linkQuic(b, t, quic);
1044 test_step.dependOn(&b.addRunArtifact(t).step); 1048 test_step.dependOn(&b.addRunArtifact(t).step);
1045 } 1049 }
@@ -1135,8 +1139,8 @@ pub fn build(b: *std.Build) void {
1135 // @embedFile of mux_core.wasm drags the wasm build in with it, so the 1139 // @embedFile of mux_core.wasm drags the wasm build in with it, so the
1136 // compile coverage this gate gives is every module in the table plus the 1140 // compile coverage this gate gives is every module in the table plus the
1137 // wasm core. What it does NOT cover is the executable wiring itself — 1141 // wasm core. What it does NOT cover is the executable wiring itself —
1138 // linking muxd/mux/muxa/muxweb, the QUIC archives included — which is 1142 // linking `mux` and the test fixtures, the QUIC archives included — which
1139 // `zig build` (and e2e, which runs the binaries). 1143 // is `zig build` (and e2e, which runs the binaries).
1140 const check_step = b.step("check", "fmt + unit tests + shell syntax — the pre-commit gate"); 1144 const check_step = b.step("check", "fmt + unit tests + shell syntax — the pre-commit gate");
1141 check_step.dependOn(fmt_step); 1145 check_step.dependOn(fmt_step);
1142 check_step.dependOn(test_step); 1146 check_step.dependOn(test_step);
src/cli/mux_main.zig
Old New
@@ -8,7 +8,7 @@
8 //! 8 //!
9 //! Bare `mux` is the WALL: every session every daemon on 9 //! Bare `mux` is the WALL: every session every daemon on
10 //! `$XDG_STATE_HOME/mux/hosts` has live. Naming a transport is the same 10 //! `$XDG_STATE_HOME/mux/hosts` has live. Naming a transport is the same
11 //! wall, entered zoomed on that daemon's session (`wallview.runAttach`). 11 //! wall, entered zoomed on that daemon's session (`wall.runAttach`).
12 //! What lives up here is argv, the refusals that must happen before a dial 12 //! What lives up here is argv, the refusals that must happen before a dial
13 //! (self-attach, an unbindable socket path), the auto-start, and the 13 //! (self-attach, an unbindable socket path), the auto-start, and the
14 //! `hosts` subcommand — everything about the command line rather than 14 //! `hosts` subcommand — everything about the command line rather than
@@ -21,7 +21,7 @@ const xdg = @import("xdg");
21 const spawn = @import("spawn"); 21 const spawn = @import("spawn");
22 const handoff = @import("client").handoff; 22 const handoff = @import("client").handoff;
23 const sockpath = @import("sockpath"); 23 const sockpath = @import("sockpath");
24 const wallview = @import("wall"); 24 const wall = @import("wall");
25 const hosts = @import("client").hosts; 25 const hosts = @import("client").hosts;
26 const cliflags = @import("cliflags"); 26 const cliflags = @import("cliflags");
27 const TmpDir = @import("testtmp").TmpDir; 27 const TmpDir = @import("testtmp").TmpDir;
@@ -317,7 +317,7 @@ pub fn main(args: []const [:0]const u8) !u8 {
317 return 2; 317 return 2;
318 }, 318 },
319 }; 319 };
320 return wallview.runAttach(alloc, .{ .quic = .{ 320 return wall.runAttach(alloc, .{ .quic = .{
321 .host_port = q.host_port, 321 .host_port = q.host_port,
322 .key_path = key_path, 322 .key_path = key_path,
323 .idle_ms = q.idle_ms, 323 .idle_ms = q.idle_ms,
@@ -341,10 +341,10 @@ pub fn main(args: []const [:0]const u8) !u8 {
341 // wall yet and no alternate screen to corrupt, and the user is 341 // wall yet and no alternate screen to corrupt, and the user is
342 // sitting in front of the wait those bytes describe. 342 // sitting in front of the wait those bytes describe.
343 target.narrate = true; 343 target.narrate = true;
344 return wallview.runAttach(alloc, .{ .hand = target }, h.session, null, h.idle_ms, h.agent); 344 return wall.runAttach(alloc, .{ .hand = target }, h.session, null, h.idle_ms, h.agent);
345 }, 345 },
346 .attach => |t| { 346 .attach => |t| {
347 if (t.via) |cmd| return wallview.runAttach( 347 if (t.via) |cmd| return wall.runAttach(
348 alloc, 348 alloc,
349 .{ .via = cmd }, 349 .{ .via = cmd },
350 t.session, 350 t.session,
@@ -385,8 +385,8 @@ fn attachLocal(
385 // USER's attach only — the Ctrl-\ chords grow their tiles from inside 385 // USER's attach only — the Ctrl-\ chords grow their tiles from inside
386 // the wall and never come back through here, so focusing from session 0 386 // the wall and never come back through here, so focusing from session 0
387 // to session 1 keeps working. The picker's `a` is the one chord that 387 // to session 1 keeps working. The picker's `a` is the one chord that
388 // takes a spelling, and it runs `wallview.showsSelf` itself. 388 // takes a spelling, and it runs `wall.showsSelf` itself.
389 if (wallview.showsSelf( 389 if (wall.showsSelf(
390 .{ .sock = sock_path }, 390 .{ .sock = sock_path },
391 session, 391 session,
392 std.posix.getenv(proto.sock_env), 392 std.posix.getenv(proto.sock_env),
@@ -397,7 +397,7 @@ fn attachLocal(
397 } 397 }
398 398
399 if (!sockpath.answers(sock_path) and !try startLocalDaemon(alloc, sock_path)) return 1; 399 if (!sockpath.answers(sock_path) and !try startLocalDaemon(alloc, sock_path)) return 1;
400 return wallview.runAttach( 400 return wall.runAttach(
401 alloc, 401 alloc,
402 .{ .sock = sock_path }, 402 .{ .sock = sock_path },
403 session, 403 session,
@@ -447,7 +447,7 @@ comptime {
447 /// `mux`: the wall of daemons. An empty file is the local one, so a first 447 /// `mux`: the wall of daemons. An empty file is the local one, so a first
448 /// run is still just a shell. 448 /// run is still just a shell.
449 /// 449 ///
450 /// Resolution allocates into an arena because `wallview.run` never returns 450 /// Resolution allocates into an arena because `wall.run` never returns
451 /// on the success path; only the early refusals come back through the 451 /// on the success path; only the early refusals come back through the
452 /// defers here. 452 /// defers here.
453 fn wallOfHosts(alloc: std.mem.Allocator) !u8 { 453 fn wallOfHosts(alloc: std.mem.Allocator) !u8 {
@@ -461,7 +461,7 @@ fn wallOfHosts(alloc: std.mem.Allocator) !u8 {
461 // The local daemon is a host like any other; what is special about 461 // The local daemon is a host like any other; what is special about
462 // it is only that `mux` reaches for it when nothing is listed. The 462 // it is only that `mux` reaches for it when nothing is listed. The
463 // line itself is written once the attach answers, by 463 // line itself is written once the attach answers, by
464 // `wallview.runAttach`. 464 // `wall.runAttach`.
465 const sock_path = try sockpath.defaultOrExplain(arena, "mux") orelse return 1; 465 const sock_path = try sockpath.defaultOrExplain(arena, "mux") orelse return 1;
466 return attachLocal(alloc, sock_path, "", false); 466 return attachLocal(alloc, sock_path, "", false);
467 } 467 }
@@ -478,18 +478,18 @@ fn wallOfHosts(alloc: std.mem.Allocator) !u8 {
478 } 478 }
479 479
480 const key = std.posix.getenv(xdg.key_env); 480 const key = std.posix.getenv(xdg.key_env);
481 const specs = try arena.alloc(wallview.HostSpec, h.lines.items.len); 481 const specs = try arena.alloc(wall.HostSpec, h.lines.items.len);
482 for (specs, h.lines.items) |*s, line| { 482 for (specs, h.lines.items) |*s, line| {
483 // Refused outright, unlike the same bad line under `mux HOST`: here 483 // Refused outright, unlike the same bad line under `mux HOST`: here
484 // the wall IS what was asked for, and a wall silently missing one of 484 // the wall IS what was asked for, and a wall silently missing one of
485 // the machines the user wrote down is the lie this whole file exists 485 // the machines the user wrote down is the lie this whole file exists
486 // to stop telling. 486 // to stop telling.
487 s.* = wallview.resolveHost(arena, line, key, client.quic_idle_ms_default) catch |err| { 487 s.* = wall.resolveHost(arena, line, key, client.quic_idle_ms_default) catch |err| {
488 std.debug.print("mux: bad host '{s}': {s}\n", .{ line, hosts.reason(err) }); 488 std.debug.print("mux: bad host '{s}': {s}\n", .{ line, hosts.reason(err) });
489 return 2; 489 return 2;
490 }; 490 };
491 } 491 }
492 return wallview.run(arena, specs, .{ .key = key }); 492 return wall.run(arena, specs, .{ .key = key });
493 } 493 }
494 494
495 /// What a count is worth waiting for. It bounds the DAEMON's answer, not 495 /// What a count is worth waiting for. It bounds the DAEMON's answer, not
@@ -536,7 +536,7 @@ fn hostsList(arena: std.mem.Allocator, path: []const u8, out_fd: std.posix.fd_t)
536 const lines = hosts.loadLines(arena, path) catch |err| return refuseFile(arena, "mux hosts", path, err); 536 const lines = hosts.loadLines(arena, path) catch |err| return refuseFile(arena, "mux hosts", path, err);
537 const key = std.posix.getenv(xdg.key_env); 537 const key = std.posix.getenv(xdg.key_env);
538 for (lines.items) |line| { 538 for (lines.items) |line| {
539 const spec = wallview.resolveHost(arena, line, key, client.quic_idle_ms_default) catch |err| { 539 const spec = wall.resolveHost(arena, line, key, client.quic_idle_ms_default) catch |err| {
540 printRow(out_fd, line, "\t[bad host: {s}]\n", .{hosts.reason(err)}); 540 printRow(out_fd, line, "\t[bad host: {s}]\n", .{hosts.reason(err)});
541 continue; 541 continue;
542 }; 542 };
src/client/client.zig
Old New
@@ -25,10 +25,10 @@ const sockpath = @import("sockpath");
25 pub const hosts = @import("hosts.zig"); 25 pub const hosts = @import("hosts.zig");
26 pub const handoff = @import("handoff.zig"); 26 pub const handoff = @import("handoff.zig");
27 pub const layout = @import("layout.zig"); 27 pub const layout = @import("layout.zig");
28 // This file uses `keymap.detach_key` only — the abort byte both opening waits 28 // The seam carries `keymap.detach_key` and nothing else: the dial here and
29 // watch for. The dial has no session to command and no terminal to drive, 29 // the prefix filter in interact.zig both watch for that one abort byte. It
30 // which is why the byte comes from keymap rather than from the filter that 30 // is keymap's rather than the filter's because a dial has no session to
31 // consumes it. 31 // command and no terminal to drive.
32 pub const keymap = @import("keymap.zig"); 32 pub const keymap = @import("keymap.zig");
33 pub const askpass = @import("askpass.zig"); 33 pub const askpass = @import("askpass.zig");
34 pub const core = @import("client_core.zig"); 34 pub const core = @import("client_core.zig");