docs/superpowers/plans/2026-09-05-native-tiling.md
Ref: Size: 42.7 KiB History
# Native tiling — delegated delivery plan Design authority: [native tiling spec](../specs/2026-09-05-native-tiling-design.md). Status: native tiling milestone complete. Sprint 5 implemented, reviewed, validated, and accepted by the user on 2026-09-05. Sprint 4 is implemented, reviewed, and validated; separate recorded-demo acceptance was not given. Sprint 3 acceptance passed. The retained SSH error-classification debt is addressed in the separately authorized [follow-up sprint](2026-09-05-ssh-error-classification.md), whose implementation and validation are complete and recorded-demo acceptance is pending. The user subsequently authorized the [interaction ownership debt sprint](2026-09-05-native-interaction-debt.md), preserving the accepted controls and behavior. ## Working agreement Follow the reusable [sprint workflow](../../native-sprint-workflow.md), including a bounded pre-sprint cleanup pass and separate validated cleanup commit before feature changes. The closing refactoring package remains mandatory. For ergonomic choices, use propose → implement a trial → demo → adjust. State trial defaults before UI work, then record requested changes and whether feedback came from a recording or hands-on use. Demo acceptance does not imply advance agreement or a hands-on trial. Keep agreed behavior and scope authoritative. Keep one sprint active. The root agent owns integration, user updates, acceptance evidence, and commits. Assign one implementer and one independent adversarial reviewer by default. Reuse those agents across packages; do not spawn a new agent for every file or test. A second implementer is useful only for an independent package with agreed interfaces and disjoint file ownership. Give each agent the spec, the current package below, relevant files, acceptance criteria, and the other agent's name. They inspect the code needed for that task; no blanket skill loading or repeated whole-repository surveys. The implementer owns edits. The reviewer sends concrete failures or counterexamples directly to the implementer, who fixes them and returns evidence. Review changed behavior and its tests, not just the implementation's own account. A review finding records the failing scenario, consequence, and expected result. Each is closed with a fix and verification, or a reason both agents accept that it does not apply. If they cannot agree, root adjudicates against the spec and observed behavior; do not iterate merely to manufacture agreement. Ask the user only for a product decision that existing requirements do not settle. Every sprint reserves an explicit refactoring package after functional integration and before final acceptance. The implementer and reviewer inspect the combined change for duplicated logic, unclear ownership, inconsistent interfaces or naming, unnecessary state, obsolete paths, temporary scaffolding, and technical debt in the touched code. Complete the cleanup as part of the sprint, including updating tests and documentation; it is not an optional follow-up after the feature ships. Deduplicate behavior where the responsibilities genuinely match, simplify ownership and error handling, and remove superseded helpers and test-only bypasses. Keep the GUI and terminal interaction policies separate. Do not introduce speculative frameworks or broaden into an unrelated CLI redesign to satisfy a cleanup quota. If a concrete debt item must remain, record its location, consequence, reason for deferral, and the package or trigger that will address it. Root resolves disputed deferrals; known correctness failures cannot be deferred past acceptance. The reviewer reports remaining findings explicitly and reviews the refactored result. Root inspects the final diff and runs the independent integration gate against that result. Earlier passes do not validate subsequent refactoring: rerun affected checks and the sprint acceptance scenario after cleanup. Commit a sprint only after required checks pass and review findings are resolved. Record the commit, exact validation commands/results, and material limits in the delivery record. Do not proceed to the next sprint with a broken build or an unresolved acceptance failure. ## Sprint 1 — two real panes User-visible result: two terminals on independent hosts in one native window, with `v`/`b` insertion, prefix + `h/j/k/l` and click focus, independent input and PTY sizing. One flood, disconnect, or shell exit cannot take down the other pane. Window close detaches sessions. No picker, persistence, divider dragging, or tab UI is required yet. One tab exists in the model from the start. ### 1A. Ownership and layout contract Implementer owns a new SDL-free GUI workspace model and its focused tests. Reuse `src/client/layout.zig` behind an adapter if it preserves the agreed semantics; do not change terminal-client tiling policy. Root owns any module registration and build rule changes once the model's location and exports are agreed. Define stable workspace-wide pane IDs, tab IDs, one-tab ownership, per-tab focus and pending direction, and leaf insertion/removal. Define a render-facing list of pane IDs and rectangles, including terminal content versus headers/dividers. Specify how layout units convert to physical pixels and terminal cells, how a too-small window is handled without discarding panes, and how transient IDs map to the existing tree's bounded leaf indices. For a window smaller than the tree's minimum footprint, retain a valid minimum layout and clip it to the window. Never send a zero PTY size or discard a leaf; hit-testing is restricted to visible rectangles. Refuse a new split that cannot fit the current viewport, while existing splits survive a later window shrink. Freeze the small model/painter interface with the reviewer before 1B: lifecycle ownership, rectangle units, insertion transaction, target-string lifetimes, and focus access. Include failure cleanup and a rule that callbacks use pane ID plus attachment generation, never array position or current focus. Acceptance: side-by-side and above/below splits, one nested split, directional neighbors, cancellation with unchanged geometry, minimum sizes, removal and stable IDs, and allocation failure without a half-inserted pane. Existing GUI still builds. ### 1B. Multiple attachments and pane rendering Implementer owns `src/gui/frame.zig`, pane runtime integration, and any necessary changes to `src/gui/quads.zig` / `src/gui/gl.zig`. Root integrates exports in `src/gui/native.zig`. Depends on the reviewed 1A interface. Use one existing `client.session_pump` per pane. Own targets for the entire pump lifetime, keep wakes safe through removal/shutdown, and isolate dialing, exited, failed, and reconnecting state per pane. Stop treating one pane's phase as a reason to return from the whole window loop. The new pane-exit behavior also applies with one pane: revise the existing shell-exit-7 native test to assert an exited pane and a still-usable GUI, followed by an explicit quit. Update help/spec claims about process exit propagation deliberately; do not retain two competing pane lifecycle models merely to keep that old assertion. Build the whole active tab using physical content rectangles, pane-local offsets, clipping, labelled headers, and only the focused cursor. Prepare glyphs for ALL visible panes before normalizing any atlas UVs: another pane growing the shared atlas must not invalidate earlier instances. First copy visible cells and owned text into a reusable frame snapshot under one pump lock at a time; prepare and emit from those same snapshots. A second read of live grids could introduce new text after glyph preparation. Never borrow row text after releasing its pump lock. Bound work per pump and do not hold multiple pump mutexes together. Resize each attached PTY from its own content area. Acceptance: distinct grids produce distinct clipped pixels; shrinking a pane clears its old region; later-pane glyphs that grow the atlas do not corrupt earlier panes; font-scale changes update every pane. A failed or exited pane remains visible while another accepts input. Closing the window releases resources and preserves daemon sessions, including when a pane is still dialing. ### 1C. Human-usable insertion and focus Implementer owns GUI command handling and `src/cli/muxg.zig` argument changes. Depends on 1B; the same implementer avoids competing edits to `frame.zig`. Provide `--next-target SPEC --next-session NAME` for one staged second pane, using the existing host-spec grammar (SSH host, `quic://...`, or the single string `--sock PATH`) and session name validation. Both flags are required together; reject repeated staged targets in this milestone. The primary target keeps its existing CLI syntax. For example: `muxg alpha --session work --next-target beta --next-session logs`. For a local fixture, use `--next-target '--sock /tmp/b.sock' --next-session logs`. The second target is staged until prefix + `v`/`b`, then prefix + Enter inserts it. This is a usable substitute for session selection in sprint 1; sprint 2 replaces target acquisition with the picker while retaining insertion. The target should be selectable from real CLI arguments, not only a test FIFO. Implement Ctrl+\ command mode, visible pending direction, `h/j/k/l` and arrow focus, click focus, cancellation, and prefix passthrough. Consume both key events and associated SDL text events correctly: commands must not leak bytes into a terminal or suppress subsequent ordinary text. Window resize remains live. Pending insertion retains the pane ID armed by `v`/`b` even if focus moves before Enter; the preview stays on that pane. Removing that pane cancels its pending insertion. Re-arming explicitly replaces the pending pane/direction. Acceptance: a person can launch and insert the second explicit target in either direction and type independently in both. Exercise actual prefix/key/text events, mouse coordinates at high DPI, cancellation, and input after command completion. Pressing the prefix twice forwards its normal terminal keymap encoding once and exits command mode. Test that passthrough explicitly rather than leaving it implicit. Extend test hooks only as needed to drive those same event paths. ### 1D. Deduplication, refactoring, and debt cleanup Depends on integrated 1A–1C. The implementer owns cleanup; the reviewer challenges the resulting boundaries and any retained debt. Root coordinates changes to its harness files rather than allowing concurrent edits. Inspect the combined model, runtime, painter, CLI, and test changes. Consolidate repeated rectangle/cell calculations, target ownership and cleanup, input-mode transitions, and pane status handling where their semantics match. Remove obsolete single-pane paths and transitional helpers. Keep one authoritative source for focus, geometry, and attachment identity. Preserve the supported explicit-target launch surface; remove only scaffolding that the integrated implementation replaces. Acceptance: concrete cleanup completed and reviewed, with a concise account of what was simplified and any justified remaining debt. No requirement to invent an abstraction when the integrated code is already straightforward. Final tests and the GUI demonstration in 1E run on the cleaned-up implementation. ### 1E. Independent acceptance and commit Root owns `test/native.sh` or a separate native-tiling integration leg, runs the GUI demonstration, and summarizes evidence. The reviewer reviews the harness as well as production changes, looking for checks that can pass with a broken GUI. Harness work can be prepared alongside 1B after hook and interface agreement; final acceptance runs after 1D. No other agent edits root's harness files. - Start two isolated daemons with separate sockets and state directories. Never restart or alter the user's live sessions for automated checks. - Insert both orientations. Type unique markers through keyboard and click focus; assert arrival in the intended daemon and absence from the other. - Inspect framebuffer regions for each pane's distinct content, focus indicator, clipping, and erased pixels after shrink. A capture that forces a redraw is not evidence that ordinary output wakes the painter; separately check frame progress. - Check BOTH reported PTY sizes against pane content geometry after window resize. - While A floods bounded output, send a marker to B and require its delivery and painting within a stated deadline. Then make A unavailable; B remains usable. Cover A's shell exit and an initially unreachable target as distinct cases. - Close the window and verify surviving daemon sessions can be attached again. - Run native Wayland at the real display scale. Validate an available real remote transport separately; distinguish Unix-socket, SSH, and QUIC coverage in the report. If no remote fixture is available, finish and report the isolated-daemon gate and runnable build, with remote acceptance explicitly outstanding. Obtain a fixture before claiming SSH/QUIC or real multi-host validation; do not stall unrelated work waiting on external infrastructure or silently count local sockets as remote hosts. Run the ReleaseSafe native build/tests and relevant integration leg. Run repository checks and existing-client regressions if shared modules/build boundaries changed. Report measured deadlines and performance against the agreed single-window budget; do not broaden/repeat testing after a pass without a new change or unresolved risk. ## Subsequent sprints The following packages use the same implementation → refactoring → adversarial review → root acceptance loop. Do not dispatch them while sprint 1 is still under integration or ergonomic review. | Sprint / package | Owned work and dependencies | Required demo / acceptance | | --- | --- | --- | | 2A: picker data | Cancellable host/session discovery and creation, through existing client APIs; depends on stable pane IDs | Slow/unreachable hosts do not block another picker request; stale responses cannot insert into a replaced pane | | 2B: picker UI | Host list → existing/new session list; command routing and insertion use 1C; depends on 2A | Nested splits across hosts; Esc/back creates nothing; session creation errors preserve the workspace; missing-session attach semantics explicitly checked | | 2C: ergonomic gate | Root trials selection, pending direction, insertion geometry, and prefix bindings with user | Fix confusing behavior before extending interactions; focused integration and adversarial review pass | | 2D: refactor and accept | Clean up picker states, discovery ownership, repeated host/session operations, and replaced staging scaffolding after the ergonomic trial | Reviewer approves cleanup/debt record; rerun picker acceptance before commit | | 3A: resize model | Divider geometry, weights, minimum dimensions; depends on reviewed insertion semantics | Nested splits resize predictably; minimum dimensions preserve pane identity; pure layout checks | | 3B: resize interaction | Divider dragging and keyboard resize mode; depends on 3A | Each PTY follows its pane; commands do not leak; clipped glyphs and old pixels remain correct at high DPI | | 3C: refactor and accept | Consolidate rectangle conversion, hit-testing, and resize constraints across keyboard and mouse paths | Reviewed cleanup; rerun resize and high-DPI acceptance before commit | | 4A: lifecycle/recovery | Distinct detach/end, retry and in-place retarget, missing/exited/offline state; depends on picker and stable identity | Detach preserves session; end respects daemon outcome; failure retains pane; restored missing sessions are not silently recreated | | 4B: native persistence | Versioned tabs-aware file, atomic save/load, default workspace ownership, explicit-target temporary workspace | Close/reopen restores layout, targets, focus and sizes; offline target retained; malformed state preserved; terminal layout untouched | | 4C: restore integration | Coordinate attachment restoration, cancellation, recovery UI, and shutdown; depends on 4A/4B | One unavailable host does not delay usable hosts; an unavailable host can return; no orphan pumps or accidental session termination | | 4D: refactor and accept | Deduplicate live/restore attachment lifecycle, simplify save/load ownership, and remove superseded recovery paths | Reviewed cleanup/debt record; rerun lifecycle and persistence acceptance before commit | | 5: final cleanup and review | Review accumulated debt across sprints, finish justified cross-package simplification, then run the complete workflow and final adversarial review | Multi-host acceptance, resource cleanup, persistence failures, high DPI and final ergonomic trial on final code; runnable build and commit | Sprint 4 began on 2026-09-05. Opening cleanup `aa5e977` centralizes native attachment startup and teardown in `Live.start`/`Live.destroy`, preserving the broadcast-before-join shutdown ordering. Independent review, 33 native tests, and `make check` passed before the separate commit. Logs: `/tmp/muxg-lifecycle-baseline-test.log` and `/tmp/muxg-lifecycle-baseline-check.log`. Work ownership: the implementation agent owns lifecycle, recovery UI, and restore integration; root owns the independent persistence module and integration; the adversarial reviewer reviews both boundaries; the acceptance agent owns real session/filesystem integration scenarios. The closing cleanup and recorded-demo acceptance are still required. Package 2A must include cancellation during transport opening as well as request handling. Current `listSessions`/`endSession` paths open with no abort FD and start reply deadlines after dialing. Add narrow cancellable API support where necessary; moving those existing calls to workers alone does not bound shutdown. Test an unresponsive dial, a slow reply, cancellation, and late completion after retargeting. Resolve attach-existing versus create semantics in 2A before wiring 2B. A session can vanish between listing and attachment, and the current attach path may create it again. Establish an existing-only operation/capability or an explicitly refused unsupported path; a preflight list alone cannot provide that guarantee. Preserve legacy client behavior when extending shared wire handling and cover old-daemon compatibility. Reuse this guarantee for persistence in sprint 4. Possible later parallelism: 3A geometry versus root's resize harness, and 4A lifecycle versus 4B serialization after agreeing the serializable model. Discovery and picker UI, frame-loop rendering and input, and persistence plus concurrent workspace edits must stay sequential until their interfaces are settled. ## Delivery record 2026-09-05: independent planning review completed with no material findings left. Resolved frame snapshot ownership, cancellation during dialing, attach/create races, tiny-window behavior, command passthrough, and exact initial target syntax. ### Sprint 1 — 2026-09-05 Packages 1A–1D are complete. Native ownership now lives in `workspace.zig` and `runtime.zig`: a one-tab workspace, stable pane identities, independent pumps, pixel geometry, and owned snapshots for frame construction. The staged-target CLI, `v`/`b` insertion, keyboard/click focus, per-pane resize, retained exit/offline state, and window detach are implemented. No terminal-client interaction policy changed. The fixed binary pixel tree replaces the proposed adapter to the terminal tree: its fixed capacity permits transactional insertion, both divider orientations, and native pixel minima without changing the terminal tree's cell rails or policy. The first version divides each branch equally; weights arrive with Sprint 3. Refactoring completed before final native validation: one geometry policy, one target resolver, shared glyph preparation/clipping/artifact writing, removal of the single-pane process-exit path, and broadcast cancellation before pump joins. Review caught and fixed held command-key repeat leaking into terminal input. The two agents reached agreement on production code and the independent harness. Validation completed: - `ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache deps/zig/zig build native native-test -Doptimize=ReleaseSafe --summary all`: 25 native tests passed; ReleaseSafe GUI built. - `ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache make native-e2e`: 10 existing native checkpoints and 14 new tiling checkpoints passed against the current daemon. Unix/Unix and Unix/QUIC pane pairs are covered. The measured quiet-pane input reached an actual painted frame in 81 ms during a neighbouring flood; window p99 was 72 microseconds against the 20 ms budget. Prefix passthrough produced exactly NUL followed by A through a real PTY (`od`: `00 41`) with the original trial prefix. The ergonomic follow-up uses `Ctrl+\`; its repeated prefix sends `0x1c`, verified with `od` as `1c 41` with PTY signal processing disabled. - Native Wayland: 1698×2760 framebuffer at 849×1380 logical size, 200% density; both daemon sizes, independent coloured pixels, and a non-origin pane click converted from physical pixels to logical input coordinates passed. The full repository gate (`make ci`) passed on the final refactored source: formatting, unit tests, architecture checks, 114 end-to-end scenarios with 38 convergence points, 10 agent checks, and throughput limits. Validation also fixed an unrelated mouse-test false positive: the literal `60` could match the runner PID in the pane's socket label. The assertion now uses `app-history-row-60`; terminal behavior is unchanged. Real remote-host validation passed against `ubuntu@10.78.5.4`, using the dedicated fixture SSH key and a static ReleaseSafe daemon built from the current source. `PYTHONDONTWRITEBYTECODE=1 python3 /tmp/muxg-remote-fixture/validate.py ubuntu@10.78.5.4` exited 0. Both local/SSH and local/direct-QUIC pairs demonstrated independent daemon grids, last-painted frame text, coloured pixels confined to each pane, keyboard refocus, and sessions surviving GUI close. SSH used the actual endpoint fallback; QUIC used a direct `quic://` target. Remote resize/flood coverage was not repeated; those cases passed in the isolated integration gate above. The fixture removed its temporary daemons and remote directory and left the VM running. Logs: `/tmp/muxg-tiling-real-remote.log`; local frame artifacts: `/tmp/muxg-tiling-rfzrpp0b`. Independent review found no outstanding blockers. Sprint commit: `feat: add independent native terminal panes`. The next gate is the user's ergonomic trial before Sprint 2 picker work. Ergonomic follow-up: the user selected `Ctrl+\` to match terminal mux. Updated the handler, event hook, documentation, and existing passthrough checks; the implementer and reviewer agreed with no outstanding findings. ReleaseSafe native tests (25), native integration checkpoints (10 + 14), and `make ci` passed. The first CI attempt hit an intermittent existing CLI pager-wheel assertion; the isolated mouse group and full rerun passed without CLI changes. Logs: `/tmp/muxg-prefix-native.log`, `/tmp/muxg-prefix-e2e.log`, and `/tmp/muxg-prefix-ci-recheck.log`. Sprint lessons and checkable next-sprint actions live separately in [RETRO.md](../../../RETRO.md). At the Sprint 1 handoff, Unix connect and DNS resolution remained synchronous; broadcast cancellation alone did not bound those stages. Package 2A resolves this debt with cancellable connection setup, an owned resolver helper, and explicit stalled-connect/DNS checks. Divider resizing, GUI layout persistence, and tab UI retain their later-sprint scope. ### Sprint 2 — 2026-09-05 Sprint commit: `67a1e976` — `feat: add native host and session pickers`. Packages 2A and 2B are implemented. The host/session picker supplies insertion targets; Add host records a validated shared-catalogue entry, and New session requests an explicit name. Keyboard navigation, modal text input, scaled row clicks, nested splits, back/cancel, retained attachment failures, and error notices are functional. The temporary `--next-target` / `--next-session` flags are removed. Discovery jobs own their target strings, thread, cancellation channel, and result. Request generations and originating tab/pane identities reject stale completions. DNS uses a cancellable exec helper; Unix connect, handshake, partial frame reads, and queued writes share cancellation and deadline handling. Forced teardown reaps owned children even if they ignore ordinary termination. Initial terminal-client stdin remains owned by its command loop or SSH authentication, as appropriate. Existing selection uses the daemon's existing zero-size join-only attach, followed by a size claim after admission; reconnect/resync retains that policy. Explicit creation adds `create_req`/`create_reply` without changing cells or legacy attach semantics. The daemon atomically refuses a name collision without attachment or resize. Old daemons may ignore creation; timeout/cancellation after submission reports uncertainty with no automatic fallback, retry, or deletion. Package 2D cleanup and adversarial review completed: one shared buffered wire, target deep copy, and owned-child cleanup path; one pure insertion plan for preview and commit; removed staging code. Review resolved first-attach stdin ownership, post-handshake QUIC cleanup, forced-child shutdown, modal key repeats, captured tab identity, and known/unknown creation outcomes. The GUI harness reproduced a resize-plus-Enter event-order failure before the fix and verified it afterward. Validation on the final refactored code: - `ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache deps/zig/zig build native native-test -Doptimize=ReleaseSafe --summary all`: 27 native tests passed. - `ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache MUXG_OLD_DAEMON=/tmp/muxg-sprint2-old-mux make native-e2e`: all 10 viewer, 14 tiling, and 10 picker checkpoints passed. The preserved Sprint 1 daemon served existing sessions and safely left explicit creation unanswered. Final flood input-to-painted-frame latency was 81 ms; frame p99 was 81 us. Logs: `/tmp/muxg-sprint2-native-final.log` and `/tmp/muxg-sprint2-native-e2e-final.log`; picker artifacts: `/tmp/muxg-tiling-3e0ymjj5`. - Native Wayland at 200% passed picker rendering, a row click converted from physical to logical coordinates, creation, nested selection, both PTY sizes, and independent pane pixels. Log: `/tmp/muxg-sprint2-wayland-final.log`; artifacts: `/tmp/muxg-tiling-x_9hwpwa`. - Real `ubuntu@10.78.5.4` passed session listing → New session → pane over both actual SSH fallback and direct QUIC, with independent input, last-painted text, confined pixels, refocus, and sessions surviving GUI close. The helper stopped only its dedicated daemons, verified removal of its remote directory, and left the VM running. Log: `/tmp/muxg-sprint2-remote-final.log`; artifacts: `/tmp/muxg-tiling-jq51irp6`. The first repository gate caught a local terminal-client regression: opening a socket consumed a preloaded detach chord as a dial abort. The corrected policy leaves initial local stdin untouched, while GUI cancellation remains active. The new deterministic socket/stdio regression requires both exit zero and a newly created session, preventing an early-success false pass. All 212 client tests and the focused boot (15) and mouse (10) scenarios passed after the fix. `ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache make ci` passed on the final source: formatting, unit tests, shell syntax, and source checks; 115 end-to-end scenarios with 38 convergence points; all 10 agent checks; and throughput limits. Throughput reported 5 ms solo, 42 ms attached, and 90 ms detached repaint. Final log: `/tmp/muxg-sprint2-ci-verified.log`. The user accepted the recorded Sway demo on 2026-09-05: “Lgtm i pass the demo!” Package 2C is closed; with the completed refactoring and final acceptance checks, packages 2A–2D are complete. The 31-second recording demonstrates host selection, new-session creation beside the original pane, an existing session nested below, and directional focus with independent input, using two isolated local daemons. It was shared privately through Tailscale Serve. Recording artifact: `/home/xanderle/.codex/visualizations/2026/09/04/01a06dd2-1bb6-79c3-8a66-bc6bd8f2dedf/native-tiling-sprint2.mp4`. The user was away from the desktop; this is explicit demo acceptance, not a claimed hands-on trial. No interaction changes were requested. See [RETRO.md](../../../RETRO.md) for the closed actions and the timing lesson: pre-implementation agreement was proposed, but acceptance arrived after delivery. Native layout persistence and recovery commands retain their planned Sprint 4 scope. ## Sprint 3 — divider resizing Accepted. The user requested a bounded cleanup before every sprint in addition to the closing refactor. Commit `3eaad75` records the reusable workflow and the reviewed opening cleanup: `Tree.layout` now owns minimum-footprint expansion and flattening for both live layout and insertion planning. This preserves behavior; `make check` and all 27 existing native ReleaseSafe tests passed before the commit. Logs: `/tmp/muxg-sprint3-preflight-check.log` and `/tmp/muxg-sprint3-preflight-native.log`. Packages 3A–3C are complete; implementation is committed as `3eab2a35`. Relative weights preserve split proportions across window sizes. Dragging and prefix + `r` use one minimum-clamped divider operation. Keyboard direction selects the nearest matching-axis ancestor; drag capture ends on release, geometry/focus changes, or keyboard modal transitions. The resize trial defaults are in the spec; recorded-demo acceptance is complete. Adversarial review resolved actual-key repeat leakage, ignored modal keys repeating after exit, and mouse drag state surviving picker or resize-key transitions. The independent OS-size oracle found the inherited one-row native minimum was rejected by the daemon. Native minimums now derive from the shared protocol's two columns and two content rows, plus the GUI header. No daemon or CLI policy changed. Closing cleanup consolidated direction mapping, signed pointer conversion, shared minimum constraints, framebuffer serialization, and input-state lifetime. The pair reported no outstanding production findings. Relative-weight persistence is the planned responsibility of Sprint 4, not a correctness deferral. Validation on frozen production source: - `ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache deps/zig/zig build native native-test -Doptimize=ReleaseSafe --summary all`: 33 native tests passed, with a working ReleaseSafe executable. Log: `/tmp/muxg-resize-final-test.log`. - `ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache MUXG_OLD_DAEMON=/tmp/muxg-sprint2-old-mux make native-e2e`: all 10 viewer, 14 tiling, 10 picker, and 8 resize checkpoints passed. This includes prior-daemon compatibility, the existing QUIC picker leg, and stale asynchronous completion. Log: `/tmp/muxg-sprint3-native-e2e-final.log`. - `python3 -B test/native_resize.py zig-out/bin/mux zig-out/bin/muxg`: 8/8 offscreen checks passed; with `MUXG_VIDEODRIVER=wayland MUXG_TEST_SWAY_FLOAT=1`, 8/8 passed on real Sway at 200%. Both achieved an exact 8x8 tiny window. Only the owned test window was floated; no global compositor settings changed. The harness reads every session's kernel PTY dimensions and retained framebuffer pixels without sending corrective input or forcing a redraw. Logs: `/tmp/muxg-resize-acceptance.log`, `/tmp/muxg-resize-wayland.log`. - `python3 /tmp/muxg-sprint3-demo/scale-check.py`: actual isolated Wayland at 200% → 100% → 150% → 200% passed nested drag/key resizing, cancellation of an in-progress drag on scale change, all-pane PTY dimensions, erased pixels, and batched resize/input ordering. Log: `/tmp/muxg-sprint3-scale-final.log`; artifacts: `/tmp/muxg-tiling-4d7h2jog`. - `ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache make ci`: passed formatting, unit tests, shell/source checks, all 115 end-to-end scenarios with 38 convergence points, all 10 agent checks, and throughput limits. Throughput was 7 ms solo, 40 ms attached, and 90 ms detached repaint. Log: `/tmp/muxg-sprint3-ci-final.log`. The real Sway recording uses three isolated local daemons, showing keyboard resizing, dragging each divider, and resumed terminal input. It contains 119 continuous compositor frames over 23.8 seconds, no audio, and was visually checked. The recording and all daemon fixtures were stopped cleanly. Retained artifact: `/home/xanderle/.codex/visualizations/2026/09/04/01a06dd2-1bb6-79c3-8a66-bc6bd8f2dedf/native-tiling-sprint3.mp4`. Private demo: `https://charizard.folk-amberjack.ts.net/sprint3/`; HTTPS byte-range playback returned 206. Its loopback server is `/tmp/muxg-sprint3-demo/server.py` on port 18766. The earlier Sprint 2 route remains intact. To remove only this route, use `tailscale serve --https=443 --set-path /sprint3 off`, then stop that owned server. The original resize checks use SDL-injected input. The user requested a mouse-focused demo, then explicitly approved it: “demo approved!” on 2026-09-05. This closes Sprint 3's product gate. The follow-up recording uses actual Wayland virtual-pointer motion and button events, with `grim -c` including the cursor. Both dividers moved in both directions, movement stopped on release, and all three kernel PTY sizes matched. No product changes were needed. It contains 114 continuous compositor frames over 22.8 seconds without audio. Log: `/tmp/muxg-sprint3-mouse/record.log`; fixtures `/tmp/muxg-tiling-fprn8mxs` were cleaned. Retained recording: `/home/xanderle/.codex/visualizations/2026/09/04/01a06dd2-1bb6-79c3-8a66-bc6bd8f2dedf/native-tiling-sprint3-mouse.mp4`. Private playback: `https://charizard.folk-amberjack.ts.net/sprint3/mouse/`, verified with HTTPS 206. Its server is `/tmp/muxg-sprint3-mouse/server.py` on port 18767; remove only its route with `tailscale serve --https=443 --set-path /sprint3/mouse off` before stopping that owned server. No hands-on or remote-specific resize trial is claimed. Sprint 4 retains lifecycle/recovery and native persistence scope; its opening actions are recorded in RETRO.md. ## Sprint 4 delivery evidence Implemented lifecycle/recovery, native persistence, and restore integration. Opening cleanup is `aa5e977`. Closing cleanup shares attachment startup for add and restore, consolidates modal held-key ownership and popup reset, and consumes End outcomes through Runtime's captured status. No unresolved production review finding remains. Separate recorded-demo acceptance was not given; the user later explicitly authorized proceeding to Sprint 5, whose final demo includes lifecycle. The native controls are prefix `d` to detach, `x` to request End, and `p` for Retry/Choose session/Detach. End travels over the pane's own connection, respecting the daemon's other-client refusal. Force requires a separate confirmation with Cancel selected. Delayed results retain the originating pane/generation; a force prompt names its session/target and does not appear after focus moves elsewhere. Unknown End outcomes are not automatically retried. Retry and in-place replacement keep pane identity and layout while advancing attachment generation. No-argument launches restore `native-workspace.json`; first launch opens the picker. The default writer has an exclusive sibling lock, and explicit-target workspaces leave its file alone. Strict decoding preserves malformed/unreadable files for the whole run. Atomic saves retain stable IDs, targets, focus and split weights. Restored and replacement attachments are join-only; missing sessions are never silently recreated. Offline targets retry independently with bounded backoff. Validation evidence: - Final ReleaseSafe build and **43 native tests**: `/tmp/muxg-lifecycle-target-context-build.log`. Shared client tests **215/215**: `/tmp/muxg-picker-client-test.log`. - Complete native acceptance: **10 viewer + 14 tiling + 10 picker + 8 resize + 13 lifecycle** scenarios. Initial final run: `/tmp/muxg-sprint4-native-e2e-final.log`; final confirmation-context revision: `/tmp/muxg-sprint4-native-e2e-final-context.log`. - Independent lifecycle rerun on the final binary: **13/13**, `/tmp/muxg-sprint4-lifecycle-final.log`. Its observations include real daemon sessions, kernel PTY dimensions, last-rendered pixels, saved bytes, and a terminal-layout sentinel. Real 100% save to Sway 200% restore also passed all 13 scenarios: `/tmp/muxg-lifecycle-wayland.log`. - Real VM SSH fallback and direct QUIC: **3/3**, repeated on the final binary in `/tmp/muxg-sprint4-remote/final-validation.log`. Saved targets restored after catalogue removal; independent remote kernel PTY sizes matched panes; End removed the SSH session and Detach preserved the QUIC session. Only this run's remote fixture directories/daemons were removed; the VM remains running. - **`make ci` passed**: `/tmp/muxg-sprint4-ci-final.log`, including 115 end-to-end scenarios/38 convergence points and 10 agent scenarios. Throughput measured 7 ms solo, 53 ms attached, and 92 ms detached repaint. The last native-only confirmation change is covered by the subsequent native build/gates. The actual Sway recording is **45 seconds, 1100x700, video only**. It shows three panes across two isolated daemon targets, close/reopen with one target unavailable, that target returning, a shell exit and missing-session restore, explicit creation in place, Detach survival, End, and continued input to the remaining pane. Three continuous window recordings are joined across restarts, omitting the desktop between owned windows. Script and assertions: `/tmp/muxg-sprint4-demo/record.py` and `record.log`. Owned fixture processes were stopped after recording. Retained video: `/home/xanderle/.codex/visualizations/2026/09/04/01a06dd2-1bb6-79c3-8a66-bc6bd8f2dedf/native-tiling-sprint4.mp4`. Private playback: `https://charizard.folk-amberjack.ts.net/sprint4/`, verified with HTTPS 206 byte-range delivery. The loopback server is `/tmp/muxg-sprint4-demo/server.py`, port 18768. Remove only this route with `tailscale serve --https=443 --set-path /sprint4 off`, then stop the owned server; earlier demo routes remain available. The recording does not claim a hands-on trial or a live remote host outage; remote behavior is validated separately. ## Sprint 5 — final review and ergonomic trial The user explicitly authorized this sprint. Opening and closing cleanup reviews found no justified structural refactor. Adversarial review found one missed transition: a delayed shared-session End refusal could open a force menu while command mode was armed, leaving the prefix active after dismissal. The focused fix prevents that menu from interrupting command mode. Other lifecycle, runtime, persistence ownership and stale-generation checks passed the final review. Root's real-daemon command-mode regression failed on the previous release binary (`/tmp/muxg-sprint5-command-before.log`) and passed after the fix. The new matrix in `test/native_lifecycle.py` pauses only owned daemons and covers command, focus, picker, resize, drag, replacement, detach and accepted-End transitions. Another real client supplies shared-session refusals. Non-detach refusal paths assert the actual daemon reason, preventing timeouts from falsely passing. Detach keeps the remote outcome explicitly unknown. Paused daemons resume in cleanup. Validation on the reviewed source: - ReleaseSafe daemon/GUI pair and **44/44 native unit tests**; complete native integration: **10 viewer + 14 tiling + 10 picker + 8 resize + 21 lifecycle**. `/tmp/muxg-sprint5-build.log`, `/tmp/muxg-sprint5-native-e2e.log`. The optional tenth picker scenario ran separately against the retained prior-sprint daemon: `/tmp/muxg-sprint5-old-daemon.log` (passed). - All **21 lifecycle scenarios on Wayland at 200%**, including the new matrix: `/tmp/muxg-sprint5-lifecycle-sway.log`. Scale transitions **200% → 100% → 150% → 200%**, stale-drag cancellation, independent kernel PTY sizes and retained pixels passed: `/tmp/muxg-sprint5-scale.log`. - Real compositor mouse/button movement on the final binary passed both axes, release and PTY checks: `/tmp/muxg-sprint5-mouse/record.log`. - Real VM SSH fallback and direct QUIC lifecycle **3/3** with verified cleanup: `/tmp/muxg-sprint5-remote/validation.log`. Both local demo binaries and the uploaded static remote daemon were built in ReleaseSafe from this tree. - `make native-stress` passed offscreen and on GPU-backed isolated Sway at 200%: `/tmp/muxg-sprint5-stress-offscreen.log` and `/tmp/muxg-sprint5-stress-gpu-sway.log`. Offscreen sampled input maxima were **15.3 ms** for both neighbours. On Sway they were **50.4 ms shared-daemon** and **65.3 ms separate-daemon**, with **113,704,475 bytes / 30.1 seconds** and frame p99 **16,040 us**. Input figures are observed upper bounds with 5 ms polling. Reports: `/tmp/muxg-tiling-0v2lvdd9/stress-result.json` and `/tmp/muxg-tiling-q3hz7zxu/stress-result.json`. - **`make ci` passed**: `/tmp/muxg-sprint5-ci-final.log`, including repository checks, end-to-end and agent scenarios. Throughput: 4 ms solo, 48 ms attached, 91 ms detached repaint. The earlier run received SIGTERM without a reported assertion failure and is not counted as a completed gate. The first Sway stress attempt raced window mapping; the isolated compositor now sizes fixtures when they map. A software-rendered run exceeded the frame budget (p99 55.1 ms) despite responsive input. It is retained in `/tmp/muxg-sprint5-stress-sway-final.log`, not counted as a pass. The final run used Sway's verified AMD/radeonsi renderer with the same budgets and ran without other GUI fixtures on that compositor. Test compositors have been stopped; the sleeping physical desktop was unchanged. Final trial: [private video page](https://charizard.folk-amberjack.ts.net/sprint5/). The main recording is **61.2 seconds, 1100×700, H.264, no audio**, with three continuous clips joined across window restarts. It shows host-picker cancellation, raw output with usable neighbours, keyboard resize, delayed End, Cancel-first confirmation, restore, an unavailable target returning, missing-session recovery, Detach and End. A **23.4-second mouse recording** verifies real compositor input. Both were recorded on isolated Sway at 200% using local daemon targets; SSH/QUIC was validated separately. No video-based latency measurement is claimed. Retained videos are `native-tiling-sprint5.mp4` and `native-tiling-sprint5-mouse.mp4` under `/home/xanderle/.codex/visualizations/2026/09/04/01a06dd2-1bb6-79c3-8a66-bc6bd8f2dedf/`. The owned page/video server is `/tmp/muxg-sprint5-demo/server.py` on loopback 18769; both videos passed HTTPS 206 byte-range checks. Teardown removes only `tailscale serve --https=443 --set-path /sprint5 off`, then stops that server. Earlier demo routes remain available. Scripts and assertions are retained under `/tmp/muxg-sprint5-demo/` and `/tmp/muxg-sprint5-mouse/`. Implementation `1d43cd59` and reviewer approval are complete. The user accepted the final recorded trial on 2026-09-05: “Trial passes! Good work”. No ergonomic adjustments were requested; hands-on use is not claimed. This closes Sprint 5 and the native tiling milestone. SSH EOF/authentication error classification is retained with the follow-up owner/trigger in RETRO.md. Tabs UI and rearranging panes are outside the delivered milestone.