# Active work — cross-machine resume ledger **Snapshot: 2026-07-26.** 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. ## 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` @ `a27f646` (Lean 4 Stage 4a #179 atop Stage 3b #170, Stage 3a #167, the bottom-panel landed-doc refresh #156, the inline-math slice #158, dired Stage 1 #165, the GPU terminal input fix #166, Lean 4 Stage 2 #161, the dired framing #164, COHERENCE.md #163, find-file #162, Lean 4 Stage 1 #160, and the minimap blank-slab fix #159; protocol v20). The previous snapshot named `d152120`; the recovery check below accepts it or anything newer. - 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 `d152120` or a newer intentional main. If it does not, stop and repair the remote/fetch configuration. ## Lean 4 lane (Arc 8) — Stages 1–4a MERGED; Stage 4b IN REVIEW - **Stages 1, 2, 3a, 3b and 4a are MERGED** — #160 (`main` @ `0827dd1`), #161 (`46a1b8f`), #167 (`6f348c9`), #170 (`d400f30`), #179 (`a27f646`). Their full histories were pruned from this ledger in round 6, per this file's own instruction to remove entries when their PR merges; the durable facts now live in `docs/agent-handoff.md` §1's Lean 4 bullet, which is where a fresh machine should read them. `docs/lean4-mode-framing.md` rev 9 carries the decisions. ### Stage 4b — the Unicode input method (branch `lean4-stage4b-input-method`) - Framing `docs/lean4-mode-framing.md` **revision 9**, approved. Stage 4a (the typed-edit consumer chain) MERGED as #179; this branch is 4b, the Lean content that registers on it. - Footprint: `scripts/regen-lean-abbrev` (new, the generator), `builtin/runtime/lean_abbrev.lua` (new, VENDORED DATA — 1,855 entries from `leanprover/vscode-lean4@17d1d08`, Apache-2.0), `builtin/runtime/lean_input.lua` (new, the consumer at priority 50), `src/editor.rs` (two `include_str!` blocks), `tests/lean_input_acceptance.rs` (new, 25 tests), and one `#[cfg(feature = "crdt")]` `--lib` test in `src/daemon.rs` (acceptance 45f). No protocol change (Q#LN14). Entirely Lua apart from the load sites and that one test. - **Round 9 corrected three acceptance criteria that the real table contradicts** — found by simulating the state machine over all 1,855 entries and re-reading upstream at the pinned commit, not by reading the prose again. `\to` is NOT eager (`top`, `to0`, `toa` extend it); `\zzzz` expands to `ζzzz ` because `ze`/`zeta`/`zsqrtd` exist, and only `$ % , ; @ W` open no key at all; and `\alpha`'s undo does not restore `\alpha ` because `alpha` IS eager, so the terminator is a separate edit. Criteria 38, 41 and 42 now state both paths. - **Two generator bugs, both caught by its own round-trip check failing closed:** `str.splitlines()` also splits on U+2028/U+2029, and 53 symbols contain one literally, so the check reported a count mismatch that was its own bug; then escaping via `chr(byte)` produced a latin-1-shaped string that `write_text(encoding="utf-8")` re-encoded, and every non-ASCII symbol landed double-encoded. The first version of the check compared IN-MEMORY strings and agreed with itself. **It now stages the file, re-reads the bytes from disk, and renames into place only on a match.** - **The point must be placed explicitly after the replace.** The expansion SHRINKS the buffer (`\alpha` 6 bytes → `α` 2), so a point left at the pre-edit offset is past the new end and every later self-insert is silently rejected — the editor looks dead after the first expansion. Pairing's "no cursor motion on the clean path" does not generalize: that holds only for an insert AT the cursor. - **Three tests were vacuous when first written and were found by biting, not by review:** the abandonment test asserted text that a wrongly-surviving record would also produce (claiming makes no edit — it needed the follow-up keystroke that completes an eager key); the re-arm test used the framing's own `\alpha\to`, which never reaches the re-arm branch because `alpha` is eager and closes the record first (`\al\to` does); and both buffer-switch tests passed through `find_or_open`'s fresh-load path, which fires `buffer.after-load` and a record-less edit rather than `buffer.after-switch` — deleting the subscriber left them green. All three now bite. - **Bite table** (each mutation, and the tests it fails): | Mutation | Tests it fails | |---|---| | register at priority 150 (after pairing) | 2 | | claim only completed expansions | 2 | | longest match instead of shortest | 9 | | equal-length tie keeps the LATER key | 3 | | remove the eager branch | 8 | | expand without the terminator in the span | 2 | | remove the re-arm branch | 1 | | remove the point-still-at-span-end check | 1 | | remove the exact-revision check | 1 | | leave the point where the replace found it | 5 | | remove the `lean4` language gate | 1 | | remove the `lean.abbrev` gate | 2 | | `buffer.after-switch` clears every frontend | 1 | | delete the `buffer.after-switch` subscriber | 1 | | `frontend.detached` purges every frontend | 1 | Acceptance 45f bit by construction: without a registered window for the source frontend it ran six fan-outs with a nil record and proved nothing, because `handle_remote_crdt_op` arms nothing unless the source's active window displays the buffer. - Undo is cross-peer-degraded on CRDT frontends and that is ACCEPTED, named in the module header (Q#LN21): six source-peer optimistic inserts replaced by one daemon-peer op. `set_round_trip_input` would fix it and also disables `dispatch_idle`, so RET would stop inserting a newline. ## Dired lane — Stage 0 MERGED; Stage 1 IN REVIEW (PR #165) - Approved framing: `docs/dired-framing.md` **revision 6** — rev 5 is the approved text (merged as its own docs PR #164), rev 6 adds §0's Stage 1 implementation notes (S1-1…S1-9). Stages 2 (marks and operations) and 3 (wdired) each get their own detailed framing after the prior stage lands. - **Stage 0 (`C-x C-f` find-file) MERGED as #162** (`main` @ `2af1ab3`, 2026-07-25, one review round, 12/12 CI green). Durable facts moved to `docs/agent-handoff.md` §1 per rule 3 below. - **Stage 1 branch: `githubsucks/dired-stage1`**, worktree `../pmacs-dired-stage1`, based on `githubsucks/main` @ `8c86d34` (the framing merge #164). **A fresh cut, not a rebase:** the older `dired` branch (`ffdd642`, worktree `../pmacs-dired-arc`) was based on the superseded `0827dd1` and carried only the framing content #164 already put on `main`, so merging it would have reconciled two histories of one document. It is left untouched and carries nothing unmerged. - **Stage 1 implemented; no wire change (protocol stays v20).** What landed on the branch: - `builtin/runtime/dired.lua`: one buffer per directory named `*dired:*` with the handle-table ownership check; read-only intercept + `set_round_trip_input`; the `dired` major mode and its mode-scoped keymap (`RET`/`f`, `^`, `n`/`p`, `g`, `q`, `s`); basename cursor re-seating across every wholesale repaint; `display_file` for file visits and same-window reuse for directory descent; `C-x d` / `C-x C-j`; the `dired.kill-when-opening` setting. Loaded after `window.lua`. - `src/fs.rs`: `ReadDirTolerance`, `FsDirEntryError`, `FsDirListing`, and one walk that either fails on a per-entry condition or records it (Q#DR6). `src/async_runtime.rs` carries the listing in `ReplyKind::ReadDir` / `JobResult::ReadDir`; `src/lua_bindings/mod.rs` keys the Lua result **shape** on `errors.is_some()`, so the bare array the frozen M8.2 fixture consumes with `ipairs` is untouched; `builtin/runtime/fs.lua` validates read-op opts and **rejects unknown keys** (a typo'd `tolerant` used to degrade silently to fatal). - `src/editor_core.rs` + `src/lua_bindings/mod.rs`: `normalize_buffer_path` is `pub` and exposed as `pmacs.path.canonicalize` — Q#DR2's preferred end state, so no Lua mirror exists and Stage 2 owes no mirror removal. This makes B2 ("tolerant `read_dir` is the only Rust change") false by one small binding, deliberately. - `tests/dired_acceptance.rs`: 22 tests over framing items 1–16, dispatch-driven; item 17 is the m8_1/m8_2/m8_3 additivity gate. - **The framing claim the substrate falsified (S1-2):** R2-3 expected a dedicated dired panel to carry its dedication across a descent. `display_buffer` never replaces the buffer in a slot dedicated to another one — it discards every side-specific parameter and falls back to the document window (Q#BP3 2.iii), and the exact-window arm errors. Dired does not unpin the user's panel; both arms are pinned. - **The vacuity the bites found (S1-3):** acceptance 3c cannot pin the descent *routing*. Dired holds focus in its own panel, so a raw `switch_buffer` lands in the same window and every 3c assertion holds either way. Dedication is the only discriminator, so the dedicated-panel test is the real pin — and the vacuity is documented at the assertion rather than relabelled. - **The pre-existing test dired's first mode-scoped binding broke (S1-4):** `describe_key_identifies_every_default_binding` asserted every binding in the stack resolves through `describe.key` context-free, which held only while the modes table was empty. It now sets the effective context per binding and explicitly *clears* the mode for global ones, because a leaked mode legitimately shadows a global chord of the same name (dired's `RET` shadows `edit.newline-and-indent`). - Durable substrate facts, independent of this arc: - `pmacs.buffer.kill` (not `remove`) redirects windows off a doomed buffer before removal, so `kill-when-opening` kills **after** the replacement is displayed. - Interactive origin does **not** survive an await: work resumed in `tick_async` sees no `InteractiveCommandOrigin`, so `pmacs.window.*` acts for the *ambient* active frontend (S1-9). - Kinds are lstat-based in both `read_dir` and `stat`, so nothing in an entry says whether a symlink points at a directory; `RET` probes by trying to list it (S1-8). - A path-backed buffer's *name* is its full path, not its basename — worth knowing before writing any name assertion. - `C-x d` takes **no** completion source on purpose (S1-5): with one, RET on an empty field opens whatever sorts first, and RET-on-where-you-are is the gesture the binding exists for. The field is prefilled instead. - **Bite verification:** 15 claims, each mutated in place and required to fail the test that names it. `dired.lua` is new, so `scripts/bite`'s file swap does not apply; every mutation was applied and reverted with `git checkout --`. One came back VACUOUS and is recorded above. - **Review round 1 addressed** (framing rev 7, S1-10…S1-12). Three behavioral fixes, each bite-verified: `dired.revert`'s re-seat is guarded on the active buffer (an ambient `move_to_line` after an await moved an unrelated buffer's cursor — the buffer-level instance of S1-9); `fmt_size` keeps the column width past ten digits, because `_layout` is a contract Stage 3 is planned against; and the symlink descent dropped its probe, since `open_directory`'s changed-nothing-on-failure invariant *is* the probe (it was listing the target directory twice). Plus a consecutive-`readdir`-error cap, because **nothing cancels a dired listing** — it carries no supersede key, so cancellation was never the backstop the tolerant loop implicitly relied on. Naming/comment findings taken as-is. - Durable process lesson, hit twice now: a mutation-bite helper restores with `git checkout --`, which reverts to **HEAD** — so a fix must be committed *before* it is bitten. Round 1's fixes were briefly wiped by exactly that. - **Canonical main integrated twice** — at `46a1b8f` (multi-root LSP affinity #161) and again at `b889873` (GPU terminal input #166), both merged rather than rebased per the #135/#137 precedent so the review anchors stay addressable. Each conflict was a single doc hunk resolved as the union: this lane owns COHERENCE's journey step 7 file half, #161 owns the in-flight list, #166 owns step 8's GPU-terminal addendum. Three things worth carrying: - **A conflicting PR silently stops running CI.** GitHub builds `pull_request` runs against the merge ref, which does not exist while the PR conflicts, so no run is created and nothing reports a failure — the checks list simply stays as it was. Three pushes to this branch produced no CI at all before the cause was found. Watch `mergeable` on a long-lived lane, not just the check list. - #161's own COHERENCE finding **falsified a claim in this lane's module doc**: `pmacs.error` is never defined in production, so an uncaught raise inside a `pmacs.async` coroutine does not reach `*errors*` as the comment said. It reaches a bare `error()` inside `pmacs._async.tick()`, whose result `tick_async` discards with `let _ =` — i.e. nowhere. That makes dired's per-coroutine `pcall` + `set_status` load-bearing rather than tidy, and the comment now says so. - **A lane in review against a fast-moving `main` needs its gates rerun per integration, not per push.** Main advanced twice inside this review round, and the second time landed while the first integration's sweep was still running. The numbers below describe the twice-merged tree. - Verification on the twice-merged tree (`main` @ `b889873`): `cargo fmt --check` clean; strict workspace Clippy clean; **1,832 default + 2,009 CRDT** library tests; dired acceptance **25 default + 25 CRDT**; m8_1 10 / m8_2 15 / m8_3 32 unchanged; multi-root 13 and vterm Stage 3 5 (both suites main added, green under this lane's `mod.rs` and `editor.rs` changes); M4 121; required GPU 155; **isolated-`XDG_CONFIG_HOME` workspace sweep 3,205 passed across 93 suites, zero failures**; `git diff --check` clean. The sweep needs the isolated config for the reason recorded in the bottom-panel lane below. - Coherence (framing §0.5, required since #163): serves `COHERENCE.md` §20 Priority 1, which names this work explicitly; journey step 7's file half goes from no surface to a surface; **adds no interaction island** — keys are a mode-scoped keymap, and wdired will be a mode swap; adopts `pmacs.config` for `dired.kill-when-opening`; inherits §9's worker-attribution gap for its `read_dir` jobs without worsening it. The audited claims this changes are updated in `COHERENCE.md` itself, per its §25. - **Boundary with the Journey Stage 1 arc** (`COHERENCE.md` §20 arc-cut 1): CLI directory-argument handling (`pmacs .` exits 1) belongs there, not here — Stage 1 does **not** fix it. The two meet at `resolve_target_buffer`; dired supplies the buffer a directory should resolve *to*, and `pmacs .` should route into it rather than growing a second directory surface. ## GPU terminal input lane — IN REVIEW - Portable branch: `githubsucks/gpu-terminal-input`, worktree `../pmacs-gui-term-input`, based on `githubsucks/main` @ `46a1b8f`. - Approved framing: `docs/gpu-terminal-input-framing.md` revision 2, committed as the branch's first commit (`9a0df21`). Bug fix, not a feature; **no protocol change (stays v20)**. - Reported as "text input within the terminal doesn't work on GUI, this is fine in TUI". Root cause: the dispatcher applied **both** terminal-layout syncs to **every** attached frontend, and a semantic session satisfies both conditions (a `term_sizes` entry from `AttachRequest` *and* a terminal declaration). Its PTY was resized twice per tick forever — grid arm installs the TUI placement size, semantic arm installs the declared content rectangle, each arm's idempotence guard seeing only what the other just wrote — so the child took a `SIGWINCH` storm at tick cadence. - **The fix is a split, not a guard.** The grid arm is also the only per-tick controller-liveness release a semantic frontend gets, and `sync_semantic_terminal_layout` cannot take that over: the buffer-follow snapshot clears the viewport declaration (`on_buffer_snapshot_sent`), so that arm stops running in exactly the switch-away case that needs the release. `sync_terminal_layout` is therefore split into a frontend-kind-neutral half (panel reconcile + liveness) and a grid-only geometry half, with the loop body extracted to `sync_terminal_layouts_for_tick` so the exclusivity is structural and tests drive the real thing. - **Trap for anyone touching this again:** the release at the "no `window_placements` entry" arm reads like liveness and is grid geometry. A semantic frontend has no placement entry at all, so moving it into the neutral half releases a GPU controller every tick. - Bite-verified against **two** pre-images, because the naive guard fixes the storm and introduces the leak: | pin | `main` | naive guard | the split | |---|---|---|---| | settle (acc 2+3) | FAIL | pass | pass | | controller release (acc 6) | pass | FAIL | pass | | grid still resizes (acc 5) | pass | pass | pass | - Real-path evidence: a quiet child trapping `SIGWINCH` reports **144 frames in 4 s and `WINCH 1..12` on screen** against the pre-fix tree, versus a settled screen with the fix. - **Deliberately out of scope, named:** interactive-shell echo on a raw-mode PTY (Q#GT5 — reproduces in-process too, so it is not the GUI/TUI asymmetry), and a geometry change appearing to clear the visible screen (reproduces pre-fix; why acceptance 4 latches its observation across frames). - Verification on this branch: `cargo fmt --check` clean; strict workspace Clippy clean; 1,829 default + 2,006 CRDT library tests; vterm Stage 1/2/3 10 / 6 / 9 CRDT; bottom-panel Stage 1 46; M4 121; required GPU 155; **isolated-config workspace sweep 3,177 across 92 suites, zero failures**; `git diff --check` clean. Gates were run against the committed tree. ## Terminal config + copy mode arc — Stage 1 IN REVIEW - Approved framing: `docs/terminal-config-and-copy-mode-framing.md` **revision 4** (four review rounds), committed as the first commit of Stage 1's branch. Two stages, two branches, two PRs; **no protocol change**. - **Stage 1 = `githubsucks/terminal-config`**, worktree `../pmacs-terminal-config`, based on `githubsucks/main` @ `d152120` and merged up to `c93f9ee` during review round 1. Profiles, scrollback, escape key, and the `C-c t` opening binding. - **Stage 2 = `terminal-copy-mode`, not started.** Branch it off `main` after Stage 1 merges: no dependency, but both edit `builtin/runtime/terminal.lua`. - Load-bearing decisions, each forced by scouted ground truth: - profiles are a **raw Lua table** — `ConfigValue` is four scalars with no table kind, so they join `pmacs.lsp.config` / `pmacs.pair.sets`; - the **two open-time settings resolve through the global chain**, because they are read before the identity buffer exists; only `terminal.escape-key` resolves per buffer; - the escape cache lives on **`TerminalSession`** so its lifetime is the terminal's. `value_epoch` alone is not a sufficient key: it does not advance when focus moves between terminals with different buffer-local values; - repeating the escape sends **that chord**, not a hardcoded `0x03`. - **Four bites, each against a different plausible wrong implementation** — hardcoded ETX fails acc6/9; epoch-only cache key fails acc7; single last-entry cache fails acc8's parse count; removing the invalid-value fallback fails acc10. The first version of acc7 passed against the epoch-only bite because it asserted only that terminal A still worked; the discriminating assertion is that **each** terminal honors its own chord and not the other's. - Test instruments worth reusing: `cat -v` is the echo probe, because the screen rejects C0 controls before they reach cells so a raw echoed `Ctrl-X` is invisible; and the probe **counts occurrences** rather than testing presence, because a single-character probe collides with the child's own banner text. - **Review round 1 (2026-07-25) — five findings, all real, all fixed.** One blocker and two majors were the same failure in three places: a claim asserted somewhere cheaper than where it lives. - *Blocker — `COHERENCE.md` was stale in four places, not the three reported.* Step 8 still read "no keybinding"; §11 still read "five settings"; and §6's dispatch table still cited `is_terminal_escape_chord`, **a symbol this PR deletes**. §25 makes that update ride the PR. A PR that changes audited ground truth has to re-grep the audit for its own symbols, not only for its topic. - *Major — acceptance 5 was vacuous.* It asserted a registry round-trip, so it stayed green with the setting's **only** consumer deleted. It now opens a real terminal whose child overflows the 24-row screen, scrolls the view to its oldest retained row, and asserts `LINE001` is present at 10,000 and absent at 0. **Asserting a value was stored is not asserting anything reads it.** - *Major — acceptance 8a asserted the session count, not the cache.* An editor-side map with no purge hook — the exact rejected design — leaks *while* sessions drain, so it passed. Fixed with a `TerminalManager::escape_caches()` seam. **A lifecycle claim needs a lifecycle observable.** - *Moderate — `table.sort` over user-controlled profile keys.* A table holding both a string and a numeric key raised `attempt to compare number with string` **on the unknown-profile path**, replacing the diagnostic being asked for; `%q` raised likewise on a non-string `profile` argument. Both are partial functions applied to user input **on a diagnostic path** — the error reporter was the thing that failed. - *Minor — the committed framing still said "not yet approved".* - **Three new bites, each falsified by revert**: deleting the scrollback consumer fails acc5 (and only acc5); restoring the raw-key sort reproduces `attempt to compare string with number` verbatim; and implementing the rejected editor-side map fails the new acc8a at `left: 2, right: 1` **while passing the old session-count version** — which is the review finding demonstrated rather than argued. - Verification after the round-1 fixes, on the tree merged with `githubsucks/main` @ `c93f9ee`: `cargo fmt --check` clean; strict workspace Clippy clean; 1,832 default + 2,009 CRDT library tests; `terminal_config_acceptance` **12/12 in both configurations**; vterm Stage 1/2 9+10 / 6+6; config registry 16+16; bottom-panel Stage 1 46+46; M4 121; required GPU 202; `git diff --check` clean. - `compile_mode_acceptance` fails 11/67 against the **real** user config and passes 67/67 with an isolated `XDG_CONFIG_HOME` — the known pre-existing trap, not this branch. - **`vterm_stage3_acceptance::a37` fails on this machine — and fails identically on the PR's own base `d152120`**, so it is not this branch's regression. It is load-sensitive: it passed at `d152120` once and failed at that same commit twenty minutes later, with a second agent saturating the machine with `rustc` in between. Two ways it lies, both worth knowing: it **silently returns `ok` when `pmacs-gpu` is not built** in the same target dir (only `PMACS_REQUIRE_GPU=1` promotes that skip to a failure, and the gate list applies that flag to `-p pmacs-gpu`, a *different* package), and it is **crdt-gated, so CI has never run it at all**. A green a37 in a gate log means nothing unless the binary was built and the flag was set. Needs its own lane; see the CI `crdt`-coverage lane on #168. - `pmacs-gpu` itself failed 201/202 once under the same load and passed 202/202 on immediate rerun. ## Bottom-panel lane (Arc 7) — Stage 1 + framing MERGED; Stage 2A IN REVIEW Stage 1 and the Stage 2 framing are on `main`. **Stage 2A is implemented and in review.** - **Stage 2A — portable branch `githubsucks/bottom-panel-stage2a`**, worktree `../pmacs-bp-stage2a`, **canonical `main` @ `cf54270` integrated** (review round 1, finding 4 — the terminal-config lane #173 also changes `src/editor.rs`, so gates were rerun on the merge result, not the old combination). Five commits: the classified census routing, the painter extraction + acceptance, the lane record, then the round-1, round-2 and round-3 review fixes. **No protocol change; no behavior change for any frontend today** — with `panel_capable = false` for semantic sessions, `primary_document_window` returns `view.active` in every existing configuration, so this is seam adoption that becomes load-bearing in 2B. - Verification on the merge result: `cargo fmt --check` clean; strict workspace Clippy clean; **1,832 default + 2,015 CRDT** library tests; `bottom_panel_stage2a_acceptance` **17**; bottom-panel Stage 1 46; statusline segments 8 CRDT; m11_5 semantic 2 CRDT; GPU initial target 14 CRDT; terminal config 12 CRDT; vterm Stage 1/2 10 / 6; folding Stage 2 48; M4 121; required GPU 202; `git diff --check` clean. - **Every routed producer is now pinned at a seam its production caller uses, and each pin was falsified by revert**: #1 follow, #2 lazy CRDT upgrade, #3 `CursorByte`, #5 decorations, #7 `Viewport` (aligns without focusing), #8 `Pointer` (aligns and focuses), #9 the terminal-context gate, #12 statusline, #21 the publication filter, plus the focus-class negatives. #1/#3/#21 required extracting three named helpers, because their only production caller is `dispatcher_loop`, which no test can drive. - **Three lessons about the TESTS, not the code, all from review:** (a) a *structural* test comparing the two authorities directly does **not** catch a misrouted consumer — only consumer-level assertions do; (b) a daemon-path test must `register_session` or the event is dropped at the uninstalled-session check before reaching the code under test; (c) a discriminating fixture must make the two routings DISAGREE — comparing two non-terminal buffers, or two windows with no selection, yields the same answer either way and proves nothing. Round 2 found four of my own pins vacuous by exactly these shapes, and round 3 found two more problems of the same family: a pin placed at a HELPER while production called it from a producer (reverting only the producer's call site left every test green), and a socket-pair assertion whose blocking read made a regression HANG instead of fail. Both now assert at the producer, with read timeouts on every read. - **Review round 1 closed: 4 P1 + 2 P2, all real.** The P1s were a stale-`Pointer` focus steal (the failed-alignment arm returned the window, so #8's activation focused it before `dispatch_pointer` rejected the buffer), the missing A2A-2 two-context fan-out, a census suite that asserted the AUTHORITY rather than the CONSUMERS, and the missing main integration. **Two of the new pins were themselves vacuous on the first attempt** — the dispatcher test passed because an unregistered session is dropped at `daemon.rs:1962` before reaching the aligner, and the painter test was a fixed-point check that survived deleting `text_view.render`. Both now fail under their own bite. - **`vterm_stage3_acceptance::a37` is a pre-existing flake here**, not a Stage 2A regression: measured **6/8 failures on the base commit** and **7/8 on the branch** in matched isolated samples. It needs a real daemon + real PTY + headless GPU and is documented load-sensitive. It also silently returns `ok` unless `pmacs-gpu` has been built, and is `crdt`-gated so CI never runs it at all. - **Two suites are dark without `--features crdt`**: `m11_5_semantic_acceptance` reports **0 tests** and `gpu_initial_target_acceptance` reports **1** in the default config. Both are semantic-census suites, so Stage 2A must be gated with the feature on or its most relevant coverage never executes. - Stage 1 merged as **#155** (`main` @ `e745068`, 2026-07-24, after two review rounds). No protocol change. Durable substrate facts live in `docs/agent-handoff.md` §1; the two round lessons are in §5. - Landed-docs follow-up merged as **#156** (`main` @ `d152120`, 2026-07-25). - **Stage 2 framing: `docs/bottom-panel-stage2-framing.md` revision 4**, on branch `githubsucks/bottom-panel-stage2-framing` (three commits, one per revision), worktree `../pmacs-bp-stage2`, based on `githubsucks/main` @ `ccf29e3`. Round 1 closed 2 blocking + 3 high; round 2 closed 1 blocking + 2 high + 1 medium and decided both open items; round 3 closed 1 blocking + 1 high + 1 medium. No open items remain. The approved parent framing `docs/bottom-panel-framing.md` (rev 4) remains authoritative, **including its acceptance criteria 37–55**. - Retained, carrying nothing unmerged: branch `bottom-panel` and worktree `../pmacs-bottom-panel`. - **Stage 2 ships as two serial slices**, 2A landing before 2B branches: **2A** = classified §1.3 census routing + `paint_frame` per-window painter extraction (with the active-window auto-scroll preparation), no protocol change; **2B** = protocol **v21** (`InstanceMessage::PanelFrame` plus `FrontendEvent::{FrontendCellGeometry, PanelResizeRows, PanelPointer}`, gated both directions, each extended enum byte-pinned on its own previous final variant), daemon panel projection, the GPU band, and the negotiated `panel_capable` flip. Stage 3 is the adopter default flip. - **Correction — this entry previously mis-stated the census contract.** It is **not** "route every consumer through `primary_document_window`". Q#BP14 classifies the 23 reads into four classes and routes only the **Projection** class that way; focus/input (#13–#15, #23), focus chrome and surface-routed (#16–#19), and focus/session (#20) keep their own authorities. Rerouting them would break remote-op validation and application, `DispatchIdle`, presence, focused search/menu/completion routing, and terminal bell ownership. The Stage 2 framing carries the full table. - **The GPU document bottom is three boundaries, not one.** `text_area_bottom` (`pmacs-gpu/src/main.rs:8490`) is today `status_band_top`, `geometry_capacity_bottom`, and `document_text_bottom` at once. Once a band is installed they diverge: the status chrome must stay pixel-identical at the physical window bottom while document consumers move. A blanket rewrite of that helper moves both together and passes an "everything moved" assertion, so the Stage 2 criterion asserts **both directions in one scenario**. The census is 20 production sites (8 status-owned, 12 document-owned) + 1 definition + 8 test sites = 29 matches; the framing carries the per-site table. The three easiest to misclassify are document completion `:6140`, minibuffer candidates `:7351`, and edge scrolling `:8561` — each with its own visible symptom. - **Folding Stage 3 and this arc's Stage 2 both touch the semantic projection.** Whichever is framed second re-scouts the other's landed state. ## 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 ``` ## Documentation lane - Portable branch: `githubsucks/handoff-2026-07-20` - Carries synchronized `AGENTS.md` / `CLAUDE.md`, this ledger, the durable handoff refresh, and the keybinding reference correction. - It changes no runtime code. - Review and merge this documentation branch separately; it must not be folded into a feature framing branch. - Now also absorbs both landed arcs: Vterm Stage 1 (#126) and the config registry (#127). Canonical `main` is merged into it up to `2e37c04`, so its diff against `main` is documentation only. ## Closed since the last snapshot - **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: **the workspace sweep must run with an isolated `XDG_CONFIG_HOME`**, because the real user `init.lua` installs a local package and the losing race leaks a status message into painted-frame comparisons. 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 `