06046c02
docs: what the new check caught
a73x 2026-08-20 09:01
Commit message
src/client.zig
| Old | New | ||
|---|---|---|---|
| @@ -7,8 +7,8 @@ | |||
| 7 | //! backoff schedule, the wall-file record an attach earns, and the pure | 7 | //! backoff schedule, the wall-file record an attach earns, and the pure |
| 8 | //! answers a `Ctrl-\ c`/`n`/`p` needs out of a `sessions_reply`. | 8 | //! answers a `Ctrl-\ c`/`n`/`p` needs out of a `sessions_reply`. |
| 9 | //! | 9 | //! |
| 10 | //! What it no longer owns is a SESSION. Phase 3c of the wall-home-screen | 10 | //! What it no longer owns is a SESSION. The wall-home-screen work |
| 11 | //! spec converged the two interaction loops that existed here and in | 11 | //! converged the two interaction loops that existed here and in |
| 12 | //! wallview.zig into one: `mux [TARGET]` is now a wall of one tile, entered | 12 | //! wallview.zig into one: `mux [TARGET]` is now a wall of one tile, entered |
| 13 | //! zoomed, so the loop that drives an `interact.Core` over a link — the | 13 | //! zoomed, so the loop that drives an `interact.Core` over a link — the |
| 14 | //! chord meanings, the reconnect, the terminal's lifetime, the exit code — | 14 | //! chord meanings, the reconnect, the terminal's lifetime, the exit code — |
src/handoff.zig
| Old | New | ||
|---|---|---|---|
| @@ -9,9 +9,10 @@ const std = @import("std"); | |||
| 9 | const xdg = @import("xdg"); | 9 | const xdg = @import("xdg"); |
| 10 | 10 | ||
| 11 | /// The QUIC attach budget for one attempt, warm path and cold path | 11 | /// The QUIC attach budget for one attempt, warm path and cold path |
| 12 | /// alike — and, since M15, for a direct `quic://` dial too. Pinned from | 12 | /// alike — and, since the module-seam refactor, for a direct `quic://` dial |
| 13 | /// measurement on 2026-08-11 (decisions.md, M14), re-measured 2026-08-12 | 13 | /// too. Pinned from measurement on 2026-08-11 when the ssh→QUIC handoff |
| 14 | /// (M15 Task 4) after the one fast-failure case was taught to work. | 14 | /// landed (decisions.md), re-measured 2026-08-12 after the one fast-failure |
| 15 | /// case was taught to work. | ||
| 15 | /// | 16 | /// |
| 16 | /// Measured: silence is what this bound is for, and silence is the | 17 | /// Measured: silence is what this bound is for, and silence is the |
| 17 | /// common case. A wrong PSK against a live listener ran 2037ms of a | 18 | /// common case. A wrong PSK against a live listener ran 2037ms of a |
| @@ -29,7 +30,7 @@ const xdg = @import("xdg"); | |||
| 29 | /// | 30 | /// |
| 30 | /// Silence spending the budget fixes the shape of the trade, and 2000 | 31 | /// Silence spending the budget fixes the shape of the trade, and 2000 |
| 31 | /// stands. Below: a real handshake is 4ms local, 6.9ms cold attach on | 32 | /// stands. Below: a real handshake is 4ms local, 6.9ms cold attach on |
| 32 | /// the M8 LAN, and 234ms at 75ms RTT — 3.1x RTT, since every fresh | 33 | /// the LAN box, and 234ms at 75ms RTT — 3.1x RTT, since every fresh |
| 33 | /// Initial costs a Retry round trip — so 2000 covers RTT to ~645ms, | 34 | /// Initial costs a Retry round trip — so 2000 covers RTT to ~645ms, |
| 34 | /// past any terrestrial link. Above: with no negative caching of "UDP | 35 | /// past any terrestrial link. Above: with no negative caching of "UDP |
| 35 | /// blocked" by design, every fallback attach on such a network pays this | 36 | /// blocked" by design, every fallback attach on such a network pays this |
| @@ -359,7 +360,7 @@ test "announce: a key with leading zero bytes still hexes to 64 chars" { | |||
| 359 | 360 | ||
| 360 | test "announce: the WRITER refuses port 0, where the mistake is still local" { | 361 | test "announce: the WRITER refuses port 0, where the mistake is still local" { |
| 361 | // `endpoint_reply` carries 0 as a legal wire value meaning "could | 362 | // `endpoint_reply` carries 0 as a legal wire value meaning "could |
| 362 | // not", so Task 5 holds a u16 that may be 0 and must turn it into | 363 | // not", so the reader holds a u16 that may be 0 and must turn it into |
| 363 | // `endpoint none` rather than a line. Refusing at the writer means a | 364 | // `endpoint none` rather than a line. Refusing at the writer means a |
| 364 | // caller that forgets fails on its own line, instead of shipping a | 365 | // caller that forgets fails on its own line, instead of shipping a |
| 365 | // line that only fails much later, on the client, as a parse error | 366 | // line that only fails much later, on the client, as a parse error |
src/keymap.zig
| Old | New | ||
|---|---|---|---|
| @@ -1,7 +1,8 @@ | |||
| 1 | //! Normalized key events → VT byte sequences. The portable half of input: | 1 | //! Normalized key events → VT byte sequences. The portable half of input: |
| 2 | //! each shell (browser KeyboardEvent, later xkb) produces the normalized | 2 | //! each shell (browser KeyboardEvent, later xkb) produces the normalized |
| 3 | //! form; this module owns every byte that reaches the PTY, so the tables | 3 | //! form; this module owns every byte that reaches the PTY, so the tables |
| 4 | //! are unit-tested natively with no browser in the loop (M-web Task 3). | 4 | //! are unit-tested natively with no browser in the loop — they arrived |
| 5 | //! for the browser client and were never allowed to depend on it. | ||
| 5 | //! | 6 | //! |
| 6 | //! v1 scope (spec): printable input, control characters, arrows and nav | 7 | //! v1 scope (spec): printable input, control characters, arrows and nav |
| 7 | //! keys, function keys, the xterm modifier-encoded CSI variants, and | 8 | //! keys, function keys, the xterm modifier-encoded CSI variants, and |
src/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -346,7 +346,7 @@ pub fn main() !u8 { | |||
| 346 | .stats => return stats(alloc, sock_path), | 346 | .stats => return stats(alloc, sock_path), |
| 347 | .stop => return stopCmd(alloc, sock_path), | 347 | .stop => return stopCmd(alloc, sock_path), |
| 348 | .proxy => { | 348 | .proxy => { |
| 349 | // Attach auto-start (M13): the user asked for a session, not a | 349 | // Attach auto-start: the user asked for a session, not a |
| 350 | // daemon. Same helper and deadline as `muxd start`. Unlike it, a | 350 | // daemon. Same helper and deadline as `muxd start`. Unlike it, a |
| 351 | // daemon that was already there is silent: the user asked for a | 351 | // daemon that was already there is silent: the user asked for a |
| 352 | // session and is about to get one. | 352 | // session and is about to get one. |
| @@ -367,7 +367,8 @@ pub fn main() !u8 { | |||
| 367 | /// a daemon to set an environment for. | 367 | /// a daemon to set an environment for. |
| 368 | /// | 368 | /// |
| 369 | /// Opt-IN: `=1` and nothing else. It was an opt-out through the agent | 369 | /// Opt-IN: `=1` and nothing else. It was an opt-out through the agent |
| 370 | /// surface and M18, on the reasoning that marks are what make an exit code | 370 | /// surface and the multi-session daemon, on the reasoning that marks are |
| 371 | /// what make an exit code | ||
| 371 | /// knowable; the daily-driver reading is the opposite one, because the shim | 372 | /// knowable; the daily-driver reading is the opposite one, because the shim |
| 372 | /// costs a zsh user their `~/.zshenv` and displaces a bash user's DEBUG trap | 373 | /// costs a zsh user their `~/.zshenv` and displaces a bash user's DEBUG trap |
| 373 | /// (atuin, bash-preexec) on every session, while only `muxa` reads what it | 374 | /// (atuin, bash-preexec) on every session, while only `muxa` reads what it |
| @@ -560,7 +561,7 @@ fn oneShotQuery( | |||
| 560 | fn dump(alloc: std.mem.Allocator, sock_path: []const u8, vt_mode: bool, session: []const u8) !u8 { | 561 | fn dump(alloc: std.mem.Allocator, sock_path: []const u8, vt_mode: bool, session: []const u8) !u8 { |
| 561 | // vt byte ++ session-name tail, built by the wire module — empty is the | 562 | // vt byte ++ session-name tail, built by the wire module — empty is the |
| 562 | // wire's own default spelling, so a bare `muxd dump` sends exactly the | 563 | // wire's own default spelling, so a bare `muxd dump` sends exactly the |
| 563 | // pre-M18 one-byte payload. | 564 | // one-byte payload that predates session names. |
| 564 | var buf: [proto.debug_dump_max_len]u8 = undefined; | 565 | var buf: [proto.debug_dump_max_len]u8 = undefined; |
| 565 | const payload = proto.encodeDebugDumpNamed(&buf, vt_mode, session); | 566 | const payload = proto.encodeDebugDumpNamed(&buf, vt_mode, session); |
| 566 | return oneShotQuery(alloc, sock_path, "dump", .debug_dump, payload, .dump_reply); | 567 | return oneShotQuery(alloc, sock_path, "dump", .debug_dump, payload, .dump_reply); |
src/predict.zig
| Old | New | ||
|---|---|---|---|
| @@ -43,20 +43,21 @@ | |||
| 43 | //! once or twice per command at a normal prompt, as readline hands the | 43 | //! once or twice per command at a normal prompt, as readline hands the |
| 44 | //! terminal back and forth to run each command; and since every move | 44 | //! terminal back and forth to run each command; and since every move |
| 45 | //! re-earns display from scratch, the first couple of keystrokes after each | 45 | //! re-earns display from scratch, the first couple of keystrokes after each |
| 46 | //! prompt are invisible predictions. That is the conservative trade M9 | 46 | //! prompt are invisible predictions. That is the conservative trade this |
| 47 | //! takes deliberately — per-context confidence memory is the banked polish. | 47 | //! module takes deliberately — per-context confidence memory is the banked |
| 48 | //! polish. | ||
| 48 | //! | 49 | //! |
| 49 | //! Memory: the overlay owns everything it holds. Predictions are copies of | 50 | //! Memory: the overlay owns everything it holds. Predictions are copies of |
| 50 | //! bytes, never slices into frame payloads or engine rows, and the queue is | 51 | //! bytes, never slices into frame payloads or engine rows, and the queue is |
| 51 | //! read back by index rather than handed out as a slice — a slice would go | 52 | //! read back by index rather than handed out as a slice — a slice would go |
| 52 | //! stale on the next append, which is the shape the M8 egress records call | 53 | //! stale on the next append, which is the shape decisions.md's egress |
| 53 | //! the UAF-that-never-crashes. | 54 | //! records call the UAF-that-never-crashes. |
| 54 | const std = @import("std"); | 55 | const std = @import("std"); |
| 55 | const proto = @import("protocol"); | 56 | const proto = @import("protocol"); |
| 56 | 57 | ||
| 57 | /// One predicted character at one place on the grid. Printable ASCII only | 58 | /// One predicted character at one place on the grid. Printable ASCII only, |
| 58 | /// in M9: `ch` is a byte by value, so there is nothing here that can outlive | 59 | /// deliberately: `ch` is a byte by value, so there is nothing here that can |
| 59 | /// what it was copied from. | 60 | /// outlive what it was copied from. |
| 60 | pub const Cell = struct { row: u16, col: u16, ch: u8 }; | 61 | pub const Cell = struct { row: u16, col: u16, ch: u8 }; |
| 61 | 62 | ||
| 62 | pub const Pred = struct { | 63 | pub const Pred = struct { |
| @@ -107,7 +108,7 @@ pub const Counters = struct { | |||
| 107 | /// how often we were wrong, not how much was thrown away. | 108 | /// how often we were wrong, not how much was thrown away. |
| 108 | contradicted: u64 = 0, | 109 | contradicted: u64 = 0, |
| 109 | /// PER EVENT: the subset of `contradicted` where nothing ever answered | 110 | /// PER EVENT: the subset of `contradicted` where nothing ever answered |
| 110 | /// rather than something disagreed. Counted in both, so Task 7 can read | 111 | /// rather than something disagreed. Counted in both, so a reader can tell |
| 111 | /// "we guessed wrong" apart from "the application went quiet". | 112 | /// "we guessed wrong" apart from "the application went quiet". |
| 112 | expired: u64 = 0, | 113 | expired: u64 = 0, |
| 113 | /// PER PREDICTION: queued but discarded without a verdict — by a | 114 | /// PER PREDICTION: queued but discarded without a verdict — by a |
| @@ -742,7 +743,7 @@ test "a contradiction flushes the whole queue, not merely the cell that was wron | |||
| 742 | // be claiming agreement about a screen it had already been told it was | 743 | // be claiming agreement about a screen it had already been told it was |
| 743 | // wrong about. | 744 | // wrong about. |
| 744 | try std.testing.expectEqual(@as(u64, 0), ov.counters.confirmed); | 745 | try std.testing.expectEqual(@as(u64, 0), ov.counters.confirmed); |
| 745 | // A refutation is not an expiry, and Task 7 reads these apart. | 746 | // A refutation is not an expiry, and the counters read these apart. |
| 746 | try std.testing.expectEqual(@as(u64, 0), ov.counters.expired); | 747 | try std.testing.expectEqual(@as(u64, 0), ov.counters.expired); |
| 747 | } | 748 | } |
| 748 | 749 | ||
src/protocol.zig
| Old | New | ||
|---|---|---|---|
| @@ -703,8 +703,8 @@ pub const clipboard_base64_max: usize = 64 * 1024; | |||
| 703 | /// | 703 | /// |
| 704 | /// 1024 is not an independent choice — it is the length ghostty's own | 704 | /// 1024 is not an independent choice — it is the length ghostty's own |
| 705 | /// handler already truncates a title to before it reaches `Terminal.title` | 705 | /// handler already truncates a title to before it reaches `Terminal.title` |
| 706 | /// (stream_terminal.zig, `max_title_len`), so a title read off the engine | 706 | /// (terminal/stream_terminal.zig, `max_title_len`), so a title read off |
| 707 | /// can never exceed it today. Both ends check it anyway: the peer is not | 707 | /// the engine can never exceed it today. Both ends check it anyway: the peer is not |
| 708 | /// necessarily this version of muxd, and the engine's truncation is not | 708 | /// necessarily this version of muxd, and the engine's truncation is not |
| 709 | /// part of any contract mux is entitled to lean on. | 709 | /// part of any contract mux is entitled to lean on. |
| 710 | pub const term_title_max: usize = 1024; | 710 | pub const term_title_max: usize = 1024; |
src/proxy.zig
| Old | New | ||
|---|---|---|---|
| @@ -9,7 +9,7 @@ const TmpDir = @import("testtmp").TmpDir; | |||
| 9 | /// Make a hangup on any of this process's pipes surface as EPIPE from | 9 | /// Make a hangup on any of this process's pipes surface as EPIPE from |
| 10 | /// write() instead of killing it. | 10 | /// write() instead of killing it. |
| 11 | /// | 11 | /// |
| 12 | /// Defence in depth, not a fix: Zig's start.zig already installs a noop | 12 | /// Defence in depth, not a fix: Zig's std/start.zig already installs a noop |
| 13 | /// SIGPIPE handler, so `pump`'s write-error returns are reachable without | 13 | /// SIGPIPE handler, so `pump`'s write-error returns are reachable without |
| 14 | /// this. What it pins is that the reachability belongs to this code instead | 14 | /// this. What it pins is that the reachability belongs to this code instead |
| 15 | /// of to a std default (`std.options.keep_sigpipe`) another module could | 15 | /// of to a std default (`std.options.keep_sigpipe`) another module could |
src/pty.zig
| Old | New | ||
|---|---|---|---|
| @@ -137,7 +137,8 @@ pub const Pty = struct { | |||
| 137 | // shell is interactive and therefore ignores the SIGTERM that | 137 | // shell is interactive and therefore ignores the SIGTERM that |
| 138 | // follows; and the blocking waitpid after THAT never returns. One | 138 | // follows; and the blocking waitpid after THAT never returns. One |
| 139 | // leaked fd is a daemon that cannot shut down. Unreachable at one | 139 | // leaked fd is a daemon that cannot shut down. Unreachable at one |
| 140 | // session per daemon, which is why M18 is where it surfaced. | 140 | // session per daemon, which is why the multi-session daemon is |
| 141 | // where it surfaced. | ||
| 141 | // | 142 | // |
| 142 | // Set in the parent rather than asked for at open time because | 143 | // Set in the parent rather than asked for at open time because |
| 143 | // forkpty owns the open: there is no flags argument to pass. Nothing | 144 | // forkpty owns the open: there is no flags argument to pass. Nothing |
| @@ -447,7 +448,8 @@ test "Pty: checkExited reports shell exit" { | |||
| 447 | } | 448 | } |
| 448 | 449 | ||
| 449 | test "Pty: a later spawn does not inherit an earlier session's master" { | 450 | test "Pty: a later spawn does not inherit an earlier session's master" { |
| 450 | // The hangup contract, and the reason M18 needs it stated as a test: | 451 | // The hangup contract, and the reason a multi-session daemon needs it |
| 452 | // stated as a test: | ||
| 451 | // closing a master is how the daemon hangs up on the shell behind it, | 453 | // closing a master is how the daemon hangs up on the shell behind it, |
| 452 | // and that only works if the close is the LAST one. glibc's forkpty | 454 | // and that only works if the close is the LAST one. glibc's forkpty |
| 453 | // hands the master back without CLOEXEC, so before this was fixed every | 455 | // hands the master back without CLOEXEC, so before this was fixed every |
src/replica.zig
| Old | New | ||
|---|---|---|---|
| @@ -1,9 +1,9 @@ | |||
| 1 | //! The replay core: what a mux client does to mirror a daemon's session. | 1 | //! The replay core: what a mux client does to mirror a daemon's session. |
| 2 | //! Applies snapshot and delta frames to a local engine, tracks the resume | 2 | //! Applies snapshot and delta frames to a local engine, tracks the resume |
| 3 | //! coordinates (`last_seq`/`session_epoch`), and follows the authoritative | 3 | //! coordinates (`last_seq`/`session_epoch`), and follows the authoritative |
| 4 | //! grid. Extracted from client.zig's session() (M-web Task 1) so the CLI | 4 | //! grid. Extracted from client.zig's session() when the browser client |
| 5 | //! client, the wasm core, and the server's test fixtures replay through one | 5 | //! landed, so the CLI client, the wasm core, and the server's test fixtures |
| 6 | //! implementation instead of three. | 6 | //! replay through one implementation instead of three. |
| 7 | //! | 7 | //! |
| 8 | //! Deliberately platform-free: no posix, no fds, no clocks — this module | 8 | //! Deliberately platform-free: no posix, no fds, no clocks — this module |
| 9 | //! must compile for wasm32-freestanding. Anything that needs a terminal, | 9 | //! must compile for wasm32-freestanding. Anything that needs a terminal, |
src/server.zig
| Old | New | ||
|---|---|---|---|
| @@ -7929,7 +7929,7 @@ test "Server: a dead name re-attaches as a fresh session with a new epoch" { | |||
| 7929 | } | 7929 | } |
| 7930 | 7930 | ||
| 7931 | // --------------------------------------------------------------------------- | 7931 | // --------------------------------------------------------------------------- |
| 7932 | // Task 5: every out-of-band instrument learns a session name — dump, the | 7932 | // Every out-of-band instrument learns a session name — dump, the |
| 7933 | // observer's status_req, stats, and the attached-client tail-match rule for | 7933 | // observer's status_req, stats, and the attached-client tail-match rule for |
| 7934 | // status_req/await_req. | 7934 | // status_req/await_req. |
| 7935 | // --------------------------------------------------------------------------- | 7935 | // --------------------------------------------------------------------------- |
src/spawn.zig
| Old | New | ||
|---|---|---|---|
| @@ -1,8 +1,9 @@ | |||
| 1 | //! Get a daemon onto a socket path: probe, spawn detached, poll until it | 1 | //! Get a daemon onto a socket path: probe, spawn detached, poll until it |
| 2 | //! answers. Three callers: `muxd start`, which is explicit and spelled | 2 | //! answers. Three callers: `muxd start`, which is explicit and spelled |
| 3 | //! out, and the two attach auto-starts (`muxd proxy` and `mux`), which | 3 | //! out, and the two attach auto-starts (`muxd proxy` and `mux`), which |
| 4 | //! share `ensureForAttach` below. The M10 prediction held — auto-start | 4 | //! share `ensureForAttach` below. The prediction made when the QUIC |
| 5 | //! arrived as call sites, not as a rewrite. | 5 | //! ergonomics landed held — auto-start arrived as call sites, not as a |
| 6 | //! rewrite. | ||
| 6 | const std = @import("std"); | 7 | const std = @import("std"); |
| 7 | const xdg = @import("xdg"); | 8 | const xdg = @import("xdg"); |
| 8 | 9 | ||
src/wallview.zig
| Old | New | ||
|---|---|---|---|
| @@ -1316,8 +1316,8 @@ fn pumpTile(t: *Tile) void { | |||
| 1316 | // Whatever the keyboard left, through everything a plain client's | 1316 | // Whatever the keyboard left, through everything a plain client's |
| 1317 | // keystrokes go through: the mouse split, the wheel, alternate | 1317 | // keystrokes go through: the mouse split, the wheel, alternate |
| 1318 | // scroll, the scrollback view, the prediction and the input frame. | 1318 | // scroll, the scrollback view, the prediction and the input frame. |
| 1319 | // Phase 1 hand-rolled a subset of that, and a zoomed tile had no | 1319 | // The first version of the wall hand-rolled a subset of that, and |
| 1320 | // wheel at all. | 1320 | // a zoomed tile had no wheel at all. |
| 1321 | // | 1321 | // |
| 1322 | // Sent whatever the zoom is doing NOW: these bytes were typed AT | 1322 | // Sent whatever the zoom is doing NOW: these bytes were typed AT |
| 1323 | // this session while this tile was the zoom, and the keyboard is | 1323 | // this session while this tile was the zoom, and the keyboard is |
| @@ -1603,8 +1603,8 @@ fn paintDeadZoomLocked(t: *Tile) void { | |||
| 1603 | proto.writeAllFd(t.shared.out_fd, fbs.getWritten()) catch {}; | 1603 | proto.writeAllFd(t.shared.out_fd, fbs.getWritten()) catch {}; |
| 1604 | } | 1604 | } |
| 1605 | 1605 | ||
| 1606 | /// The whole screen an empty wall gets. Phase 1's dead-zoom line, for the | 1606 | /// The whole screen an empty wall gets. The first wall's dead-zoom line, |
| 1607 | /// same reason: a blank terminal with no cursor reads as hung, and the | 1607 | /// for the same reason: a blank terminal with no cursor reads as hung, and the |
| 1608 | /// last `x` is precisely when the user needs to be told that what they see | 1608 | /// last `x` is precisely when the user needs to be told that what they see |
| 1609 | /// is the answer and not a crash. | 1609 | /// is the answer and not a crash. |
| 1610 | fn paintEmptyWallLocked(shared: *Shared) void { | 1610 | fn paintEmptyWallLocked(shared: *Shared) void { |
src/wasm_core.zig
| Old | New | ||
|---|---|---|---|
| @@ -1,5 +1,5 @@ | |||
| 1 | //! The browser replica core: Engine + Replica + keymap compiled to | 1 | //! The browser replica core: Engine + Replica + keymap compiled to |
| 2 | //! wasm32-freestanding (M-web Task 4). The JS shell is glue; every | 2 | //! wasm32-freestanding. The JS shell is glue; every |
| 3 | //! decision lives on this side of the boundary. | 3 | //! decision lives on this side of the boundary. |
| 4 | //! | 4 | //! |
| 5 | //! FRAME-driven, not byte-driven: the host stages one mux protocol frame | 5 | //! FRAME-driven, not byte-driven: the host stages one mux protocol frame |
src/webhub.zig
| Old | New | ||
|---|---|---|---|
| @@ -1,7 +1,7 @@ | |||
| 1 | //! The muxweb hub's HTTP/WebSocket layer: route table, | 1 | //! The muxweb hub's HTTP/WebSocket layer: route table, |
| 2 | //! Origin gate, and the WS endpoint naming — the decisions std.http does | 2 | //! Origin gate, and the WS endpoint naming — the decisions std.http does |
| 3 | //! NOT make for us. The connection loop and per-tile pump build on this | 3 | //! NOT make for us. The connection loop and per-tile pump build on this; |
| 4 | //! (Tasks 6-7); the assets are @embedFile'd by webhub_main.zig (the exe | 4 | //! the assets are @embedFile'd by webhub_main.zig (the exe |
| 5 | //! root) and injected here, which keeps this module testable without | 5 | //! root) and injected here, which keeps this module testable without |
| 6 | //! building the wasm artifact. | 6 | //! building the wasm artifact. |
| 7 | //! | 7 | //! |