a73x

74730ed7

refactor: isolate native core and component development gates

a73x   2026-09-05 15:42

Commit message
refactor: isolate native core and component development gates

CLAUDE.md
Old New
@@ -16,6 +16,8 @@ make mac-sdk # Darwin only, and `make build` runs it: a shadow
16 make build test e2e # Makefile already points at it 16 make build test e2e # Makefile already points at it
17 make check # fmt + unit tests + shell syntax + comment-claim refs — pre-commit gate 17 make check # fmt + unit tests + shell syntax + comment-claim refs — pre-commit gate
18 make ci # check + e2e + agent + throughput — the delivery gate 18 make ci # check + e2e + agent + throughput — the delivery gate
19 make daemon-test client-test # focused component tests; same artifacts as make test
20 make native-core-test # GUI workspace/interaction tests without window/font libraries
19 make agent soak bench throughput 21 make agent soak bench throughput
20 make native native-e2e # opt-in muxg; SDL3/freetype/fontconfig/HarfBuzz + GL headers; e2e also needs python3 22 make native native-e2e # opt-in muxg; SDL3/freetype/fontconfig/HarfBuzz + GL headers; e2e also needs python3
21 make vm # real user journeys against the mux-e2e VM (test/vm.sh) 23 make vm # real user journeys against the mux-e2e VM (test/vm.sh)
@@ -528,6 +530,9 @@ native session panes; it is built only by the native steps and is never installe
528 530
529 ## Where the answers live 531 ## Where the answers live
530 532
533 `docs/component-ownership.md` maps the daemon, shared client/wire, and native GUI
534 boundaries to focused gates and proposes ownership responsibilities.
535
531 `docs/roadmap.md` (~660 ln) the ranked queue, but stale past 2026-08-16 — 536 `docs/roadmap.md` (~660 ln) the ranked queue, but stale past 2026-08-16 —
532 `git-collab issue list` is the live order · `docs/decisions.md` (9.2k ln, grep 537 `git-collab issue list` is the live order · `docs/decisions.md` (9.2k ln, grep
533 only) every decision + measurement · `README.md` user-facing usage. Design 538 only) every decision + measurement · `README.md` user-facing usage. Design
Makefile
Old New
@@ -31,7 +31,7 @@ SHA256 ?= shasum -a 256
31 endif 31 endif
32 MUX_TARGET ?= x86_64-linux-musl 32 MUX_TARGET ?= x86_64-linux-musl
33 33
34 .PHONY: build check ci test e2e soak bench agent native native-e2e native-stress throughput vm coverage deps clean clean-deps xversion xversion-build install release release-mac mac-sdk mac xos provision-mac 34 .PHONY: build check ci test daemon-test client-test native-core-test e2e soak bench agent native native-e2e native-stress throughput vm coverage deps clean clean-deps xversion xversion-build install release release-mac mac-sdk mac xos provision-mac
35 35
36 # The QUIC stack (deps/quic) is built on demand by build.zig, so no target 36 # The QUIC stack (deps/quic) is built on demand by build.zig, so no target
37 # here needs to depend on this one. It exists to make the one-time cost 37 # here needs to depend on this one. It exists to make the one-time cost
@@ -269,6 +269,11 @@ ci:
269 agent: mac-sdk 269 agent: mac-sdk
270 $(ZIG) build agent 270 $(ZIG) build agent
271 271
272 # Component gates reuse the aggregate test artifacts. Native core needs no
273 # window/font libraries; the full native gate below includes its tests too.
274 daemon-test client-test native-core-test: mac-sdk
275 $(ZIG) build $@
276
272 # Opt-in because these link system SDL3, freetype, fontconfig and HarfBuzz. 277 # Opt-in because these link system SDL3, freetype, fontconfig and HarfBuzz.
273 # They stay outside every default build and delivery gate. 278 # They stay outside every default build and delivery gate.
274 native: mac-sdk 279 native: mac-sdk
RETRO.md
Old New
@@ -576,8 +576,95 @@ still being written.
576 out of `frame.zig`, preserving passive frame and state observations. 576 out of `frame.zig`, preserving passive frame and state observations.
577 - [ ] A future recording-tooling package should promote the recorder/clip driver; 577 - [ ] A future recording-tooling package should promote the recorder/clip driver;
578 only the scale gate moved into the repository in this bounded sprint. 578 only the scale gate moved into the repository in this bounded sprint.
579 - [ ] If the terminal host-picker timeout recurs, capture its menu/discovery 579 - [x] Investigate the terminal host-picker timeout if it recurs. The component
580 ordering before adjusting waits. Its source path is `test/e2e_09_hosts.sh`. 580 sprint below reproduced the ordering hazard and replaced the missing readiness
581 condition with an event-based wait in `test/e2e_09_hosts.sh`. No timeout grew.
581 - [x] Obtain authorization before the next production sprint. The user requested 582 - [x] Obtain authorization before the next production sprint. The user requested
582 another separation sprint aimed at independent daemon/GUI work, followed by 583 another separation sprint aimed at independent daemon/GUI work, followed by
583 clearer code ownership. That authorization does not imply demo acceptance. 584 clearer code ownership. That authorization does not imply demo acceptance.
585
586
587 ## Component separation — independent daemon and GUI work, 2026-09-05
588
589 The user authorized another separation sprint, followed by a clearer ownership
590 split. Implementation, closing review and all final delivery gates are complete,
591 including full CI after the fixture fix. The actual GUI recording is retained. The
592 [delivery record](docs/superpowers/plans/2026-09-05-component-separation.md)
593 contains the graph, commands and evidence. Recorded-demo acceptance is pending.
594
595 ### Results and cleanup
596
597 - The new `native_core` module exclusively owns workspace, runtime, picker,
598 persistence and interaction. The frame/painter consumes its declared import.
599 No behavior or file placement changed; compatibility aliases keep the native
600 module's existing public surface available.
601 - `make native-core-test` runs without window/font libraries. `make native`
602 includes the core and frame/painter suites; their ReleaseSafe run passed 47
603 tests. `make daemon-test` and `make client-test` reuse the same artifacts as
604 the aggregate unit gate, without running unrelated components.
605 - Six disposable-snapshot probes proved the boundaries: broken GUI compilation
606 leaves daemon/core tests usable; broken daemon compilation leaves GUI tests
607 usable; core tests need no window-library package metadata. Matching negative
608 controls failed as expected. No poisoned source entered the working checkout.
609 - Opening assessment preserved the existing shared client facade and module
610 table; neither needed a broad rewrite. Closing cleanup removed a redundant
611 test alias and corrected source-ban/build comments. All core child files have
612 one source owner, and native tests explicitly retain both suites.
613 - The recurring terminal CI failure was a fixture race, not a native module
614 change: selecting row 2 immediately after Enter could use a stale one-session
615 poll and clamp to row 1. The fix waits for the newly born row itself, retains
616 the selected-row/holder assertions, and changes no timeout. The focused
617 15-scenario host group and subsequent independent full CI both passed.
618
619 ### Validation limits and recording
620
621 Native integration (including real old-daemon compatibility), five real OpenSSH
622 recovery groups, high-DPI scale transitions and the actual GUI recording passed.
623 The initial stress run missed frame p99 at 20.176 ms; the previous release also
624 missed the same 20 ms budget on that compositor. A normal-logging compositor
625 repeat passed without budget changes: frame p99 18.992 ms before reopen and
626 18.246 ms afterward, sampled input maxima 55.5/55.4 ms for shared/separate
627 daemons, 104.6 MB over 30.1 seconds. The exact cause of the variation is unproven;
628 all results and renderer provenance limits are retained. The input measurements
629 are polling upper bounds, and no new macOS evidence is claimed.
630
631 The 56.8-second final release recording has two disclosed restart cuts and no
632 audio. Fixtures and the owned compositor were stopped. The retained local video
633 is `native-component-trial.mp4` beside this thread's other visualization artifacts;
634 no new sharing payload was published. Acceptance remains a user decision.
635
636 ### Delegation and ownership
637
638 Fresh short briefs and immediately checked session metadata confirmed Luna for
639 implementation and Terra for review. Luna wired the core/frame source split and
640 repaired the test fixture; root owned build wiring, independent probes, delivery
641 gates and documentation. Terra caught the insufficient stale-row readiness wait
642 and reviewed the corrected event condition, build artifacts and ownership map.
643
644 Available cumulative counters for these sprint-specific agent sessions are below.
645 Cached input is included in input, not additive; counters are not a monetary bill.
646
647 | Agent | Input tokens | Cached input (included) | Output tokens |
648 | --- | ---: | ---: | ---: |
649 | Luna implementer | 3,801,635 | 3,675,648 | 10,737 |
650 | Terra reviewer | 3,593,717 | 3,406,080 | 10,881 |
651
652 The [ownership proposal](docs/component-ownership.md) recommends daemon, native
653 GUI and shared wire/client responsibilities first. GUI policy and rendering have
654 an internal seam when separate owners become useful. Shared build/release and
655 wire-contract changes still need coordination. No people or CODEOWNERS rules
656 were invented, and mixed directories are called out explicitly.
657
658 ### Next actions
659
660 - [ ] Record acceptance of this demo separately from earlier pending recordings
661 and hands-on feedback.
662 - [ ] Assign people or agents to the proposed ownership areas when those names
663 are known; use separate worktrees and the focused gates for internal work.
664 - [ ] Keep shared wire/lifecycle changes coordinated across affected frontends.
665 Independent compilation does not make their semantics independent.
666 - [ ] If revisiting performance measurement, distinguish frame preparation from
667 compositor swap waiting and record display/logging conditions before drawing
668 conclusions from a narrow frame-budget miss.
669 - [ ] Promote the general recorder/clip fixture in a future tooling package; the
670 repository scale gate already removed one temporary-script dependency.
build.zig
Old New
@@ -325,7 +325,8 @@ const mod_table = [_]ModSpec{
325 .{ .name = "mux", .path = "src/cli/mux.zig", .link_libc = true, .imports = &.{ "daemon", "client", "wall", "agent", "webhub", "term", "proxy", "quic", "xdg", "spawn", "sockpath", "cliflags", "dial", "server_os", "client_os" }, .test_imports = &.{"testtmp"}, .quic_tests = true }, 325 .{ .name = "mux", .path = "src/cli/mux.zig", .link_libc = true, .imports = &.{ "daemon", "client", "wall", "agent", "webhub", "term", "proxy", "quic", "xdg", "spawn", "sockpath", "cliflags", "dial", "server_os", "client_os" }, .test_imports = &.{"testtmp"}, .quic_tests = true },
326 // Opt-in rows: no default artifact or test names them, so machines 326 // Opt-in rows: no default artifact or test names them, so machines
327 // without the viewer's system libraries keep all existing gates. 327 // without the viewer's system libraries keep all existing gates.
328 .{ .name = "native", .path = "src/gui/native.zig", .link_libc = true, .imports = &.{ "client", "term" }, .opt_in = true }, 328 .{ .name = "native_core", .path = "src/gui/native_core.zig", .link_libc = true, .imports = &.{ "client", "term" }, .opt_in = true },
329 .{ .name = "native", .path = "src/gui/native.zig", .link_libc = true, .imports = &.{ "native_core", "client", "term" }, .opt_in = true },
329 .{ .name = "muxg", .path = "src/cli/muxg.zig", .link_libc = true, .imports = &.{ "native", "client", "term", "cliflags", "sockpath", "xdg" }, .opt_in = true }, 330 .{ .name = "muxg", .path = "src/cli/muxg.zig", .link_libc = true, .imports = &.{ "native", "client", "term", "cliflags", "sockpath", "xdg" }, .opt_in = true },
330 }; 331 };
331 332
@@ -1071,7 +1072,14 @@ pub fn build(b: *std.Build) void {
1071 // a clean checkout — the dependency must reach the test binaries, 1072 // a clean checkout — the dependency must reach the test binaries,
1072 // not only the binary (decisions.md, M8). 1073 // not only the binary (decisions.md, M8).
1073 if (mod_table[i].quic_tests) linkQuic(b, t, quic); 1074 if (mod_table[i].quic_tests) linkQuic(b, t, quic);
1074 test_step.dependOn(&b.addRunArtifact(t).step); 1075 const run = b.addRunArtifact(t);
1076 test_step.dependOn(&run.step);
1077 // Component owners use the same artifact as the full gate, without
1078 // compiling or running another component's suite.
1079 if (std.mem.eql(u8, name, "daemon") or std.mem.eql(u8, name, "client")) {
1080 const component_test = b.step(b.fmt("{s}-test", .{name}), b.fmt("Run {s} component tests", .{name}));
1081 component_test.dependOn(&run.step);
1082 }
1075 } 1083 }
1076 1084
1077 // web/verify.js drives mux_core.wasm through the page's real call 1085 // web/verify.js drives mux_core.wasm through the page's real call
@@ -1131,8 +1139,8 @@ pub fn build(b: *std.Build) void {
1131 const agent_step = b.step("agent", "Run the agent-surface end-to-end suite"); 1139 const agent_step = b.step("agent", "Run the agent-surface end-to-end suite");
1132 agent_step.dependOn(&agent.step); 1140 agent_step.dependOn(&agent.step);
1133 1141
1134 // The native client is entirely opt-in. These are the only compile 1142 // The native client and its core are opt-in. Only the window/painter
1135 // steps that reach its rows or ask pkg-config for its system libraries. 1143 // artifacts ask pkg-config for the native system libraries.
1136 // GL functions are resolved through the window library at runtime, so 1144 // GL functions are resolved through the window library at runtime, so
1137 // there is deliberately no libGL link here. 1145 // there is deliberately no libGL link here.
1138 const native_mod = mods[comptime idxOf("native")]; 1146 const native_mod = mods[comptime idxOf("native")];
@@ -1146,6 +1154,15 @@ pub fn build(b: *std.Build) void {
1146 const native_step = b.step("native", "Build muxg (opt-in; needs SDL3, freetype, fontconfig, HarfBuzz)"); 1154 const native_step = b.step("native", "Build muxg (opt-in; needs SDL3, freetype, fontconfig, HarfBuzz)");
1147 native_step.dependOn(&install_muxg.step); 1155 native_step.dependOn(&install_muxg.step);
1148 1156
1157 // Policy tests have their own artifact: running them does not compile
1158 // the frame adapter or resolve any window/font system libraries.
1159 const native_core_tests = b.addTest(.{ .root_module = mods[comptime idxOf("native_core")] });
1160 linkerFor(native_core_tests);
1161 linkQuic(b, native_core_tests, quic);
1162 const run_native_core_tests = b.addRunArtifact(native_core_tests);
1163 const native_core_test_step = b.step("native-core-test", "Run native workspace and interaction tests without window libraries");
1164 native_core_test_step.dependOn(&run_native_core_tests.step);
1165
1149 // This compiles the same native component and links the same libraries, 1166 // This compiles the same native component and links the same libraries,
1150 // but its tests do not initialize video or open a window. 1167 // but its tests do not initialize video or open a window.
1151 const native_tests = b.addTest(.{ .root_module = native_mod }); 1168 const native_tests = b.addTest(.{ .root_module = native_mod });
@@ -1154,6 +1171,7 @@ pub fn build(b: *std.Build) void {
1154 native_tests.linkSystemLibrary2(lib, .{ .use_pkg_config = .force }); 1171 native_tests.linkSystemLibrary2(lib, .{ .use_pkg_config = .force });
1155 linkQuic(b, native_tests, quic); 1172 linkQuic(b, native_tests, quic);
1156 const native_test_step = b.step("native-test", "Run the native client's unit tests (opt-in)"); 1173 const native_test_step = b.step("native-test", "Run the native client's unit tests (opt-in)");
1174 native_test_step.dependOn(&run_native_core_tests.step);
1157 native_test_step.dependOn(&b.addRunArtifact(native_tests).step); 1175 native_test_step.dependOn(&b.addRunArtifact(native_tests).step);
1158 1176
1159 // The runner is added with the full event path; declaring it here keeps 1177 // The runner is added with the full event path; declaring it here keeps
docs/component-ownership.md
Old New
@@ -0,0 +1,82 @@
1 # Component boundaries and proposed ownership
2
3 The repository already separates the daemon from the GUI in its production
4 module graph. The component-separation sprint adds independent test targets and
5 separates native workspace policy from the window/painter module. These are
6 working boundaries; the roles below are a proposal for assigning people or
7 agents. No named reviewers or CODEOWNERS enforcement have been assigned.
8
9 ## Start with three ownership areas
10
11 | Area | Primary responsibility and files | Focused development gate |
12 | --- | --- | --- |
13 | Daemon | Session lifecycle, authoritative terminal engine, PTYs, scheduling, server transport and platform operations: `src/server/`, `src/engine/engine.zig`, `src/engine/delta.zig`, `src/os/server_os*` | `make daemon-test` |
14 | Native GUI | Pane/workspace policy, persistence, input, rendering and window lifecycle: `src/gui/`, `src/cli/muxg.zig` | `make native-core-test` for policy; `make native` for the complete GUI |
15 | Shared wire and client services | Frame/schema compatibility and the reusable connection, discovery and session interfaces: the `term` module (`src/engine/term.zig`, `protocol.zig`, `grid.zig`, `replica.zig`), `src/client/` shared files, `src/dial.zig`, `src/link.zig`, `src/quic.zig` and client OS support | `make client-test` for client changes; `make check` for the complete shared unit coverage |
16
17 The daemon mode entrypoint, `src/cli/main.zig`, belongs with daemon behavior but
18 is compiled by the shared `mux` dispatcher. Changes there require the complete
19 CLI checks. Likewise `make daemon-test` exercises daemon tests, not every test
20 in the engine or transport dependencies it compiles. Focused gates accelerate
21 development; they do not replace dependency and integration validation.
22
23 Terminal UI (`src/tui/`, `src/cli/mux_main.zig`) and browser UI (`web/`,
24 `src/client/webhub.zig`, `src/cli/webhub_main.zig`, `src/client/wasm_core.zig`)
25 remain separate frontend responsibilities. In particular, a blanket ownership
26 rule for `src/client/` would incorrectly include the browser frontend. Assign
27 exceptions explicitly when named CODEOWNERS entries are introduced.
28
29 Build configuration, the CLI dispatcher, release tooling and integration tests
30 need a maintainer who coordinates changes crossing these areas. They should not
31 be edited independently by several implementation agents in the same checkout.
32
33 ## The native GUI has two internal components
34
35 `native_core` owns `workspace.zig`, `runtime.zig`, `picker.zig`, `persistence.zig`
36 and `interaction.zig`, exported by `src/gui/native_core.zig`. It imports only
37 the shared `client` and `term` modules. It owns native pane identity, attachment
38 lifetimes, authored layout and interaction policy, and receives measured geometry
39 and translated input through its public interfaces.
40
41 `native` owns `frame.zig` and the painter/font/atlas files. It consumes
42 `native_core` through a declared module import; it cannot take a second ownership
43 claim on the core files through relative imports. SDL stays in `frame.zig`.
44 `native-test` aggregates the independent core suite and the window/painter suite.
45
46 Keep these under one GUI owner initially. Assigning separate policy and rendering
47 owners becomes useful when there is enough parallel work to justify it; their
48 contract already exists without another file reorganization.
49
50 ## Changes that need coordination
51
52 A daemon internal change can be developed and tested without compiling the GUI.
53 A GUI change can be developed without compiling the daemon. Each owner should
54 use an isolated worktree so unfinished shared build-file edits do not interfere
55 with another owner's work. Repository-wide source rules still apply to every
56 build in a checkout.
57
58 A wire-format or lifecycle-semantics change is a shared contract change. Its
59 author should identify affected clients, preserve old-peer behavior where
60 required, and run relevant cross-version and real-boundary tests. GUI recovery,
61 join-only restoration, End/Detach semantics, and remote attachment identity are
62 examples where coordination matters even if only daemon code changes.
63
64 Keep module internals private to their component and add reusable operations to
65 the existing shared client/wire interfaces. Native layout policy stays separate
66 from terminal layout policy. Domain owners can decide internal implementation;
67 changing a shared contract needs the affected owners' review.
68
69 ## Evidence behind the split
70
71 The [component-separation delivery record](superpowers/plans/2026-09-05-component-separation.md)
72 retains independent compilation probes in a disposable source snapshot:
73
74 - A deliberate GUI compilation failure leaves daemon and native-core tests usable.
75 - A deliberate daemon compilation failure leaves the GUI build/tests usable.
76 - Native-core tests pass with window-library package metadata unavailable, while
77 a control GUI build correctly fails.
78
79 The matching negative controls prove the injected failures were active. The
80 source snapshot is restored after probing; the working checkout is never poisoned.
81 The full delivery gates remain `make ci` plus the relevant native integration,
82 scale, recovery and responsiveness checks.
docs/native-sprint-workflow.md
Old New
@@ -98,13 +98,22 @@ packages. Keep one sprint active, ending in a working demo and explicit acceptan
98 export ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache 98 export ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache
99 make check 99 make check
100 make ci 100 make ci
101 deps/zig/zig build native-core-test -Doptimize=ReleaseSafe
101 deps/zig/zig build native native-test -Doptimize=ReleaseSafe 102 deps/zig/zig build native native-test -Doptimize=ReleaseSafe
102 make native-e2e 103 make native-e2e
103 make native-stress # Linux raw-output responsiveness 104 make native-stress # Linux raw-output responsiveness
104 ``` 105 ```
105 106
106 `make ci` excludes native tests. Use the pinned compiler. A sandbox denial is 107 `make ci` excludes native tests. Use the pinned compiler.
107 not a product failure: run required local socket/PTY checks with the appropriate 108
109 The focused `make native-core-test` gate compiles workspace, runtime, picker,
110 persistence and interaction without window/font libraries. `native-test`
111 includes that same test artifact plus the frame/painter suite. Daemon and
112 shared client work can use `make daemon-test` and `make client-test`, which
113 reuse their artifacts from the aggregate unit gate. These focused commands
114 complement the full delivery checks above.
115
116 A sandbox denial is not a product failure: run required local socket/PTY checks with the appropriate
108 authorized permissions. Rerun affected gates after meaningful changes; do not 117 authorized permissions. Rerun affected gates after meaningful changes; do not
109 repeatedly run broad suites without a new reason. 118 repeatedly run broad suites without a new reason.
110 9. **Record and share the actual demo.** Use Rig with `MUXG_VIDEODRIVER=wayland` 119 9. **Record and share the actual demo.** Use Rig with `MUXG_VIDEODRIVER=wayland`
docs/superpowers/plans/2026-09-05-component-separation.md
Old New
@@ -0,0 +1,132 @@
1 # Component separation and independent development
2
3 The user authorized another separation sprint after interaction ownership commit
4 `efc99d5`, followed by a clearer division of ownership so daemon and GUI work can
5 proceed independently. Prior recorded-demo acceptance remains pending; this is
6 explicit advancement authorization, not retrospective demo approval.
7
8 Status: implemented, reviewed and validated. All final delivery gates passed;
9 the retained recording awaits user acceptance. Ownership roles are proposed;
10 no people are assigned.
11
12 ## Scope and contract
13
14 Opening inspection found the daemon and GUI already independent in the production
15 module graph. The shared client facade serves actual common transport/discovery
16 needs; splitting it would require a larger change without evidence of benefit.
17 No separate preparatory cleanup was needed.
18
19 The smallest useful change is independent component testing and an independently
20 owned native policy module:
21
22 | Module | Owns | Declared production imports |
23 | --- | --- | --- |
24 | `native_core` | workspace, runtime, picker, persistence, interaction | client, term |
25 | `native` | frame adapter, font/atlas/painter, frame timing | native_core, client, term |
26 | `daemon` | existing server core and children | Unchanged |
27
28 `src/gui/native_core.zig` is the only module root that reaches its five child
29 files by relative import. The frame adapter consumes them through the declared
30 `native_core` import. Compatibility aliases in `native.zig` preserve its public
31 API without creating a second source owner. Native layout, runtime, shared
32 client behavior and all controls remain unchanged.
33
34 `native-core-test` compiles its own test artifact with shared QUIC support and
35 no window/font libraries. `native-test` runs that same artifact plus the
36 frame/painter tests. `daemon-test` and `client-test` reuse their existing run
37 nodes from aggregate `test`; there is no second test implementation or duplicate
38 artifact. All native targets remain opt-in.
39
40 Root owned the build graph, Makefile, independent validation and documentation.
41 Luna implemented and wired the core root and GUI imports. Terra independently
42 reviewed both changes and the ownership/test contracts. Fresh short contexts
43 were used, and recorded `turn_context.model` metadata confirmed Luna and Terra
44 immediately at startup. Closing cleanup corrected a forbidden window-library
45 name in the new root's comment, removed a redundant test alias and updated the
46 build comments. No production behavior was changed during cleanup.
47
48 ## Acceptance and evidence
49
50 The final integrated ReleaseSafe build passed 47 native tests: 20 in the
51 independent core and 27 in the frame/painter suite. The new root contributes one
52 registration test; existing test coverage is retained.
53
54 Independent compilation was tested in a disposable snapshot containing the final
55 module/build changes. Each deliberate failure has a matching negative control:
56
57 | Snapshot condition | Expected independent result | Negative control |
58 | --- | --- | --- |
59 | Frame contains a deliberate compile error | `daemon-test native-core-test` pass | `native-test` fails with the planted GUI diagnostic |
60 | Daemon contains a deliberate compile error | `native native-test` pass | `daemon-test` fails with the planted daemon diagnostic |
61 | Window-library pkg-config search paths are empty | `native-core-test` passes | `native` fails to resolve its required packages |
62
63 All six checks passed. The snapshot files were restored in `finally`; the shared
64 checkout was never modified by the probes. This proves compile/test isolation,
65 not that shared wire changes can be made without coordinating their semantics.
66 Repository-wide source rules and shared build-file validity still apply.
67
68 Final gates use `ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache`, the pinned compiler,
69 and authorized local socket/PTY access:
70
71 | Gate | Result |
72 | --- | --- |
73 | `deps/zig/zig build install native native-test -Doptimize=ReleaseSafe -p dist/native-component-release --summary all` | Passed; both release binaries from one graph, 47 tests |
74 | Independent compilation probes above | Passed |
75 | `make native-e2e` | Passed; 62 scenarios |
76 | Prior-daemon compatibility | Passed separately; 63 total native integration scenarios |
77 | Real OpenSSH/Wayland recovery | All five groups passed at 200% |
78 | Wayland scale and raw-output stress | Scale passed; stress passed on normal-logging repeat, earlier misses retained |
79 | `make check` / `make ci` | Passed on the final fixture repair |
80
81 The full CI attempt exposed the recurring terminal picker fixture race from the
82 prior sprint. Its script pressed Enter and immediately selected row 2 while the
83 host poll could still contain only session 0. That selection was clamped; waiting
84 for silence afterward could not repair it. The repaired script waits for the
85 actual unselected born-session row (`2 1`) before pressing 2, retaining the
86 selected-row and holder-count assertions and all original timeouts. Terra
87 rejected an initial wait on session 0 because it would accept the stale list.
88 The focused 15-scenario host group and the subsequent independent full CI run
89 both passed.
90 No terminal production code changed.
91
92 The first Wayland stress run narrowly missed the 20 ms frame p99 budget at
93 20.176 ms. The previously validated interaction release also missed it on that
94 compositor (20.567 ms before reopen, 20.270 ms afterward). After restarting the
95 owned compositor with normal logging, preserving its geometry, scale and GLES2
96 renderer settings, the final release passed unchanged budgets: 18.992 ms before
97 reopen and 18.246 ms afterward. This does not establish the exact cause of the
98 variation. Debug startup identified NVIDIA GeForce RTX 3080; normal-logging
99 startup did not repeat the device identification. Keep that provenance limit
100 with the measurement rather than inferring a performance improvement.
101
102 The successful 30.1-second stress run read 104.6 MB. Sampled input-to-painted
103 maxima were 55.5 ms on the shared daemon and 55.4 ms on the separate daemon,
104 94 samples each. The 5 ms polling interval and observation overhead make these
105 upper bounds. Runs used an isolated compositor without concurrent GUI fixtures
106 or recording, and no budget was relaxed. No new macOS result is claimed.
107
108 A 56.8-second actual GUI recording uses the final release at 200%, H.264
109 1100×700 with no audio. It shows the unchanged menu, resize, delayed End,
110 restoration and replacement behavior through three continuous clips with two
111 restart cuts. Input is through ordinary SDL events; this is not a new
112 compositor mouse-path trial. The fixtures and owned compositor were stopped.
113 The recording is retained at:
114 `/home/xanderle/.codex/visualizations/2026/09/05/01a071c2-5767-7790-a335-1a84536c9ca5/native-component-trial.mp4`.
115
116 Logs, exact probe commands, both stress failures and the successful repeat,
117 source/release hashes, recording driver and model metadata are in the sibling
118 `component-evidence/` directory. User recording acceptance is separate from all
119 automated checks. No new Tailscale route or payload was published.
120
121 ## Ownership follow-up
122
123 The [ownership proposal](../../component-ownership.md) starts with daemon, native
124 GUI, and shared wire/client services. GUI policy and rendering now have a usable
125 internal boundary, but assigning them separate people is optional. Terminal and
126 browser frontends retain their own responsibilities. Shared build/release and
127 contract changes require coordination; ordinary internal work uses the focused
128 component gates and separate worktrees.
129
130 No directory reshuffle, repository split, named CODEOWNERS assignment or shared
131 wire redesign is included. Future assignments should follow these tested module
132 boundaries and account for mixed folders such as `src/client/` and `src/engine/`.
docs/superpowers/plans/2026-09-05-native-interaction-debt.md
Old New
@@ -5,8 +5,9 @@ remains accepted; the SSH recording's acceptance remains pending. Advancing to
5 this sprint does not imply acceptance of that recording. 5 this sprint does not imply acceptance of that recording.
6 6
7 Status: implementation, review and all required validation complete. 7 Status: implementation, review and all required validation complete.
8 Recorded-demo acceptance is pending. The user authorized another separation 8 Recorded-demo acceptance is pending. The user authorized the
9 sprint before this recording was accepted; that authorization is not demo approval. 9 [component-separation sprint](2026-09-05-component-separation.md) before this
10 recording was accepted; that authorization is not demo approval.
10 11
11 ## Scope and acceptance 12 ## Scope and acceptance
12 13
src/gui/frame.zig
Old New
@@ -3,11 +3,12 @@ const std = @import("std");
3 const client = @import("client"); 3 const client = @import("client");
4 const term = @import("term"); 4 const term = @import("term");
5 const keymap = client.keymap; 5 const keymap = client.keymap;
6 const model = @import("workspace.zig"); 6 const native_core = @import("native_core");
7 const runtime = @import("runtime.zig"); 7 const model = native_core.workspace;
8 const picker_mod = @import("picker.zig"); 8 const runtime = native_core.runtime;
9 const persistence = @import("persistence.zig"); 9 const picker_mod = native_core.picker;
10 const interaction = @import("interaction.zig"); 10 const persistence = native_core.persistence;
11 const interaction = native_core.interaction;
11 const font = @import("font.zig"); 12 const font = @import("font.zig");
12 const atlas = @import("atlas.zig"); 13 const atlas = @import("atlas.zig");
13 const quads = @import("quads.zig"); 14 const quads = @import("quads.zig");
src/gui/native.zig
Old New
@@ -5,23 +5,28 @@
5 //! terminal wall and confine the window API to frame.zig. The 5 //! terminal wall and confine the window API to frame.zig. The
6 //! remaining painter code stays usable in unit tests that open no window. 6 //! remaining painter code stays usable in unit tests that open no window.
7 //! 7 //!
8 //! Imports `client` and `term` and nothing else of ours. 8 //! The window-free model is the separate `native_core` component; this module
9 //! owns the window and painter files.
9 const std = @import("std"); 10 const std = @import("std");
10 const client = @import("client"); 11 const client = @import("client");
11 const term = @import("term"); 12 const term = @import("term");
12 13
13 pub const interaction = @import("interaction.zig"); 14 pub const core = @import("native_core");
14 pub const frame = @import("frame.zig"); 15 pub const frame = @import("frame.zig");
15 pub const workspace = @import("workspace.zig");
16 pub const runtime = @import("runtime.zig");
17 pub const picker = @import("picker.zig");
18 pub const persistence = @import("persistence.zig");
19 pub const bench = @import("bench.zig"); 16 pub const bench = @import("bench.zig");
20 pub const atlas = @import("atlas.zig"); 17 pub const atlas = @import("atlas.zig");
21 pub const font = @import("font.zig"); 18 pub const font = @import("font.zig");
22 pub const quads = @import("quads.zig"); 19 pub const quads = @import("quads.zig");
23 pub const gl = @import("gl.zig"); 20 pub const gl = @import("gl.zig");
24 21
22 // Compatibility aliases for callers that previously reached these through
23 // `native`; the source ownership remains with native_core.
24 pub const interaction = core.interaction;
25 pub const workspace = core.workspace;
26 pub const runtime = core.runtime;
27 pub const picker = core.picker;
28 pub const persistence = core.persistence;
29
25 pub fn run(alloc: std.mem.Allocator, opts: frame.Options) !u8 { 30 pub fn run(alloc: std.mem.Allocator, opts: frame.Options) !u8 {
26 return frame.run(alloc, opts); 31 return frame.run(alloc, opts);
27 } 32 }
@@ -29,12 +34,8 @@ pub fn run(alloc: std.mem.Allocator, opts: frame.Options) !u8 {
29 test { 34 test {
30 _ = client; 35 _ = client;
31 _ = term; 36 _ = term;
32 _ = interaction; 37 _ = core;
33 _ = frame; 38 _ = frame;
34 _ = workspace;
35 _ = runtime;
36 _ = picker;
37 _ = persistence;
38 _ = bench; 39 _ = bench;
39 _ = atlas; 40 _ = atlas;
40 _ = font; 41 _ = font;
src/gui/native_core.zig
Old New
@@ -0,0 +1,21 @@
1 //! Window-free native GUI model, transport runtime, picker, persistence, and
2 //! interaction policy. The native window and painter consume this component;
3 //! keeping it as its own module lets its tests run without window libraries.
4 const client = @import("client");
5 const term = @import("term");
6
7 pub const workspace = @import("workspace.zig");
8 pub const runtime = @import("runtime.zig");
9 pub const picker = @import("picker.zig");
10 pub const persistence = @import("persistence.zig");
11 pub const interaction = @import("interaction.zig");
12
13 test {
14 _ = client;
15 _ = term;
16 _ = workspace;
17 _ = runtime;
18 _ = picker;
19 _ = persistence;
20 _ = interaction;
21 }
test/e2e_09_hosts.sh
Old New
@@ -1085,6 +1085,11 @@ expect x forget 15000
1085 send 2 1085 send 2
1086 settle 400 15000 1086 settle 400 15000
1087 send \r 1087 send \r
1088 # Enter changes the prefix filter to the session level before the poller's
1089 # post-birth reply necessarily arrives. Wait for the unselected row for the
1090 # newly born session 1 as the readiness event, so the following numeric
1091 # selection cannot race the stale list containing only session 0.
1092 expect 2 1 15000
1088 # The BORN session's row, named. Daemon 2's list is 0 then the session c 1093 # The BORN session's row, named. Daemon 2's list is 0 then the session c
1089 # just made, so row 2 is the new one, and a bare "on this wall" would be 1094 # just made, so row 2 is the new one, and a bare "on this wall" would be
1090 # satisfied by row 1 -- the pane the layout seeded -- whatever the birth 1095 # satisfied by row 1 -- the pane the layout seeded -- whatever the birth