# Active work — cross-machine resume ledger **Snapshot: 2026-08-01.** This file records volatile work that has not landed on `main`. Read it after `docs/agent-handoff.md`. Remove completed entries when their PR merges; do not let this become a second permanent backlog. **Updated later the same day, on a new machine.** Development moved to the laptop; the recovery path in "Repository authority" below was exercised from this checkout and the `githubsucks` alias was absent and had to be added, exactly as that section anticipates. **One lane opened: CI CRDT coverage**, which had been sitting under "NEEDS A LANE" with no branch and no owner since #166. It is implemented on `ci-crdt-coverage` and its block replaces the old one below. **Updated 2026-08-05.** One lane opened: **macOS CI signal integrity** (#215, in review), which this file required a lane for and did not have until review caught it — the #171 defect recurring. Its block is below. **Updated later the same day.** #215 **merged** (`main` @ `12f2970`) and that lane is **rewritten, not removed** — rule 4 removes a lane when its ARC is done, and Stage 2 is the arc. Stage 2 (**hardening**) ran on `ci-signal-hardening`, with its own lane block, its own checkpoint table, and a lane written **before** the PR was opened rather than after review asked for it. **It merged as #216 on 2026-08-05**, which completes the arc: R2 and R4 are retired with discriminating witnesses. **The lane is kept, not yet removed, and that is a deferral rather than a judgement.** Rule 4 would remove it now — but its residue must be re-homed first, or removing it loses the state: **R1** is referred to the async-runtime lane (Q#MCI3) and **R3** is an unresolved possible product defect owned by the process-signal / PTY lane, neither of which has a block here yet. Retiring this arc and opening those two is an **absorption pass**, and doing it inside an unrelated feature PR is how a ledger acquires edits nobody reviewed. **Updated 2026-08-04.** Four PRs landed since: the CI CRDT coverage lane #209, Distribution Stage 1 #211 (released as **v1.1.0**), the post-release accuracy pass #212, and **bottom-panel Stage 3 #213 — which completes Arc 7**. Its lane is **removed** per rule 4: the arc is done *and* its durable facts are in `docs/agent-handoff.md` §1. The distribution and CI-CRDT lanes are rewritten rather than removed, because each keeps named follow-ons. **This snapshot is an absorption pass, taken with ZERO open PRs** — the one window in which a ledger refresh has nothing to re-conflict with, and taken deliberately before a machine move. Nine PRs landed since the previous anchor (#199–#207). Two arcs completed and their lanes are **removed**, their durable facts now in `docs/agent-handoff.md` §1: **Journey Stage 1** (1a plus the whole 1b split) and **test ambient-root isolation**. Discovery and reap-ledger merged a stage each and keep lanes rewritten to what remains. `docs/agent-handoff.md` §1a now carries the whole board — every arc, open lane, deferred item and standing hazard in one view. *(Earlier note, retained because its reasoning still governs:)* **This snapshot is an absorption pass.** Eight PRs merged on 2026-07-29 and 2026-07-30 (#188, #190, #191, #194, #195, #196, #197, #198) and the ledger had drifted to 1,854 lines carrying six lanes whose work was already on `main`. Those six are removed and their load-bearing decisions are in `docs/agent-handoff.md` §1 — rule 4's precondition, satisfied rather than deferred. The file is now 609 lines. **A lane is removed when its ARC is done, not when a PR merges.** Two lanes survive their sub-stage merges and are rewritten to the remaining plan rather than deleted: generated-buffer immutability (Stage 1 merged, Stage 2 not started) and bottom-panel (Stage 2 complete, Stage 3 ahead). The bottom-panel block said so in its own text — *"this lane is not removed at 2B-3's merge"* — and a wholesale removal keyed on "the PR merged" would have discarded live planning. Read each block before cutting it. **#188's lane arrived with #188**, which is the point: with several PRs open, a lane written on `main` for work that lands elsewhere re-conflicts on every merge. Written on its own branch it costs one conflict, at the merge that would have happened anyway. The PTY terminate diagnostic (#176) was the last lane retained past its merge — retained because rule 4 removes a merged lane only *after* its durable facts reach `docs/agent-handoff.md`, and that absorption was unowned. The 2026-07-28 snapshot owned it: #176's facts are now in the handoff (§1's arc bullet and §5's two ops lessons about ticking observers and proving child exit), so its lane is gone. The Lean 4, GPU-terminal-input, inline-math (#172), dired (#169), and terminal config + copy mode lanes were removed the same way — the last of these was #180's work, folded into #182 so two open PRs would stop re-conflicting in this file. **Trust the canonical-base line below over any lane header**: if a PR number appears in `git log --first-parent githubsucks/main`, it has landed regardless of what a lane says. **Two open PRs had no lane here at all before the 2026-07-28 snapshot** — #174 and #171. An open PR is exactly the volatile work this file exists to record, so its absence is a ledger defect rather than a tidy omission: #171 drifted **153 commits** while invisible here, and its still-green old CI run described a tree nobody had looked at since. **When a PR is opened, give it a lane.** All three have since merged — #174, #171 and #186 — so per rule 4 their lanes are gone again and their durable facts are in `docs/agent-handoff.md` (§5 for #174's lesson, §1 for the two framings). ## Repository authority - Canonical development URL: `https://github.com/levineuwirth/pmacs.git`. This ledger uses the normalized local alias `githubsucks` so its refs and recovery commands are identical on every machine. Remote names are otherwise machine-local: `origin` may name this canonical URL, a release mirror, or something else, and therefore has no authority by name alone. - Canonical base at this snapshot: **`githubsucks/main` @ `db1bbe9`** — the tree primitive **#217**, atop `2657568` the macOS CI signal-integrity **Stage 2 #216** (which retired R2 and R4), atop `12f2970` its Stage 1 registry **#215**, atop `f186253`: bottom-panel Stage 3 **#213**, which completes Arc 7, atop the post-release accuracy pass **#212**, Distribution Stage 1 **#211** (released as **v1.1.0**, the first release with prebuilt binaries), and the CI CRDT coverage lane **#209**. Beneath those, `cfc1710`: discovery Stage 1 #207, the ambient-root isolation implementation #206, Journey Stage 1b-3 #205, 1b-2 #204 and 1b-1 #203, the reap-ledger diagnostic #202, the isolation framing #201, the process-signal diagnostic #200 and the ledger absorption #199. **Protocol schema support is `v6..=v21`; the production server-first `Hello` still advertises v20** — two different facts, and #184 landed only the first. **The recovery floor advances with the base**, so the check below now requires `db1bbe9` or newer; a tree at `12f2970` no longer passes — it would lack #216 and #217, both of which this file describes as complete. That is deliberate — a check accepting an older commit than the declared base passes on a tree the rest of this file does not describe. **Lanes below that name an older base have not been re-based; derive their integration surface from `git diff ..main`.** - On the transfer source, `origin/main` named a release mirror at `d3fa632` and lagged badly. On the current destination, `origin` names the canonical URL. This difference is why all recovery begins by verifying URLs and normalizing `githubsucks` rather than trusting `origin/main`. - The shared desktop checkout contained unrelated uncommitted work. The branches below were prepared in isolated worktrees; never clean or overwrite the shared checkout to recover them. Start on another machine by inspecting its remotes: ```sh git remote -v git remote get-url githubsucks ``` If the second command says the alias is absent, add it; if it prints a different URL, stop and resolve that collision rather than overwriting an unknown remote: ```sh git remote add githubsucks https://github.com/levineuwirth/pmacs.git ``` Then recover current refs: ```sh git fetch githubsucks --prune git log -1 --oneline githubsucks/main git worktree list git status --short --branch ``` The `git log` command must expose `db1bbe9` — the base named above — or a newer intentional main. Keep this threshold and the canonical-base line in step: a recovery check that accepts an older commit than the base it declares canonical will pass on a tree the rest of this file does not describe. If it does not, stop and repair the remote/fetch configuration. **This path was exercised, not asserted, at this snapshot** — re-run from an empty directory on 2026-08-06 when the base advanced to `db1bbe9`, rather than having its SHA swapped. `git clone` the canonical URL, add the `githubsucks` alias, `git fetch githubsucks --prune`, confirm `db1bbe9` is an ancestor of `githubsucks/main`, and recover a lane with the three-argument `git worktree add -b githubsucks/` form. All four steps ran clean. **Correction, found by re-running it.** This file claimed the two-argument form fails for a remote-only branch with `fatal: invalid reference`. **It does not fail.** On git 2.55.0 it *succeeds* and leaves a **detached HEAD** — no branch, no upstream, `git status` reporting `## HEAD (no branch)`. Still use `-b`, but for the opposite reason to the one recorded: the danger is not an error that stops you, it is that nothing stops you. Work committed in that worktree sits on no branch, is not pushed by a bare `git push`, and is exactly the "uncommitted work does not travel" hazard in a shape that looks committed. **A documented error message that never appears is worse than no documentation**, because the reader waits for a signal that is not coming. ## Docs absorption after #217 — MERGED as #218 (2026-08-06 09:59Z) **PR #218** — https://github.com/levineuwirth/pmacs/pull/218. **This block was written with the lane's first commit, before the PR existed**, so the row above was filled in rather than invented. That is the standing correction from #171 (153 commits of drift while invisible here) and #215 (no lane until review caught it): this file requires a lane for every open PR, and the way that stops recurring is writing it now rather than after someone asks. - **Branch `docs-absorption-217`**, base `githubsucks/main` @ `db1bbe9` (the #217 merge). `githubsucks/docs-absorption-217` is the authoritative tip — any edit to this block advances past whatever SHA it records, so the ref is the thing to trust. Recover with `git fetch githubsucks && git checkout docs-absorption-217`. - **Docs only.** No `src/`, no crate, no manifest, no test changes. Gates run are fmt, `git diff --check`, `--lib`, and `listview_acceptance`; the full suite is not re-run for a change that cannot reach it, and that is stated rather than left as a gap. - **Scope:** retire the tree and macOS-CI arcs per rule 4, re-home their residue first, file R5 and R6, and carry four durable lessons into the handoff. ### Not in scope Diagnosing R5 or R6, or auditing the three readiness helpers — those are the lanes this one creates, not work it does. Retiring the CI-CRDT, Distribution, or reap-ledger lanes: each still owns undone work and rule 4 does not apply to them. ## Honoring `full_grid` (QoL Stage 1) — MERGED as #219 (2026-08-06 13:41Z) **PR #219** — https://github.com/levineuwirth/pmacs/pull/219. **This block was written with the lane's first commit, before the PR existed** — the standing correction from #171 and #215 — so the row below was filled in rather than invented. - **Branch `full-grid-resync`**, base `githubsucks/main` @ `da56bec` (the #218 merge). `githubsucks/full-grid-resync` is the authoritative tip; any edit to this block advances past whatever SHA it records. Recover with `git fetch githubsucks && git checkout full-grid-resync`. - **Framing `docs/full-grid-resync-framing.md` revision 2**, approved with **Q#FG1 = A**: the sole grid consumer honors the flag by resetting style, clearing, then applying spans. - **First of three QoL stages**, from daily-driver use. Stage 2 is GUI zoom (the machinery exists — `FontMetrics::scale` already derives every dimension and is driven by an attach message in centi-pixels; it is unbound and unpersisted). Stage 3 is long-line wrap/scroll, which is a design round: **no horizontal viewport exists at all** (`view_left` / `col_offset` / `hscroll` match nothing in `src/` or `pmacs-gpu/src/`). Separate branches on purpose — Stage 1 is a contained fix and must not wait behind Stage 3's design. ### What it ships `FG-INV` moves onto the protocol type, where consumer authors read it: a `full_grid: true` delta carries only the frame's **non-default** cells, so a consumer MUST blank its surface first. The rule already existed — in the doc comment of a **private field** on the producer's struct (`src/instance_render.rs:36`), which is why the one consumer never honored it. `emit_cell_delta` joins the existing pure escape-sequence helpers in `src/frontend.rs` (`emit_span`, `emit_status_overlay`, …), and `apply_message` routes through it. ### Why a green suite missed it for so long Seven tests cover the flag. Every one asserts the **producer sets it**; none asserted a **consumer acts on it**, and no runtime reader existed anywhere in the workspace. "Add a test for the flag" had already been done. This is handoff §5's *enforcement and documentation drift apart silently* in a second register, and it is why the fix ships the contract and the consumer together. ### Verification - Three unit witnesses, all bitten: order (reset → clear → spans), **empty spans still clear**, and a differential frame clears never. The empty-spans case discriminates on its own — under the plausible `spans.is_empty()` early return the order test still passes and only that one fails. - PTY acceptance (`tests/full_grid_resync_acceptance.rs`) drives a real `SIGWINCH`. **The mark is anchored to content, not time**: a time-based settle cannot work because a settled pmacs screen emits per-frame bytes forever. Bitten against the original defect: 34,831 bytes after the first painted frame with no `CSI 2 J`. - **What it does not prove:** the suites assert on raw bytes; there is no screen model and no `vt100`/`termwiz`/`vte` dependency. This shows pmacs *emitted* a blank at the right moment, not that the screen ended correct. A terminal emulator in test deps is a candidate, not smuggled in here. ### Not in scope Stages 2 and 3. The `pmacs.terminal` child-PTY `SIGWINCH` path. Any change to *when* `needs_full_grid` is set — the producer's triggers were verified correct, along with per-frame geometry sync and `view_top` reconciliation on shrink. ## GUI zoom (QoL Stage 2) — MERGED as #220 (2026-08-07 08:25Z) **PR #220** — https://github.com/levineuwirth/pmacs/pull/220. **Written with the lane's first commit, before the PR existed** — the standing correction from #171 and #215 — so the row below was filled in rather than invented. - **Branch `gui-zoom`**, base `githubsucks/main` @ `218d2e7` (the #219 merge). Pushed; **`githubsucks/gui-zoom` is the authoritative tip** — the ref, deliberately not a SHA, since writing one into the commit that updates this lane makes it stale in that same commit. Recover: `git fetch githubsucks && git checkout gui-zoom`. - **Framing `docs/gui-zoom-framing.md` revision 5**, approved after four review rounds; revision 5 adds §3.2 for a finding raised against the implementation. **Q#Z1 = (c)** configured base with `None` preserved; **Q#Z2 = additive**; **Q#Z3 = (C)** commands only, no default bindings; **Q#Z4** eager restore inside `install_state_dirs`. ### What it ships `builtin/runtime/zoom.lua`: two settings (`ui.gpu-font-size-base`, `ui.gpu-zoom-step`), three commands (`gpu.zoom-in` / `-out` / `-reset`), and `pmacs.zoom.restore` called from `install_state_dirs`. **No rendering work** — `FontMetrics::scale` already derived every GUI dimension and `apply_font_facts` already re-metriced everything; this drives the preference that existed. ### The findings review caught, none of which was in revision 1 - **Q#Z3 was not implementable.** `keymap_stack::Scope` is `Buffer | Mode | Global` with no frontend identity, and `FrontendEvent` has no command-invocation variant — so neither "bind on GPU only" nor "the GPU asks for a command" exists. Commands ship; the binding waits on **capability-aware keymap resolution**, now a named follow-on. - **The restore seam did not exist.** Builtins and `init.lua` both run *before* `install_state_dirs`, so a `pmacs.state.read` at module load returns nothing, always. `saveplace` and `recentf` never meet this because **both read lazily**; zoom must apply with no user action, making it the **first eager state consumer**. Restore lives at the end of `install_state_dirs` — by definition when state becomes readable, so it cannot be mis-ordered or missed by a future third startup path. - **Every size write clobbered the family.** `set_font` replaces both fields unconditionally, so `{ size = n }` alone silently cleared a configured family until restart. - **The bounds could not carry the round-trip guarantee** (raised against the implementation, not the framing). `ConfigKind::Number` validates finiteness and bounds and *nothing else*, and `on_change` is notified after the value is stored — so it cannot veto. A step of `0.015` is therefore settable, and used raw it broke the framed exact round trip: `16.00 -> 16.02 -> 16.01`. Fixed by quantizing the step **and** the base at the point of use, which is the operation `validate_font_size` already applies to sizes, one level up. Set-time enforcement was rejected: the registry cannot express precision, and a validating wrapper is bypassed by a direct `pmacs.config.set` — the seam `autosave` documents about `interval_ms`. A fifth, documentation-only: the explanation of *why* `0.015` broke said the two intermediates rounded in opposite directions. They do not. `16.015` and `16.005` are exactly `1601.5` and `1600.5` centi-pixels — both exact ties, and half-up sends **both up**. The mechanism is that half-up is not symmetric under negation, so the two roundings accumulate rather than cancel. Corrected in all three copies; no behavior change. ### Verification 15 acceptance tests, four bitten: dropping family preservation fails 3; reverting to the framing's first parser `^(%d+)$` fails 4 including the seam restore (it anchors to end-of-subject and rejects the newline-terminated file the writer emits); hardcoding the 16.0 origin fails the base test; **using the raw step instead of the quantized one fails the unrepresentable-step witness** with `left: Some(16.01) / right: Some(16.0)` **while the pre-existing 0.37 test still passes** — which is exactly why the new case had to be its own test rather than another parameter of that one. ### Not in scope Stage 3 (long lines). Capability-aware keymap resolution — Q#Z3's option (A), deliberately deferred rather than half-built. Per-buffer zoom. Any change to `FontFacts` or the wire. ## Empty-content readiness, a fourth and fifth instance — FOR THE R6 AUDIT Found 2026-08-06 while gating this lane, recorded here because it widens an existing lane's scope rather than starting one. A loaded `--features crdt` run failed `m6_1_pty_raw_mode_disables_kernel_echo` and `m6_1_pty_canonical_mode_keeps_kernel_echo` with **`stty -a output was: ""`** — read-before-write on the child's output. That is the **same family as R4** (readiness predicate satisfied by an empty file) and **R6** (readiness file never published), and it means the readiness-helper audit's scope is not just three `wait_for_file` copies under `tests/`: `src/process.rs`'s own tests carry the shape too. Both passed isolated and the full suite was green on a quiet machine, so this is load-sensitive and **undiagnosed** — recorded as a scope note for the audit, not as a registry row: these were local, and the registry judges red **CI** runs. ## Pre-checkout CI reds — a class the registry has no row shape for Seen 2026-08-06 on #220, three times across two runs (`M4`+`M5`, then `M5` again on the rerun): ``` Prepare all required actions Getting action download info Failed to resolve action download info. Error: Internal Server Error ##[error]Failed to resolve action download info. ``` **This is not a flake and not a test failure.** The job dies inside `Set up job`, before `actions/checkout` — a `grep` for `checkout|cargo|test result:` over the full job log returns **0**. No repo code is fetched, so the red carries *zero* information about the commit, in either direction. Two things follow, and both matter for signal integrity: - **Re-running is a first execution, not a retry-to-green.** The rerun rule governs a test that ran and failed; nothing ran here. The discriminator is objective and cheap — did the job reach checkout? - **It cannot be a registry row as the registry is written.** Matching requires an *exact test selector* plus fragments, and there is no test. Recorded here rather than forced into a shape it does not fit. The second `M5` failure took **4m52s**, which read like a real run; the duration was entirely retry backoff. Duration is not evidence that a job executed — **the log is**. Whether `docs/ci-red-signatures.md` should grow a short non-row section for this class is an open question for its owner, not something this lane decided. ## Long lines (QoL arc) — Stages 3 and 4 MERGED (#221, #222); Stage 5 closes it **Rewritten, not removed.** Rule 4 removes a lane when its ARC is done; this one has **Stage 5** ahead. The durable facts of Stages 3 AND 4 are both in `docs/agent-handoff.md` §1 — rule 4's precondition, satisfied rather than deferred — so what remains here is the Stage 5 plan and only the residue from earlier stages that constrains it. > **RULE 4 APPLIES AT STAGE 5's MERGE, AND NOT BEFORE.** The arc closes > at Stage 5 (GPU horizontal scroll). Q#HS1 split the GPU out > deliberately and time-boxed it; retiring this lane at Stage 4's merge > would have orphaned exactly the half the time box exists to > guarantee, while `truncate` was still a dead end in the GUI. **Do not > remove this block until Stage 5 has merged** — and when it does, the > handoff bullets are what makes removal legitimate rather than lossy. **Branch `horizontal-scroll`**, based on `githubsucks/main` @ `02f3ec3` (the #221 merge). `githubsucks/horizontal-scroll` is the authoritative tip — the ref, not a SHA, since any edit to this block advances past whatever SHA it records. Recover: `git fetch githubsucks && git checkout horizontal-scroll`. **Stage 4 MERGED as #222** (`2b56d16`). Long lines are now reachable **in the TUI**; the GUI half is Stage 5. **Branch `gpu-horizontal-scroll`**, based on `githubsucks/main` @ `2b56d16`. `githubsucks/gpu-horizontal-scroll` is the authoritative tip — the ref, not a SHA. Recover: `git fetch githubsucks && git checkout gpu-horizontal-scroll`. **Status: PR #223 OPEN, awaiting CI. DO NOT MERGE until the user says so.** `https://github.com/levineuwirth/pmacs/pull/223`. Framing revision 4 approved 2026-08-07; review round 1 answered 2026-08-08 (the completion point-predicate defect below). **The tip is the ref, `githubsucks/gpu-horizontal-scroll`, not a SHA written here.** The first version of this line pinned `55faa45` — which the very commit that wrote it invalidated, because recording the PR moved the head. Verify CI against the PR's live `headRefOid`, never against a SHA quoted in a document. G1 pixels (exact conversion via the supported monospace advance); G2 automatic cursor-follow only, zeroing on **both** wrap transition and `BufferSnapshot`; G3 monospace-only by the existing font contract; G4 minimap unchanged; G5 accepted whole — **all twelve witnesses written and mutation-tested** (see below). **Scope: local GPU viewport state.** No wire message, no protocol bump, no command surface, no minimap movement. **ONE APPROVED EXCEPTION TO THAT SCOPE** (user, 2026-08-08; recorded in the framing doc at §1.2a). Q#G5's TUI-parity witness asks for agreement that is "checkable rather than asserted". Two tests in two crates asserting the same literal is not that — it is exactly the structural duplication `pmacs-protocol::scroll`'s own module docs condemn, and that module exists because **this arc already shipped that defect** (the scroll indicator, fixed in one copy and left wrong in the other). So the follow rule moved to `pmacs_protocol::scroll::follow_left`, beside `classify`, and **both** frontends call it: `src/editor.rs::horizontal_follow` delegates, and the GPU converts px ↔ columns around it (exact by Q#G3). What it costs: Stage 5 touches `src/editor.rs`, which the scope line above does not cover. What it buys: the two frontends *cannot* choose different edges. The approval turned on what it does **not** do — it moves no viewport state, adds no wire message, and needs no protocol-version bump. **Review round 1 (2026-08-08) — one non-zero-offset defect, fixed.** `completion_anchor_px` reused `survives_code_clip_left` and passed `line_height` as the horizontal extent: **a vertical dimension standing in for a horizontal one**. An anchor up to a line-height left of the gutter survived, and `completion_dropdown_rect` clamps `ax` against the right margin only — so the popup painted over the line numbers. Now a point predicate, `screen_x < code_clip_left()`. **The lesson is about the witness, not the predicate.** The existing test placed the anchor 200px off-left, which fails a width-based predicate too — it stayed green straight through the defect and the mutation battery agreed with it, because the battery only ever asked whether *removing* the check was caught. **A boundary this stage cares about must be tested AT the boundary**: the replacement straddles the edge by ±0.05px and additionally asserts the popup's own left edge stays out of the gutter, which is what makes "`completion_dropdown_rect` needs no left clamp" a checked claim rather than a comment. **Its first finding corrects Stage 4's framing.** §1.3 there said the GPU "needs a mechanism that does not exist", and I endorsed the Stage 4/5 split partly on that basis. Half of it was right — `Scroll::horizontal` really is discarded, because glyphon 0.11 never applies it — but the document `TextArea` already carries an explicit `left` origin and a `TextBounds` clip, and shifting that origin is the same "paint from 0, clip at the edge" shape the grid uses. The split stays right (the three consumers below are real work), but it was justified partly by an overstatement. The real work is applying **one** offset to the three consumers Stage 4's framing did name correctly: the caret (`code_byte_px`), decoration geometry (`push_glyph_extent_rects`), and hit testing (`gutter_aware_rel_x`). **No wire and no version bump** — the GPU owns its viewport locally, exactly as it owns `scroll_top`. **What Stage 4 shipped**, beyond the `view_left` contract below: - **`Viewport::visible_cols` — one clip rule, five adopters.** Review found the first version had translated the base glyph walk and nothing else, so syntax styling, diagnostic underlines, search washes, `BufferStyleOverlay` and the selection painter all kept painting at absolute columns: decorations drifting off the characters they describe, only once a window had been scrolled. The selection painter was worst — it asked `pos_to_display` through the live context, which returns `None` left of the edge, so a selection starting off-screen painted **nothing at all**. A second review round caught that my reason for letting selection keep its own copy of the rule (a width the viewport supposedly lacked) was **false**: the render viewport is already `rect.size.cols - gutter_w` with an origin past the gutter. It now takes that viewport. `StyleSpanOverlay` / `VirtualCellOverlay` stay untouched — viewport-relative by contract. - **The `ui.line-wrap` description now names what `truncate` costs**, closing the #221 gap where only the toggle's status message said it. - **`R7`** in `docs/ci-red-signatures.md` — an unrelated, unreproduced `pmacs-gpu` managed-retry `BrokenPipe` under full-sweep load. First incident this session with a complete signature, so a matchable row rather than a `U` note. **What Stage 5 shipped, beyond the offset itself:** - **`crop_to_code_clip_left`, and `survives_code_clip_left` delegating to it.** One boundary rule, so a caret the crop would discard is never painted. The washes **crop** rather than drop — a selection running in from off the left edge must paint the part that IS visible, which is the same boundary Stage 4's review caught the TUI painter getting wrong. - **Twelve witnesses, each mutation-tested.** Eleven production mutations (unshifted wash x, uncropped wash, unshifted math origin, uncropped math rule, untested caret left edge, missing snapshot reset, missing wrap reset, unhidden completion anchor, unscrolled glyphs, inverted hit-test sign, pixel-instead-of-column snap) each fail the intended witness as an **assertion** failure, not a compile error. The minimap-stability witness was mutation-tested separately by threading the offset into `minimap_vertex_bytes`. - **The glyph-motion witness exists because the first pass lacked it.** The gutter byte-identity test's "the code area must actually have moved" assertion is satisfied by a decoration wash and the caret alone: it **passed with `TextArea.left` pinned to `text_left`**. The mutation battery caught that, not review. Its replacement isolates the glyph layer — no decorations, and a source line carrying no caret. - **`R8`** in `docs/ci-red-signatures.md` — a **deterministic**, pre-existing `m4_acceptance` listview failure, confirmed on `main` by merge-base control. Not this lane's, and deliberately not fixed here. **Answered by the user 2026-08-07:** - **Q#HS1 — the GPU is Stage 5, not Stage 4.** A conscious, bounded divergence rather than a repeat of Stage 3's accidental one. The time box is concrete: Stage 5 is the *immediately-next* QoL lane after Stage 4 merges, `wrap` stays the default until it lands, Stage 4's release notes state the asymmetry, and the `truncate` affordances name the GUI gap while it exists. - **Q#HS2 — automatic only.** The cursor-visibility pass gains a horizontal component; no commands, no bindings, no new interaction island. Explicit `ui.scroll-*` is deliberately out. - **Q#HS6 — `wrap` stays the default.** Coupled to Q#HS1: with the GPU deferred, a `truncate` default would ship a mode that is navigable in the TUI and a **dead end in the GUI** for anyone who never opened the setting. Revisit after Stage 5, on use evidence. - **Q#HS7 — ACCEPTED.** `view_left` is an unsnapped window display column; the effective edge is derived **per line**; a bisected wide glyph's trailing cell renders as styled blank and is designated to the **glyph's start** byte; a straddling tab's surviving cells keep the **existing** forward rounding to the byte after the tab (`src/text_view.rs:224` — preserved, not chosen). Together these make the mapping **total over visible cells**, which is the (d) invariant. The discriminating witness is multi-line, with glyph widths differing at the same column. - **Q#HS5 — APPROVED: yes, persist, no `DESKTOP_VERSION` bump** — conditional on `#[serde(default)]` **and** a literal v1 JSON fixture omitting the field, asserting restore at zero. Both conditions are part of the approval. - **Q#HS3** is re-confirmed rather than open (per-window, per Q#LL2); **Q#HS4** is deferred, live only if explicit commands arrive. **The reasoning worth keeping from the withdrawn Q#HS7(c).** Revision 2 voted to snap `view_left` to a valid boundary when set. That cannot exist, and the reason generalizes: `view_left` is ONE per-window column, but *"does column N bisect a wide glyph?"* is a **per-line** question. No setter-time value is canonical for every visible line, and snapping per line instead would break the vertical alignment a column-oriented view exists to provide. Recorded because the same trap waits for any future window-wide value derived from per-line content. **One correction carried into revision 2.** Revision 1 claimed the "unreachable past the edge" caveat is recorded in the setting's description. It is not — `builtin/runtime/linewrap.lua:23` says only "truncate at the edge"; the word appears in the toggle's status message and a source comment, neither of which a user sees if they set the mode in `init.lua`. **A real, small user-facing gap shipped in #221**; amending the description is now a Stage 4 deliverable (framing §6). ### What Stages 3 and 4 shipped that Stage 5 must live with - **`ui.line-wrap` is BUFFER-local** (`ConfigKind::Enum`, `wrap`/`truncate`, default `wrap`). Q#LL2 recorded the consequence and deliberately deferred it: `view_left` is unambiguously **per-window**, because two panes on one buffer must scroll independently exactly as they already hold independent `view_top`s (`src/desktop.rs:92`, `src/window.rs:374`). So the two halves of one user-facing concept land at different scopes. Emacs effectively does this and it is survivable — but Stage 3 signed up for it as *a decision*, and Stage 4 is where the bill arrives. - **`truncate` is the mode Stage 4 makes navigable.** Today text past the right edge is not merely off-screen but **unreachable** — and that is stated **only** in `ui.toggle-line-wrap`'s status message and a source comment, **not** in the setting's description (`builtin/runtime/linewrap.lua:23` says just "truncate at the edge"). A user who sets the mode in `init.lua` and never invokes the toggle is told nothing. Amending the description is a Stage 4 deliverable (framing §6); the status message is revisited when scroll lands. - **Under `wrap`, horizontal scroll is meaningless.** Stage 4's surface is therefore conditional on the mode, which is a coherence question (one concept, two behaviors) and not only an implementation one. - **The scroll indicator's `wrap` path takes byte percentages** from `pmacs-protocol::scroll`. It is vertical-only and Stage 4 does not change it — recorded because "scroll" in this lane means horizontal and the two must not be conflated in review. ### Ground truth gathered for the framing (verify before trusting) - **There is no horizontal scroll anywhere in the tree.** No `view_left`, `scroll_left`, or `hscroll` in `src/` or `builtin/`. This is greenfield, not an extension. - **`paint_line` starts every walk at column 0** (`src/text_view.rs`), which is the same walk Stage 3 rewrote for wrapping. A `view_left` enters here, and the wrap rule (`advance_wrapped`) must stay written exactly once. - **The GPU cannot honor horizontal scroll through cosmic-text.** `Scroll::horizontal` is discarded throughout, because **glyphon 0.11 never applies it when placing glyphs** — documented at `pmacs-gpu/src/main.rs:1611`, `:6316`, `:8020` and asserted by tests at `:16266`, `:16337`, `:16737`. The GPU's Stage 4 half needs a different mechanism entirely. **This is the fact most likely to invert the cost estimate**, exactly as the "both frontends consume the same `CellGrid`" error did in Stage 3 revision 1. - **`view_top` is persisted per leaf** in `SavedLeaf` alongside `cursor`, at `DESKTOP_VERSION = 1` (`src/desktop.rs:33`). A `view_left` that survives a restart needs either a defaulted field or a version bump — a decision, not an afterthought. - **`scroll_window` carries the cursor with the scroll** to defeat the renderer's "auto-scroll to keep cursor visible" pass, whose comment already records the hazard: an unconditional snap-back makes explicit scrolling feel stuck (`src/editor.rs:3624-3628`). A horizontal analog faces the identical problem, and Q#LL3 deferred the choice — drag the cursor, or let the next motion snap back — to this stage. - **`goal_col` is the existing column-memory field** (`src/window.rs:376`, cleared at seven sites in `src/editor.rs`). Its relationship to a horizontal offset is unexamined and is a framing question, not an implementation detail. ### Gate note this lane inherits Stage 3 put eight broken version assertions on CI by running `CLAUDE.md`'s short gate list instead of `docs/agent-handoff.md` §3's, which includes a full sweep. **§3 is the authority.** **Stage 4 should not need a protocol bump at all** — Q#HS1 puts the GPU in Stage 5, and `view_left` is per-window TUI state with no wire. If that changes, §3's protocol-bump form is `cargo test --workspace --no-fail-fast -- --skip basedpyright` in **both** feature configurations. **`--workspace`, not `--tests`**, and the distinction is not cosmetic: `--tests` selects 108 targets where `--workspace` selects 110, and the two it drops are **`pmacs_protocol` and `pmacs_gpu`**. Stage 3's own *remediation* sweep used `--tests`, so it never ran `pmacs-protocol`'s 25 tests — including the `scroll::classify` tests that lane had just written. They passed, but by luck, and a correction that reproduces the shape of its own mistake is worth naming. ### Not in scope `M-q` / auto-fill / reflow. Word wrap as a mode value — a named future third choice. Bidi/RTL. Soft-wrap gutter indicators. ## Tree primitive (P5) — MERGED as #217; adoption is the open work **The lane is gone, not the work.** Rule 4 removes a lane after merge, and the primitive is merged: `listview` rows take optional `depth` and `id`, collapse is primitive-owned, selection re-seats by id, and the LSP outline is the one adopter. `COHERENCE.md` §14 is ◐ and §20 says adoption rather than construction, which is where the remaining work is recorded — **not here**, because none of it is in flight. What review found is worth carrying forward, since all four were invisible to a passing suite: - **A selection test that toggled the root proved nothing.** The root sits on line 1 before and after collapsing, so it passed unchanged under the line-keyed re-seating that id-keyed re-seating replaced. A moving-node witness (`tr_4`) was the fix. - **TAB was bound on every listview**, so flat panels lost their fall-through to the global binding and the Q#P3 read-only intercept. Delegation restored it. - **`item` was effectively required.** `line_to_item` is sparse when a row omits the optional `item`, and `seat_cursor` took `#` of it — a display-only tree stranded the cursor on the header. Every existing test supplied `item`, so none could reach it. - **"Opaque, compared by equality" was two contracts.** Selection uses `==`; collapse keys a table, which consults no `__eq`. Narrowed to string-or-number, and with it uniqueness and not-NaN, all enforced where rows enter. The last two are the durable lesson and it is in the handoff: **an optional field that a data structure's shape depends on is not optional**, and a contract that two mechanisms must honour is only as strong as the weaker mechanism. ## Leaked daemons from `gpu_invocation_acceptance` — NEEDS A LANE **Found 2026-08-05 while cleaning up after the tree-primitive work. No branch, no framing.** - **42 orphaned `pmacs --daemon` processes** were resident on the development machine, **the oldest 3 days 23 hours old**. All had been **reparented to systemd** (`ppid=1`) and all had **deleted sockets**, so nothing could ever reach or reap them. - **Source: `tests/gpu_invocation_acceptance.rs`** — the one-command tests, whose daemons carry `--socket /one-command.sock`. The tempdir is cleaned up; the daemon is not. - **Rate measured, not estimated: 3 per sweep.** A single isolated `--features luajit,crdt` sweep leaked exactly three. 42 is what several days of sweeps accumulate to. - **This predates the tree work** — the oldest is four days old — so it is a standing leak, not something a current lane introduced. **Why it belongs to the reap-ledger family.** This is precisely the shape that lane exists for: a process that outlives its supervisor with nothing left watching it. The ledger arms only for `spec.group`, and these are daemons spawned by a test harness rather than by compile mode, so **nothing in the existing ledger covers them**. **Why it matters beyond tidiness.** Dozens of resident daemons were present during every local sweep run this week, including the one that produced the unclassified failure recorded in the **tree-primitive lane above** (and, in full, in that lane's framing §6a). That makes them a **rival explanation** to the shared-target-dir mechanism for that occurrence, and neither can be tested against it now — the signatures were not captured. A leak that quietly changes the environment of every subsequent test run is a measurement problem as well as a resource one. **First questions for whoever takes it:** does the test harness fail to reap, or does the daemon fail to exit when its socket disappears? Those have different fixes, and the second would be a product defect rather than a test one. ## macOS CI signal integrity — ARC RETIRED (#215, #216); residue re-homed **Both stages merged and the arc is done**, so rule 4 removes the lane. Stage 1 built `docs/ci-red-signatures.md` and audited the incumbents; Stage 2 retired **R2** and **R4** with discriminating witnesses. The framing `docs/macos-ci-signal-integrity-framing.md` and the registry both survive the lane — the registry is the durable artifact this arc existed to produce. **Retiring it required re-homing the residue first**, which is why this did not happen at merge. A lane removed while it still owns undone work does not close that work, it hides it. What it owned: - **R1** (supersede cancellation budget, *measurement design*) → the **async-runtime lane**, below. Its retirement condition is Q#MCI3: replace or justify the measurement. Widening the budget would make it pass and measure nothing more. - **R3** (live-leader EPERM, **UNRESOLVED — possible product defect**) → the **reap-ledger lane**, below, which already parks every disposition change pending exactly this question. - **R5** and **R6**, added 2026-08-06 and neither diagnosed → the async-runtime lane and a **readiness-helper audit** respectively. ### The rows are the state now, not this block An occurrence scan on 2026-08-06 (last 25 `main` runs: 23 green, 2 red) turned up two things worth recording as method rather than as trivia. **A signature very nearly got misfiled by theme.** `main` run 30710662474 is the same test as R3, with `EPERM` and `measured_group=unobservable(ESRCH…)` — and R3 requires `leader=live` where that run reads `leader=exited(signal SIGUSR1)`, which is R2's exact fragment. Read by test name and shared fragments it looks like R3; read by required fragment it is R2, four days before R2's retirement, on the *other* macOS flavor. **Attaching a live unresolved product-defect row to an occurrence of a retired test race is precisely the error the exact-fragment rule prevents**, and it was caught by checking the fragment rather than the resemblance. **A second red matched nothing at all** and became R5, rather than being folded into R1 because both involve supersede under a deadline on macOS. Sharing a subject is not sharing a signature. ### Async-runtime lane — NOT STARTED, owns R1 and R5 No branch, no framing. Owns **R1** (Q#MCI3: the 50ms budget starts before the second dispatch and is consumed by the test's own pump, so it measures when the test was scheduled) and **R5** (`stream_supersede_delivers_cancelled_to_on_close`, `async pump deadline exceeded`, undiagnosed). Filed together because both are the async runtime under a deadline; they are **not** assumed to share a cause. ### Readiness-helper audit — NOT STARTED, owns R6 No branch, no framing. **Three independently written readiness helpers now exist**, and they disagree: `vterm_stage2_acceptance`'s waits for expected content (hardened by #216), `wait_for_published_file` was fixed with it, and `tests/bottom_panel_stage1_acceptance.rs:2446` carries only the zero-byte half. R4's disposition predicted this recurrence under a new selector, and R6 is it. **Scope is the audit, not the call site**: how many helpers exist, whether they can be one, what each promises. Patching `acc28` alone leaves the question open under a fourth selector — which is the same mistake as fixing `wait_for_file` and leaving `wait_for_published_file`, already made once in this arc. ## CI CRDT coverage — MERGED (#209); kept for its three follow-ons **Rewritten, not removed.** Rule 4 removes a lane when its ARC is done; the coverage itself has landed but three named follow-ons have not. Durable facts — the corrections, the traps, the census tool — are in `docs/agent-handoff.md` §§1/5 per rule 3, so they are not repeated here. - **MERGED as #209** (`main` @ `c5f7501`, 2026-08-01, two review rounds), framing `docs/ci-crdt-coverage-framing.md` revision 5. The lane had sat under "NEEDS A LANE" with no branch and no owner since #166. - **CI compiles and runs the CRDT corpus for the first time.** `Test (crdt)` runs **3,766** tests where none ran before; `M10 Perf Gates (crdt)` covers two suites no workflow had ever named; `cargo clippy --features crdt` is enforced going forward rather than being a required local gate that rots in CI. **275 of 279 dark tests recovered**, the other four excluded with stated reasons. - Branch `ci-crdt-coverage` retained; it carries nothing unmerged. ### Two CI weaknesses Stage 3 exposed — decisions, not defects Both surfaced while gating #213 and belong to this lane because it owns the crdt job. - **CI does not pass `--no-fail-fast`, so a multi-suite break reports as a single-suite one.** `cargo test` halts after a failing binary, so #213's first crdt failure showed **one** suite when a local `--no-fail-fast` sweep of the same tree showed **thirteen**. The Stage 3 census hit the identical trap and recorded it; CI has it too. The cost of the flag is running the remaining suites on a red build, which is usually what you want when diagnosing. - **The crdt job pairs the heaviest build with real-PTY deadlines.** It installs lavapipe, builds the full workspace with `crdt`, runs the largest test count, and includes real-PTY smokes with **5-second** waits. #213 saw two different such suites fail on two runs of the same commit. One of those was a real regression the flip caused; the other was load. **That ambiguity is the problem** — a job where noise and signal look alike trains people to rerun rather than read. **The triage half of this is now owned by `docs/ci-red-signatures.md`** — signature-keyed rows and a rerun rule that refuses to treat a green rerun as an all-clear. What remains here is the job-cost question: whether the crdt job should carry real-PTY deadlines at all. Candidate: longer deadlines for real-PTY assertions in this job specifically, or serialize the PTY suites. ### Still owned by this lane, not yet done - **The macOS `crdt` leg.** Deliberately ubuntu-only at first, "decide about macOS from evidence." **That evidence now exists** and is favourable: the non-crdt macOS legs pass at 3,474 (thirteen fewer than ubuntu's 3,487 — `cfg`-compilation of the Linux-gated process tests, not lost coverage), and no crdt-specific failure appeared anywhere. This is now a small, decidable addition rather than an open question. - **The `--lib --features crdt` flake.** `process::tests::setsid_escapee_is_not_reaped_and_teardown_reclaims_readers` failed ~1 run in 5 with `active_reader_probe` returning `None`. **It did not reproduce in #209's runs** — `--lib --features crdt` was 2,081/2,081 and the full serialized sweep clean — but that is *not* evidence against the hypothesis: every run was `--test-threads=1` and the trigger was observed under **parallel** full-suite load. The `drain_until` explanation (draining for `Started` also ticks, and a tick can reap the leader before the following probe) remains an inference from control flow, not a falsified root cause. **Its own PR** — a product-defect hypothesis, where everything in #209 was workflow configuration. - **`InstanceCapabilities::crdt_replica`'s serde default.** `#[serde(default = "default_true")]` is a *third* default mechanism, unconditional, and therefore disagrees with the `Default` impl in a non-CRDT build. Exercising it needs a self-describing format and `pmacs-protocol`'s only serde dependency is postcard, which is not one. Adding `serde_json` as a dev-dependency to test a divergence #209 did not introduce was refused as scope creep. Parked, not forgotten. - **The two unattributed CRDT failures from #178's round-2 gating.** No test names were captured, so there is nothing to reproduce. Recovery, only if a follow-on needs the branch: ```sh git worktree add ../pmacs-ci-crdt \ -b ci-crdt-coverage-followup \ githubsucks/ci-crdt-coverage ``` ## Distribution (P8) — STAGE 1 SHIPPED as v1.1.0 (#211) **Rewritten as a lane rather than removed**: the arc is not done — Stage 1 was scoped to binaries-on-tag and everything else in §17 is untouched. Framing `docs/distribution-stage1-framing.md` revision 3. Durable facts are in `docs/agent-handoff.md` §1. - **Released 2026-08-01.** `v1.1.0-rc.1` (prerelease) then `v1.1.0`, **both cut from the same commit `000b6cd`** — the #211 merge SHA — so the final release was built from byte-identical source to the one whose artifacts were verified. - **Verified against the DOWNLOADED artifacts, both tags, not the build logs:** archive member lists (exactly `pmacs`, `pmacs-gpu`, README, two licenses), executable bits, absence of `pmacs-audit` / `pmacs_fake_lsp` / `pmacs_fake_mcp`, `pmacs --version` = `1.1.0` and `pmacs-gpu --version` = `1.1.0 (protocol v21)`, `SHA256SUMS`, the glibc floor, and CRDT presence **against a non-CRDT negative control** (1,576 `loro` strings shipped versus **0** in a control build — the control is what makes the number mean anything). - **`pmacs` alone needs only glibc 2.34; `pmacs-gpu` needs 2.35.** The stated floor is the pair's, 2.35, not the more flattering single-binary number. That is why RHEL 9 (2.34) is excluded even though the editor binary would run there. ### Still owned by this lane, not yet done Each is a stated non-goal of Stage 1 (framing §5), not an oversight. **The next increment is a decision about which of these the project wants, not a continuation of a plan.** - **Channels** (stable/nightly), **in-place update**, **rollback**. - **Signing and notarization.** macOS binaries are Gatekeeper-quarantined today; the release notes say so. - **RHEL 9 and older glibc** — needs a container or cross-build, not a runner change. - **Intel macOS**, **Windows**, **reproducible builds**, **package-manager distribution**. - **Runtime dependency checking.** §17 asks first launch to identify optional external tools; `/bin/sh`, `stty`, git and tar are documented and never checked. That is §18 onboarding work. ## Discovery lane (P4) — STAGE 1 MERGED (#207); STAGE 2 IS NEXT **Rewritten, not removed.** Rule 4 removes a lane when its ARC is done; this arc is not — Stage 1 built the command surface and everything that needs a Rust change is still ahead. Stage 1's durable facts are in `docs/agent-handoff.md` §1. - **Landed:** eleven `help.*` commands over the existing registries, indexed by `M-x help`, with `editor.describe-command` / `editor.describe-setting` kept as forwarders. No Rust, no protocol change. §5 moved substrate-without-surface → **Partial**. - **Stage 2 candidates, in rough dependency order:** 1. **Richer M-x rows** — a **protocol change**: `MinibufferPrompt.candidates` is `Vec`, while `CompletionPopupRow` already carries `kind`/`detail`, so the wire pattern is solved and the bump is the work. 2. **`Command` gains title / category / aliases / flags / arg-schema** — a Rust type change across ~147 definition sites. MCP currently works around the missing schema by stuffing rendered JSON into the description string. 3. **Predicate evaluation.** `Command.predicate` is read at `src/help.rs:76` and one test, and **evaluated nowhere**. Starting to evaluate it makes commands stop being invocable, so it needs its own decision about what "unavailable" means at each call site — M-x, dispatch, menu. `discovery_acceptance`'s `d9` pins today's behaviour with a *raising* predicate, so that stage must change the pin knowingly. 4. **Help-layer unification.** `src/help.rs` is still orphaned. Stage 1 funnels every command through `pmacs.editor._show_help` and renders via named per-subject functions, so the work is enumerated: replace the four subjects `src/help.rs` covers (key, mode, hook, buffer) and **write three new Rust renderers** for settings, lists and apropos. 5. **The help prefix.** Deliberately untouched by Stage 1 — the decision is one for the whole family, and `C-h` is **not** free (non-kitty terminals cannot disambiguate Ctrl+Backspace from Ctrl+H; both produce byte 0x08). `F1` / `C-c ?` / a rebind are the candidates. - **Two Stage-1 facts a Stage-2 author needs.** Completion is **assistance, not validation** — `resolve_accepted_value` returns the literal typed text when no candidate is selected, so closed-set acceptance is unbuilt Rust work. And **`invoke_interactive` is not the M-x path**; the forwarders learned that the hard way in CI, since `pmacs.command.invoke` is a real caller of the old names. ## Generated-buffer immutability lane (Arc: workbench primitives) — STAGE 1 MERGED; STAGE 2 IS NEXT **Framing #188 (revision 7) and Stage 1 #191 are both on `main` @ `4cd4a7b`.** Their durable facts are in `docs/agent-handoff.md` §1 — including the contract-ownership rule (the framing owns the acceptance criteria; an implementation adopts them and may not restate or narrow them) and why `dired`/`listview` were the correct first two families. - **Stage 2 is not started and has no branch.** It owns everything with new Rust in it: `Buffer::apply_generated_edit` + `GeneratedOutcome` + the `{ generated = true }` option and its `run_buffer_edit` arm; `set_generated_contents` reimplemented over it; Q#GB10's path-backed refusal and `mark_clean`; Q#GB15's `identity_protected`; Q#GB13/GB18 for `compile.lua` and the search panel; Q#GB5's `ensure_slot` lock; the remaining 13 write sites; and the three `compile_mode_acceptance` intruder tests converted per Q#GB12. - **It collides with dired Stage 2b**, which changes `paint`'s callers. Whichever starts second integrates first. ## Reap-ledger silent failures — MERGED (#202); kept for its parked follow-ons - **Branch `reap-ledger-silent-failures`**, worktree `../pmacs-reap-ledger`, based on `githubsucks/main` @ `22df6ab`. `docs/reap-ledger-silent-failures-framing.md`, **revision 4**; approved at revision 3 after two review rounds (round 1: three blocking, two major; round 2: two blocking, two major; all accepted). Revision 4 records implementation findings, not a new design round. - **All four bets resolved.** Bet 1 (every site takes a directed outcome) and Bet 2 (every consequence is reachable) hold. **Bet 3 resolves the shutdown coupling as real and measured** — under 500ms with a failed force-kill plus an errored probe, versus the full 2s bound with only the force-kill failing. **Bet 4 is falsified: no reporting channel exists**, so reporting becomes its own lane. - **The in-drain pin's first fixture was vacuous, and the bite caught it.** `poll_one` TERMs the group on leader exit, so an untrapped descendant died before writing its late marker — absent on *both* paths. With the seam reverted the pin failed only the consumed-plan check, never the content assertion. Fixed with `trap '' TERM` behind the readiness gate. - **Gates: 10/10 green** on the pushed tree, all five bootstrap-storage variables controlled — fmt, diff-check, clippy, `--lib` (1888), `--lib --features crdt` (2073), compile-mode (67), copy-mode in both feature configurations (18/19), M4 with the basedpyright skip (149), required GPU (221). The five new process pins ran **15/15** as a repetition set, since supervisor tests are load-sensitive. - **Unparked from PR #200's §5.** #200 retired the premise that justified the ledger's leniency and deliberately changed no disposition; this lane owns what it refused. - **Now also owns R3**, re-homed 2026-08-06 when the macOS CI signal-integrity arc retired. `docs/ci-red-signatures.md` R3 is a group-directed `kill` returning **EPERM while the leader was observed live**, with `measured_group` — the one field able to disagree — unreadable. It is the **same group-target question** #176 and #200 circled and this lane parks every disposition change pending, which is why it lands here rather than staying with a retired CI lane. Its registry entry is explicit that it is an **unresolved possible product defect** and that **a green rerun never retires it**; that constraint travels with the row, not with whoever inherits it. Note what it is *not*: R2 is the same test with `leader=exited(signal SIGUSR1)`, a test race, retired. An occurrence scan on 2026-08-06 nearly filed a second R2 occurrence here as R3 on the strength of the shared test name and shared `EPERM` fragment. **`leader=live` is the fragment that separates a product-defect candidate from a fixed fixture bug**, and it is the whole reason the row lists it. - **Four sites, not the two #200 named.** In the persistent ledger: a probe error of any errno drops the entry and cancels escalation; a failed escalating `SIGKILL` is marked as succeeded, so **no later tick retries it**; and `shutdown()` discards its own force-kill result the same way — on the path that exists specifically to stop a leak at editor exit. Those last two are **distinct, not cumulative**: `shutdown()` force-kills every entry with no `!entry.killed` guard, so a failed escalation still gets one attempt at exit, while a failed force-kill leaks the group past exit with nothing left to try. **Plus the in-drain twin** `final_drain_runtime`, which collapses every errno to "dead" while no tick runs; a false "dead" there cancels the readers, so its failure mode is truncated output rather than a leaked process. - **The blast radius is exactly what the ledger exists for:** a TERM-ignoring descendant that outlived its leader with output redirected. Neither leader state nor reader state can see it; only group liveness can. A silent drop leaks the one process nothing else is watching. **Journey step 9 (build/test), not step 8** — the ledger arms only for `spec.group`, which spawn rejects for PTY mode, so no terminal reaches it; compile mode is the only production caller. - **`shutdown()`'s final loop terminates when the ledger empties**, which happens via the same silent drop — so the probe error that hides a leak can also end the cleanup loop early. That coupling is why the probe cannot be made strict on its own. **Its precondition is `any_running()` already false**, so the fixture must be a leader that exited leaving a survivor; any other shape tests the other arm of the disjunction. - **None of the three has been observed.** #200 saw an explicit `SIGTERM` fail in `signal()`, not a ledger call. The premise is falsified and the path exposed; the occurrence is not evidence these fire. - **They are also untestable today**: `tick_reap_ledger` and `shutdown()` call `nix` directly and consult no injection seam, unlike `signal()`'s `forced_kill_errno`. All five existing ledger tests exercise the success path only. The seam must be **site-directed and multi-outcome**: `shutdown()` calls `self.signal()` before its ledger force-kill, so a single global slot would be consumed by the wrong call, and the coupling test needs two pending outcomes at once. The in-drain probe repeats every 1 ms but only cancels readers after 50 ms of false "dead", so it needs a directed full-drain override rather than a one-shot error. Test state is per-supervisor and shared into the drain context, never global; teardown proves its intended site was reached. The in-drain SIGKILL's local flag has no independently observable outer-path consequence, so it is named but not given a dead injection seam. The first PR is the seam **plus** the tests that exercise it — a seam without tests does not show it reaches the intended calls. - **Diagnosis first, no disposition change proposed.** Stage A of the signal lane had three tolerance rules rejected across three revisions for the same shape of error on the same data structure. - Recovery from a clean checkout: ```sh git fetch githubsucks git worktree add ../pmacs-reap-ledger \ -b reap-ledger-silent-failures \ githubsucks/reap-ledger-silent-failures ``` ## Folding lane (Arc 6) — Stages 1 and 2 MERGED; Stage 3 (GPU) is next Both shipped stages are on `main`; nothing in this arc is in flight. Stage 3 has **no branch and no framing yet**. - Stage 1 (headless fold engine) merged as **#142**, Stage 2 (grid/daemon collapse) as **#149** — both under "Closed since the last snapshot". - Retained, carrying nothing unmerged: branches `folding` / `folding-tui` and worktrees `../pmacs-folding` / `../pmacs-folding-tui`. The framings `docs/folding-framing.md` (rev 5) and `docs/folding-stage2-framing.md` (rev 4) are the approved artifacts Stage 3 re-scouts against. - **Stage 3 (GPU) obligations, already named by the framings** — the starting point for its own framing doc: GPU collapse at TUI parity; caret/hit-test fold-awareness; the `BufferSnapshot` **fold-mirror clear** (parent R2-4 — without it, empty-after-revert diff suppression leaves stale folds on the GPU, the same trap class as #120); CRDT-origin and GPU-optimistic interactive unfold (parent R2-3); and flipping `FrontendView.fold_projection` to `true` for semantic frontends, which Stage 2 deliberately left `false` (Q#FD21). ## Parked lane: kill-ring browser + persistence - Portable branch: `githubsucks/kill-ring-browser` - Parked framing head: `503c489` - State: framing only, revision 2; no implementation and no PR. - Status: explicitly parked by the user on 2026-07-20. - Its original scout was based on `0efb5cd`. The preserved framing marks this ground truth stale and requires a complete re-scout against the then-current `githubsucks/main` before implementation. - Compile-mode has merged since the original scout, so old “compile-mode in flight” keybinding/touch-set assumptions are not authoritative. Recovery worktree, only when the user un-parks it: ```sh git worktree add --track \ -b kill-ring-browser \ ../pmacs-kill-ring-browser \ githubsucks/kill-ring-browser ``` ## Closed since the last snapshot - **Process-signal diagnostic completeness — MERGED as #200** (`main` @ `a2a92bb`), atop Stage A #176. `docs/process-signal-diagnostic-completeness-framing.md` revision 6; framing approved at revision 4 after three rounds, then five review rounds on the implementation. Durable facts are in `docs/agent-handoff.md` §1. **Evidence collection only** — no tolerance rule, no retargeting, no disposition change. - **Bet 1 was falsified by CI and the framing's own fallback shipped.** `bash -m` diverges the terminal's foreground group on Linux and never on macOS. The divergent case is pinned by injection everywhere; a Linux-only corroboration drives a real shell and is the **only** test exercising `pty_foreground_group` end-to-end, so **on macOS that lookup has no end-to-end coverage**. - **Group identity remains unprovable**, and the pre-kill sample does not change that: moving `getpgid` before the `kill` removed a post-hoc reading, it did not make the reading contemporaneous. - **Still parked, each needing its own lane:** the reap ledger's silent cancellation (an EPERM probe drops the entry; a failed `SIGKILL` is marked killed) — **being scoped next**; retargeting to the measured pgid; any EPERM/ESRCH tolerance rule; Q#PS6; and `signal_target`'s read-then-kill of `tcgetpgrp` on the PTY path. - **Six lanes removed by the 2026-07-30 absorption pass**, all merged, all with their durable facts in `docs/agent-handoff.md` §1: **#190** resource-op delete-guard implementation; **#196** dired Stage 2a (rename/delete reconciliation — Stage 2b remains, unstarted and without a lane); **#188** generated-buffer immutability framing (revision 7, the governing contract); **#194** silent-skip arming; **#195** CI timeouts and concurrency; **#197** the process teardown stdin deadlock. - **#194 and #195 kept their lessons in §3 and §5 rather than §1**, which is why a PR-number search of the handoff finds them only once each. That is sufficient under rule 3 — durable knowledge has a home, not a required section. - **A census by PR number is a proxy, not a measurement.** Counting `#NNN` in the handoff said five of these lanes had no record at all; counting by *content* found most already documented, with the real gap being the implementation PRs specifically (#190, #191, #196) while their framings were recorded. The absorption written from the first count would have duplicated existing entries. - **Terminal configuration + copy mode arc — BOTH STAGES MERGED, lane removed.** Stage 1 **#173** (`main` @ `cf54270`, one review round) and Stage 2 **#178** (`main` @ `fe8b8ba`, **four review rounds**, twelve checks green on head `1b44c69` — verified by `head_sha`, not by the check summary), both 2026-07-26, both with no protocol change. Approved framing: `docs/terminal-config-and-copy-mode-framing.md` rev 4, committed as the first commit of Stage 1's branch; its Q#TC6a carries a superseded-in-part box rather than a silent rewrite. Durable facts moved to `docs/agent-handoff.md` §1 (the arc bullet) and §4 (the `set_generated_contents` invariant) per rule 3 below, and to `COHERENCE.md` §14. **Stage 2 ships eight of nine criteria and the missing one is named** — criterion 17 needs a real GPU frontend, so it waits on the `a37` footing; the handoff records what it must assert. Branches `githubsucks/terminal-config` and `githubsucks/terminal-copy-mode` with worktrees `../pmacs-terminal-config` and `../pmacs-terminal-copy-mode` are retained. The gate-run flake found while gating #178 moved to the CI `crdt`-coverage lane above, which owns its discrimination. - **Dired Stage 1 (the directory view) — MERGED as #165** (`main` @ `c8ec8f3`, 2026-07-25, after one review round). pmacs has a directory surface: `C-x d` / `C-x C-j`, one read-only buffer per directory named `*dired:*`, a `dired` major mode carrying `RET`/`f`, `^`, `n`/`p`, `g`, `q`, `s`. No wire change (v20). The Rust is two things — a per-entry-tolerant `read_dir` (Q#DR6), which had to be Rust because `read_dir_blocking` fails a whole listing on any of five per-entry conditions and a tolerant wrapper cannot be written in Lua at all, and `normalize_buffer_path` going `pub` as `pmacs.path.canonicalize` (Q#DR2's preferred end state, so no Lua mirror exists and Stage 2 owes no mirror removal). The frozen m8_1/m8_2/m8_3 counts are unchanged, which is the additivity gate. 15 claims bite-verified; one came back VACUOUS (acceptance 3c cannot pin descent routing — dired holds focus in its own panel, so dedication is the only discriminator) and is documented at the assertion rather than relabelled. Its branch (`dired-stage1`) and worktree (`../pmacs-dired-stage1`) are done; the abandoned `dired` branch (`ffdd642`, `../pmacs-dired-arc`) was superseded by a fresh cut and carries nothing unmerged. **Stage 2 (marks and operations) and Stage 3 (wdired) each still need their own framing**, and the frozen fixture shrinks after Stage 3. Durable substrate facts and both new ops lessons live in `docs/agent-handoff.md` §§1/5; the implementation notes are `docs/dired-framing.md` §0, S1-1…S1-12. Two named forward items for Stage 2: `apply_resource_op`'s rename rebind is exact-PathBuf-equality, first-match-only, looked up with the raw path while stored paths are normalized — so a directory rename strands every buffer under it, and `pmacs.fs.rename` has zero production callers, so it can be fixed at the primitive; and Q#DR5's seam is the main-thread drain `AsyncRuntime::tick`, not `_take_result`, where rename settles as an undifferentiated `ReplyKind::FsUnit` and so must be keyed on `JobKind::FsRename`. - **GPU terminal input (the double terminal-layout sync) — MERGED as #166** (`main` @ `b889873`, 2026-07-25, one review round, all twelve checks green after a macOS PTY-timing rerun). The dispatcher applied **both** terminal-layout syncs to **every** attached frontend; a semantic session satisfies both conditions, so its PTY was resized twice per tick forever and the child took a `SIGWINCH` storm that made a GPU terminal untypable while output still flowed. `sync_terminal_layout` is now split into a frontend-kind-neutral half (panel reconcile + controller liveness) and a grid-only geometry half, with the loop body extracted to `sync_terminal_layouts_for_tick` so the exclusivity is structural. No protocol change (v20). Durable lessons are in `docs/agent-handoff.md` §5; the framing (`docs/gpu-terminal-input-framing.md` rev 2) carries three falsified hypotheses, the two-pre-image bite matrix, and two named out-of-scope items (Q#GT5 interactive-shell echo on a raw PTY, which reproduces in-process and so is not the GUI/TUI asymmetry; and a geometry change appearing to clear the visible screen, which reproduces pre-fix). Branch `gpu-terminal-input` and worktree `../pmacs-gui-term-input` retained. **Its landed-doc pair MERGED as #168** (`main` @ `1b6a084`, 2026-07-26): #166 recorded as landed, the CI `crdt`-coverage gap measured (**264 tests dark workspace-wide**, 177 in the library — a reading taken at `1b6a084` and kept here only as history. **The CI `crdt`-coverage lane above is the authority for the live figure**; do not quote this one forward), the vterm audit corrected — "only 3 of 9 acceptances drive a real daemon" was optimistic; without the frontend binary the honest number is **2** — and the a37 findings folded into the coverage lane. - **Inline-math slice — MERGED as #158** (`main` @ `5aa9044`, 2026-07-25). Detect → parse → layout → draw for `$…$`, entirely inside `pmacs-gpu`, no protocol change. Verified by the user's manual pass on a real paper after the landing. What is worth carrying forward: - **The v0 subset is 34 Greek symbols, sub/superscript, and `\frac`.** An unsupported command fails the **whole span** back to source, so on a real document most inline spans still show LaTeX. Widening the symbol map is the highest-value next increment — ahead of display math, which is also deferred. - **A stale frontend binary is invisible from the source tree.** The slice lives only in `pmacs-gpu`, so after it merged the feature was absent until `cargo build --release -p pmacs-gpu` and a client restart; the daemon needs neither. Diagnose with `strings` on the binary (`Latin Modern Math`, `MathBox`) rather than by re-reading the checkout, which was already current. - **Main was integrated three times in one day** (`8c86d34`, `46a1b8f`, `b889873`), merged not rebased to preserve review anchors. Two conflicts, both this ledger and nothing else. **The dangerous case was the one that did NOT conflict**: #166 auto-merged into `pmacs-gpu/src/main.rs`, the file this lane rewrites, because the two edits sat in different regions of it. Decide whether to integrate from the shared-**file** set, never from whether git complained. - **Integration was proved by test-count reconciliation**, not by a green run: predict what the other side adds, then check the deltas. GPU 199→202 matched `e547a90`'s 3; later lib 1,826→1,829 and CRDT 2,003→2,006 matched #166's 3, with GPU unchanged because #166 adds none. Suites 91→92 was #161's new binary. - **Why the branch had no CI for a day**: a conflicting PR builds no merge ref, so no `pull_request` run is created. The ledger previously recorded this cause as unidentified; it is not. Check `mergeable` and confirm a run exists for the current head SHA. - **`m4_5_basedpyright` has no timeout and hangs forever**, parking a `--workspace` sweep (observed 2h26m at 38 of 92 suites). It is **intermittent**, so an earlier clean sweep proves nothing. Sweep with `cargo test --workspace --no-fail-fast -- --skip basedpyright` and judge progress by whether the suite count advances. - Named v0 approximations: the peer-caret half of acceptance 14 is pinned at the mapping level, not pixels; a soft-wrapped spacer draws its box whole at the first run's origin; the fit budget reads the bundled code face even under a custom `set_font` family. - **Bottom panel Stage 1 — MERGED as #155** (`main` @ `e745068`, 2026-07-24, after two review rounds). Window placement, window parameters, TUI side windows, the divider, and the adopter `display` opt-in, with no protocol change. Both rounds found the same class of defect and are worth keeping: - **Round 1**: the Q#BP6 side-window split guard had *no production caller* — `C-x 2` still reached plain `split_active` — and survived because the acceptance test called the core method directly. - **Round 2**: Q#BP7's terminal growth re-arm had *never been implemented*, and the assertion meant to pin it (`at_bottom`) is a geometric readout that a still-anchored view satisfies; the anchor assertions beside it compared `""` with `""` because the PTY fixture emitted LF-only output. - **Post-round-2 self-review**, caught by CI going red on all four Test jobs: resolving `pmacs.window.buffer()`'s no-argument arm through the acting frontend made a total function partial, and six runtime modules silently dropped operations (`kill_ring_acceptance` 30/30 → 25/5). Fixed in `9110f9f` before merge. - Gating fact found on the way: an isolated `XDG_CONFIG_HOME` prevents the real user `init.lua` from installing a local package and leaking a status message into painted-frame comparisons. **That isolates the observed config symptom only, not the gate:** the ambient-root lane above establishes that data/state/cache must be controlled too. There is also a latent pre-existing `main` bug in the buffer CRDT undo path, unrelated to this arc. - `compile_mode_acceptance` is load-sensitive under default parallelism (~1 run in 3, a different test each time); verified pre-existing by swapping in `main`'s `compile.lua`. It is 67/67 at `--test-threads=1`. - **GPU initial target — MERGED as #148** (`main` @ `0dd16a5`, 2026-07-24, after two review rounds). `pmacs --gpu [--socket …] FILE` opens a target before the GPU window appears. Protocol bumped 19 → 20: a semantic-session `SessionBootstrapRequest` after `AttachRequest`, plus an appended `InstanceMessage::InitialTargetResult` pre-window readiness barrier; v6–v19 wire encodings are unchanged. Root owns launcher tilde/cwd resolution and exact raw-byte path transport; the daemon resolves/dedups/loads the target and runs load/switch hooks inside one dispatcher transaction, then publishes CRDT-upgraded targets to existing grid replicas (gated on `upgraded_to_crdt`, independent of the load/create outcome, so a dedup onto a hidden not-yet-backed buffer still reaches pre-attached replicas — round 2 finding). Semantic replicas receive a publication only when displaying that buffer, so a second target launch cannot switch an existing GPU window. Round 2 also closed a failure-containment gap: every dispatcher-side bootstrap failure now shuts down the socket (a dropped write-half clone does not close a shared FD), and the dispatcher drops any event from a session that was never installed, rather than reaching absent render/size state. Integrated cleanly with Folding Stage 2 (#149): fold projection at attach is selected from the same negotiated `semantic_render` bit the target bootstrap uses. Its lane, worktree (`../pmacs-gpu-initial-target`), and branch (`gpu-initial-target`) are done; the `-framing` branch is kept. Durable substrate facts and both review-round lessons live in `docs/agent-handoff.md` §§1/5 and `docs/gpu-initial-target-framing.md` rev 3. - **Folding Stage 2 (grid/daemon collapse) — MERGED as #149** (`main` @ `6ed4fe9`, 2026-07-24, after **five** review rounds). The grid TUI now renders collapses. Spine (Q#FD12): `src/fold_view.rs`'s `VisibleLineMap`, derived from the fold store plus a window's line offsets and **never stored**, threaded as `Option<&'a VisibleLineMap>` on a lifetime-bearing `Viewport<'a>` that stays `Copy`. No wire schema or protocol change; the GPU path is Stage 3. 48 acceptance tests on the real `paint_frame` grid, every behavioral claim bite-verified. Durable design points, each a trap Stage 3 inherits: - the map's unit is a **merged hidden component** (overlapping *or adjacent* intervals unioned, keeping the earliest visible head), not a fold — folds may cross, and a later fold's own head can be hidden; - instances are **per rendered window** and **per command/event operation**, never per frame; a command's map follows the operation's **target** window, since a wheel event names a pane without activating it; - fold projection is **per-frontend** (`FrontendView.fold_projection`) — shared `EditorCore` motion would otherwise make a simultaneous unfolded GPU session's cursor skip lines it still displays; - a hidden cursor normalizes by **position**, not row, and `set_view_top` clamps in the setter rather than being repaired at render time; - the interactive-Lua unfold keys on the **post-intercept** edit site — a managed buffer intercept may legally relocate the op. Process notes worth keeping: `main` moved under the arc, and the merge was textually clean but **not semantically clean** (#146 added `Viewport` literals the new `folds` field invalidated) — a clean `git merge-tree` does not mean the merged tree compiles. CI was red at review on the macOS/luajit `outline_5_level_100_entry_renders_within_100ms` budget flake and went green on rerun. - **Documentation ledger refresh — MERGED as #147** (`main` @ `0a479ae`, 2026-07-24). The #142 housekeeping, expanded after review found the ledger stale through four merges rather than one. Its own macOS/luajit red was the vterm `VTERM_ALT_READY` PTY timeout; green on rerun. - **Web grammars HTML + CSS — MERGED as #146** (`main` @ `47581f4`, 2026-07-23). `.html/.htm/.xhtml` and `.css` highlight off the official `tree-sitter-html` 0.23 / `tree-sitter-css` 0.25 crate query constants (no in-repo overlay), and HTML's `INJECTIONS_QUERY` lights up `