pmacs/docs/active-work.md

47 KiB
Raw Blame History

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. Everything else in this file is unchanged.

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 @ cfc1710 (discovery Stage 1 #207, atop 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, the ledger absorption #199, and the previously recorded landed work). Protocol schema support is v6..=v21; the production server-first Hello still advertises v20 — two different facts, and #184 landed only the first. The previous snapshot named fbcf235, and the recovery floor advances with it: the check below now requires cfc1710 or newer, so a tree at fbcf235 no longer passes. That is deliberate — the floor moves with the base, because a check that accepts 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 <their base>..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:

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:

git remote add githubsucks https://github.com/levineuwirth/pmacs.git

Then recover current refs:

git fetch githubsucks --prune
git log -1 --oneline githubsucks/main
git worktree list
git status --short --branch

The git log command must expose cfc1710 — 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 — ahead of a machine move. From an empty directory: git clone the canonical URL, add the githubsucks alias, git fetch githubsucks --prune, confirm cfc1710 is an ancestor of githubsucks/main, and recover a lane with the three-argument git worktree add <path> -b <local> githubsucks/<branch> form. All four steps ran clean. The two-argument form still does not work for a remote-only branch (fatal: invalid reference), which is why every lane below spells out the -b form.

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.

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:

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<String>, 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 validationresolve_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.

Bottom-panel lane (Arc 7) — STAGE 3 COMPLETE; ARC DONE, pending PR

Arc 7 is finished. Stage 1 (#155), the Stage 2 framing (#175), Stages 2A (#177), 2B-1 (#184), 2B-2 (#187) and 2B-3 (#198) are on main; Stage 3 — the adopter default flip — is implemented on bottom-panel-stage3 and is the arc's last step. Framing docs/bottom-panel-stage3-framing.md revision 3, approved with amendments after three review rounds.

This lane is removed once Stage 3 merges and its facts are in docs/agent-handoff.md §1 (rule 4). It is retained now only because the PR has not landed.

  • Branch bottom-panel-stage3 off githubsucks/main @ 21de0b2, pushed, upstream tracking set. Six commits: fa12095 framing, 0224c68 census, 41d37fc shared resolver, 8d14e6a lane/portability, c0eb16b the flip + test revisions, 5f01ede the fallback pin.
  • Verification: full serialized sweep 3449 passed / 0 failed against a 3447 baseline (+2 new pins); fmt, diff-check, clippy with and without crdt, --lib 1896, --lib --features crdt 2081, pmacs-protocol 19, m4 149, required GPU 221, and bottom_panel_stage1_acceptance 47/47 under both Lua flavors.

What Stage 3 changed

Omitting display resolves to the panel for listview, compile and terminal. Dired keeps "current"pmacs.path.directory_handler calls it with no display, so a flipped default would open pmacs . in a bottom panel. Per-adopter select: listview true, compile false, terminal true.

Four hand-written copies of the display validator collapsed into one shared resolve_adopter_display(operation, raw, default); the default is a parameter, which is what makes dired's exemption visible at its call site rather than hidden in a divergent copy.

Three defects the flip exposed, each fixed rather than tested around

  • The outline panel's on_visit used the RAW switch (pmacs.window.switch_buffer), which replaces the buffer in the ACTIVE window. Harmless while the outline opened into a document window; once the panel became the default, RET clobbered the panel with the source. The references panel was migrated to display_file when the arc landed — the outline was missed because nothing exercised it from a panel until the flip. Q#BP11c names this exact corruption.
  • compile._last stored only {cmdline, cwd}, so g reached start_run with no display and took the new default: an explicit display = "current" silently reverted on the next recompile. An opt-out that reverts is not an opt-out.
  • opts.display on a nil opts — a regression introduced by the fix above and caught by journey_acceptance, which is what that ratchet is for.

Two contracts now pinned, not merely observed

  • Compile's chords are PANEL-LOCAL. All are bound scope = "buffer", so with select = false none dispatch from the document — C-c C-k included. M-x compile.kill still reaches the running slot anywhere via its or compile_slot() fallback. A global chord is a command-surface decision and belongs in its own framing.
  • The two q mechanisms are complementary, not competing. Presentation history chains in the side slot (C → B → A → delete, Q#BP2c); p.prev prevents raw-switch and capability-fallback listview loops. s1_12 pins the second with explicit display = "current"; the new s3_1 pins the first.

The census lesson worth carrying past this arc

It counted failures, not causes. Thirteen listview failures had ONE root cause — a panel is derived-hidden while frame geometry is unknown, and that suite never declared any because it never needed to. The same applied to m4_acceptance and vterm_stage2_acceptance. And --no-fail-fast is mandatory: the first sweep reported 2 failures in 1 suite because cargo test halts after a failing binary; the real figure was 37 across 5.

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.

  • 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:

    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:

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:<canonical path>*, 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 callerC-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; v6v19 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 <script> → js and <style> → css. Durable lesson recorded in docs/web-grammars-html-css-framing.md: the highlight.rs capture table is global, so adding a capture name retro-paints every other language — check the reverse direction and pin it.

  • LaTeX Stage 1 — MERGED as #144, with its parent inline-math framing committed as #145 (main @ f09b0a1, 2026-07-23). .tex/.latex/.sty/.cls highlight via codebook-tree-sitter-latex 0.6 plus the first in-repo query overlay (builtin/queries/latex/highlights.scm, include_str!) — the reusable pattern for grammars whose crate ships no usable queries. The crates.io tree-sitter-latex is provably broken (no scanner.c). The math parser and Tiers 34 are deferred to the inline-math arc.

  • Folding Stage 1 (headless fold engine) — MERGED as #142 (main @ c49a8c7, 2026-07-23, after three review rounds; round 3 clean). The instance-side fold store + translating/dropping View, the structural source (derived head line, closer-aware tail), the Lua data API + interactive C-c @ commands, the command-path pre-edit unfold, and authoritative-empty FoldState production landed with no protocol bump. The folding branch and worktree (../pmacs-folding) are retained but carry nothing unmerged; the folding-framing.md framing is preserved. CI red at merge was an unrelated environmental perf flake (outline_5_level_100_entry_renders_within_100ms, macOS/luajit only), green on rerun. Stage 2 has since merged as #149 (above); durable substrate seams live in docs/agent-handoff.md §1.

  • Vterm Stage 3 (protocol v19 + GPU terminal) — MERGED as #135 (main @ cac4961, 2026-07-22, after two review rounds). Arc 5's terminal stage is complete (compile mode #113, Stage 1 #126, Stage 2 #130, Stage 3 #135). Its lane, worktree (../pmacs-vterm-gpu), and branch are done; durable substrate facts live in docs/agent-handoff.md and docs/vterm-framing.md.

  • Branches deleted 2026-07-22 (authorized): vterm-stage3-framing (Revision 8 framing; its content is carried on vterm-gpu, verified as a superset before deletion — the branch was NOT an ancestor of vterm-gpu because the framing was copied rather than merged, so it needed a forced local delete) and tab-width-parity (a clean ancestor of main via #137). Both removed as worktree + local ref + githubsucks ref; the origin tracking refs were pruned. The -framing branches for each are deliberately kept.

  • Tab-width rendering parity — MERGED as #137 (main @ 2625ec7, 2026-07-22). One fixed 8-column TAB_STOP_COLUMNS in pmacs-protocol now drives core/TUI columns, GPU code projection, and minimap width; source bytes and protocol ranges are unchanged. Its lane, worktree, and tab-width-parity branch (local + githubsucks) are deleted; the tab-width-parity-framing branch is kept. This closes the long-standing "tab width is a rendering-parity bug, NOT a config gap" deferral recorded in docs/agent-handoff.md §5.

  • Locals-query processing — MERGED as #134 (with handoff #136), and modeline detection handoff #133. Both landed between this lane's base and its canonical-main integration.

  • Config registry — MERGED as #127 (main @ 2e37c04). Its lane (config-registry, worktree ../pmacs-config-registry) is done; the branch is kept but carries nothing unmerged. Durable substrate facts moved to docs/agent-handoff.md §1 per rule 3 below.

  • Both this and Vterm Stage 1 ran as concurrent lanes in sibling worktrees off main, with the shared files (src/editor.rs, src/lua_bindings/mod.rs, src/lib.rs) assigned to one lane each in advance. The rebase of the second lane onto the first had zero conflicts — worth repeating for future parallel work, along with its precondition: agree the file split before either lane starts, and keep each lane's footprint in the other's files to a single line.

Update protocol

Whenever a listed lane changes materially:

  1. update its public branch and head/state here;
  2. record new verification and remove superseded caveats;
  3. keep durable architecture in docs/agent-handoff.md, not here;
  4. remove the lane after merge or abandonment;
  5. verify every recovery command from a clean worktree before calling the transfer complete.