a73x

3eaad756

refactor: unify native layout planning before resize sprint

a73x   2026-09-05 09:22

Commit message
refactor: unify native layout planning before resize sprint

docs/native-sprint-workflow.md
Old New
@@ -0,0 +1,86 @@
1 # Native client sprint workflow
2
3 Use this formula for each native client sprint. The tiling
4 [spec](superpowers/specs/2026-09-05-native-tiling-design.md) defines behavior;
5 the [delivery plan](superpowers/plans/2026-09-05-native-tiling.md) breaks it into
6 packages. Keep one sprint active, ending in a working demo and explicit acceptance.
7
8 1. **Review and scope.** Read the previous sprint's actions in [RETRO.md](../RETRO.md).
9 Define the smallest useful deliverable, its acceptance scenarios, and what
10 belongs to later sprints. Settle material interaction choices before UI work;
11 record proposed defaults as proposals until the user accepts them.
12 2. **Tidy the starting point.** Inspect the code the sprint will touch. Make small,
13 behavior-preserving refactors that simplify the planned work, review them,
14 run affected checks and `make check`, and commit separately. No cleanup quota:
15 if the code is ready, record that. Substantial refactors become explicit
16 packages; unrelated debt goes into the backlog.
17 3. **Assign a bounded pair.** Reuse one implementer and one independent adversarial
18 reviewer. Give them the package, relevant files, ownership boundaries,
19 acceptance criteria, and each other's names. Root owns integration, independent
20 acceptance, user communication, and commits. Delegate another task only when
21 it can progress independently. Load skills only where they help the work.
22 4. **Build a functional slice.** Establish the smallest model/interface contract,
23 then connect actual input, rendering, and daemon behavior. Keep changes small
24 enough to review. Avoid expanding into later sprint features.
25 5. **Resolve concrete review findings.** The reviewer supplies a failing scenario,
26 consequence, and expected result directly to the implementer. The implementer
27 returns a fix and evidence, or a reason the finding does not apply. Root
28 adjudicates disagreement against requirements and observed behavior. Agreement
29 is not a substitute for independent validation.
30 6. **Validate through real boundaries.** Drive ordinary SDL events into an isolated
31 GUI using `test/native_tiling.py`'s Rig. Compare actual framebuffer pixels and
32 independently queried daemon state, including every pane's PTY dimensions.
33 Exercise failure paths and event ordering. Observation helpers must not repair
34 the state under test. Use real Wayland at high DPI and a scale transition;
35 exercise SSH/QUIC when changes affect their behavior. Create and clean up only
36 owned fixture sessions and daemons.
37 7. **Refactor the integrated result.** Reserve this as work, even after the opening
38 cleanup. Remove duplication, superseded paths, unnecessary state, and temporary
39 scaffolding exposed by implementation. Review the result again. For retained
40 debt, record location, consequence, reason, and next owner or trigger. Correctness
41 failures cannot be deferred past acceptance.
42 8. **Freeze and run final gates.** Stop source edits before compilation. Run the
43 required repository and native gates on the final result, retaining logs:
44
45 ```sh
46 export ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache
47 make check
48 make ci
49 deps/zig/zig build native native-test -Doptimize=ReleaseSafe
50 make native-e2e
51 ```
52
53 `make ci` excludes native tests. Use the pinned compiler. A sandbox denial is
54 not a product failure: run required local socket/PTY checks with the appropriate
55 authorized permissions. Rerun affected gates after meaningful changes; do not
56 repeatedly run broad suites without a new reason.
57 9. **Record and share the actual demo.** Use Rig with `MUXG_VIDEODRIVER=wayland`
58 and the current compositor environment. Create fixtures, focus the owned GUI,
59 and drive the same user controls being delivered. Capture the actual compositor
60 window continuously; on Sway, find its rectangle by PID with `swaymsg -t get_tree`
61 and pipe repeated `grim -g GEOMETRY -s 1 -t ppm -` frames to ffmpeg. Encode H.264,
62 `yuv420p`, even dimensions, and `+faststart`, without audio. Stop recording
63 before closing the window so another application cannot enter the recording.
64 A slideshow of screenshots is useful evidence but is not a screen recording.
65
66 For a user on the same tailnet, serve only the demo page/video through a loopback
67 HTTP server with byte-range support, then use Tailscale Serve:
68
69 ```sh
70 tailscale serve status
71 tailscale serve --bg http://127.0.0.1:18765
72 ```
73
74 Check existing routes before changing them; preserve unrelated services. Use
75 the private HTTPS URL printed by Serve. Verify playback/HTTP range responses,
76 retain the recording outside temporary fixture directories, and record the
77 server's teardown command. Funnel and audio are unnecessary for this workflow.
78 10. **Close honestly.** Inspect the final diff, commit validated work, and provide
79 the demo, controls, test results, and material limits. User demo acceptance is
80 distinct from passing automation. Update the spec/plan status and RETRO.md with
81 resolved findings and checkable next-sprint actions. Mark acceptance only when
82 given; a recorded-demo approval does not imply hands-on feedback. Then begin
83 the next authorized sprint with step 1.
84
85 Sprint 2 established the recorded Sway/private Serve demo route. Sprint 3 adds the
86 opening cleanup pass by user request; keep the closing cleanup as well.
docs/superpowers/plans/2026-09-05-native-tiling.md
Old New
@@ -1,10 +1,14 @@
1 # Native tiling — delegated delivery plan 1 # Native tiling — delegated delivery plan
2 2
3 Design authority: [native tiling spec](../specs/2026-09-05-native-tiling-design.md). 3 Design authority: [native tiling spec](../specs/2026-09-05-native-tiling-design.md).
4 Status: Sprint 2 complete; implementation, review, validation, and user demo acceptance passed. 4 Status: Sprint 2 accepted; Sprint 3 underway, beginning with reviewed cleanup.
5 5
6 ## Working agreement 6 ## Working agreement
7 7
8 Follow the reusable [sprint workflow](../../native-sprint-workflow.md), including
9 a bounded pre-sprint cleanup pass and separate validated cleanup commit before
10 feature changes. The closing refactoring package remains mandatory.
11
8 Keep one sprint active. The root agent owns integration, user updates, acceptance 12 Keep one sprint active. The root agent owns integration, user updates, acceptance
9 evidence, and commits. Assign one implementer and one independent adversarial 13 evidence, and commits. Assign one implementer and one independent adversarial
10 reviewer by default. Reuse those agents across packages; do not spawn a new agent 14 reviewer by default. Reuse those agents across packages; do not spawn a new agent
src/gui/workspace.zig
Old New
@@ -136,6 +136,14 @@ const Tree = struct {
136 .empty => unreachable, 136 .empty => unreachable,
137 }; 137 };
138 } 138 }
139 fn layout(self: *const Tree, width: u32, height: u32, m: Metrics) Layout {
140 var out: Layout = .{};
141 if (self.root) |root| {
142 const min = self.minimum(root, m);
143 self.flatten(root, .{ .w = @max(width, min.w), .h = @max(height, min.h) }, .{ .w = width, .h = height }, m, &out);
144 }
145 return out;
146 }
139 fn flatten(self: *const Tree, at: u8, rect: Rect, viewport: Rect, m: Metrics, out: *Layout) void { 147 fn flatten(self: *const Tree, at: u8, rect: Rect, viewport: Rect, m: Metrics, out: *Layout) void {
140 switch (self.nodes[at]) { 148 switch (self.nodes[at]) {
141 .leaf => |id| { 149 .leaf => |id| {
@@ -204,12 +212,7 @@ pub const Workspace = struct {
204 return null; 212 return null;
205 } 213 }
206 pub fn layout(self: *Workspace, width: u32, height: u32, m: Metrics) Layout { 214 pub fn layout(self: *Workspace, width: u32, height: u32, m: Metrics) Layout {
207 var out: Layout = .{}; 215 return self.tab().tree.layout(width, height, m);
208 if (self.tab().tree.root) |root| {
209 const min = self.tab().tree.minimum(root, m);
210 self.tab().tree.flatten(root, .{ .w = @max(width, min.w), .h = @max(height, min.h) }, .{ .w = width, .h = height }, m, &out);
211 }
212 return out;
213 } 216 }
214 pub fn arm(self: *Workspace, direction: Direction) void { 217 pub fn arm(self: *Workspace, direction: Direction) void {
215 if (self.tab().focus) |id| self.tab().pending = .{ .pane = id, .direction = direction }; 218 if (self.tab().focus) |id| self.tab().pending = .{ .pane = id, .direction = direction };
@@ -255,9 +258,7 @@ pub const Workspace = struct {
255 var tree = t.tree; 258 var tree = t.tree;
256 const id = self.next_pane_id; 259 const id = self.next_pane_id;
257 try tree.insert(origin, id, direction); 260 try tree.insert(origin, id, direction);
258 var flat: Layout = .{}; 261 const flat = tree.layout(width, height, m);
259 const min = tree.minimum(tree.root.?, m);
260 tree.flatten(tree.root.?, .{ .w = @max(width, min.w), .h = @max(height, min.h) }, .{ .w = width, .h = height }, m, &flat);
261 return .{ .tree = tree, .placement = flat.get(id).? }; 262 return .{ .tree = tree, .placement = flat.get(id).? };
262 } 263 }
263 /// Call immediately after preparing the attachment; no other model edits 264 /// Call immediately after preparing the attachment; no other model edits