pmacs/docs/active-work.md

981 lines
56 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Active work — cross-machine resume ledger
**Snapshot: 2026-07-30.** 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.
**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` @ `7586905` (the docs-only coherence listview
correction #189, atop the docs-only landed-state refresh #185, the M4
config-sink race fix #174, bottom-panel Stage 2B-1 #184, the
Journey/GPU directory-target ratchet #183, Journey Stage 1a #182 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 `0442d78`, and **the
recovery floor advances with it**: the check below now requires
`7586905` or newer, so a tree at `0442d78` 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:
```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 `7586905` — 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.
## The CRDT half of the test corpus is dark in CI — NEEDS A LANE
- **No branch, no framing yet.** Found while gating #166, then measured
properly during the vterm as-framed audit. Deliberately kept out of #166 so
a CI change would not arrive after review approval.
- **Root cause:** `.github/workflows/ci.yml` never enables the `crdt` feature
anywhere — zero hits across the workflow directory. The `test` job runs
`cargo test --all-targets --no-default-features --features luajit|lua54`.
Every `#[cfg(feature = "crdt")]` test is therefore **not compiled** in CI,
not merely skipped.
- **Measured, `--list` under CI's exact flags versus the same flags plus
`crdt`: 3,176 vs 3,449 — 273 tests dark.** Re-measured at `74301d1`
(2026-07-26; at `fe8b8ba` it read 3,170 vs 3,443, the same 273 dark —
#176 added six tests, none of them `crdt`-gated). **The number moves
with every merge and must be
re-measured, not quoted.** #168 reported 3,024 vs 3,288 — 264 dark,
177 in the library — at `1b6a084`; #178 then added CRDT-only
generated-buffer coverage, and other lanes landed CRDT tests in
between. Per target:
| dark | CI | full | target |
|---:|---:|---:|---|
| 185 | 1,848 | 2,033 | **the library itself** (`src/lib.rs`) |
| 21 | 15 | 36 | `m5_5_acceptance` |
| 13 | 1 | 14 | `gpu_invocation_acceptance` |
| 13 | 1 | 14 | `gpu_initial_target_acceptance` |
| 8 | 0 | 8 | `m10_11_acceptance` |
| 6 | 0 | 6 | `auto_pair_crdt_acceptance` |
| 6 | 0 | 6 | `m10_2_perf` |
| 4 | 5 | 9 | `vterm_stage3_acceptance` |
| 4 | 0 | 4 | `m10_10_perf` |
| 3 | 0 | 3 | `compile_mode_crdt_acceptance` |
| 2 | 22 | 24 | `theme_faces_acceptance` |
| 2 | 0 | 2 | `m11_5_semantic_acceptance` |
| 1 | 14 | 15 | `terminal_copy_mode_acceptance` |
| 1 | 9 | 10 | `vterm_stage1_acceptance` |
| 1 | 7 | 8 | `statusline_segments_acceptance` |
| 1 | 10 | 11 | `gpu_font_acceptance` |
| 1 | 0 | 1 | `auto_indent_crdt_acceptance` |
| 1 | 0 | 1 | `m10_11_perf` |
The rows sum to 273; the table is the whole census, not its head.
- **The single worst line is the library.** `cargo test --lib --features crdt`
is a REQUIRED local gate in `CLAUDE.md`, and CI has never run it. 185
library tests — the whole CRDT half — are developer-machine-only, and
that count grows with every merged branch that adds a `crdt`-gated
unit test.
- **Ten suites run zero or one test in CI**, including `gpu_initial_target`
(#148's entire acceptance, 1/14), `gpu_invocation` (#141's, 1/14), and
`a37`, the Vterm Stage 3 real-daemon/real-PTY/real-wgpu path that #135
built specifically because "a decoded-message fixture would prove none of
the three fit together".
- **⚠ `a37` will report green in the new job without running, unless the
job builds `pmacs-gpu` AND sets `PMACS_REQUIRE_GPU=1`.** Measured
2026-07-26 while gating #173. `a37_real_daemon_real_pty_and_headless_gpu_
render_one_terminal_session` derives its sibling binary path from
`CARGO_BIN_EXE_pmacs`, and on a missing binary it `eprintln!`s a skip and
**returns `ok`**. A fresh worktree running
`cargo test --features crdt --test vterm_stage3_acceptance` reports **9/9
in 0.17 s having never run it**; a real run takes ~4 s. Only
`PMACS_REQUIRE_GPU=1` promotes that skip to a failure, and `CLAUDE.md`
applies that flag to `cargo test -p pmacs-gpu` — a **different package**,
so the required local gate does not cover a37 either. The `gpu-render`
job already sets the flag, which is what makes fix-shape part 2 sound;
state it as a **requirement** of that job rather than inheriting it by
luck, because a `crdt` leg added to the plain `test` job would run a37
vacuously.
- **`a37` is also load-sensitive, which changes how to read the expected
first-run failures.** It passed at `d152120` and failed at that *same
commit* twenty minutes later, with a second agent saturating the machine
with `rustc` in between; it then failed identically on `d152120`,
`04c5ad1`, and the #173 merge commit, which is how #173 established the
failure was not its own. The signature is `last_frame_text` all spaces
with `rendered_nonuniform_frames` nonzero — frames arrive, content does
not. `pmacs-gpu`'s own suite flaked the same way under the same load
(201/202, then 202/202 on immediate rerun). **So a red a37 on the first
CI run is ambiguous by construction**: before treating it as a real
failure, run the same command on the merge base, and prefer serialized
execution for this suite over retry-until-green.
- **Sort deliberate from accidental before proposing a fix.** Some of the 264
are perf suites that are `#[ignore]`d by default and belong to their own
jobs (`m10_2_perf` 6, `m10_11_perf` 1). `m10_10_perf` has **no** `#[ignore]`
and no CI job naming it, so it looks accidental. This classification is not
finished and is the lane's first task.
- **Fix shape, two parts** (the flag combination is verified to work:
`--no-default-features --features luajit,crdt` lists 10 vterm Stage 1 tests
versus 9 without):
1. a `crdt` leg on the `test` job for the non-GPU suites and the library;
2. the GPU-requiring `crdt` suites onto the existing `gpu-render` job, which
already has lavapipe and `PMACS_REQUIRE_GPU=1`
`vterm_stage3_acceptance`, `gpu_invocation_acceptance`,
`gpu_initial_target_acceptance`, `gpu_font_acceptance`.
- **Expect first-run failures, and budget for them.** These would execute in
CI for the first time ever: real PTY timing on CI runners, wgpu under
lavapipe, and daemon-socket tests at unfamiliar concurrency. Start
ubuntu-only and decide about macOS from evidence. A red first run is the
lane working, not the lane failing.
- Mitigating fact, verified rather than assumed: #166's three unit pins are
**not** `crdt`-gated and do run under CI's exact flags, including the
controller-release pin whose only job is catching the plausible wrong fix.
- **This lane also owns a `--lib --features crdt` flake, observed and
scoped without overclaiming its cause** (inherited from #178's gating,
where the terminal lane recorded it). `cargo test --lib --features
crdt` failed ~1 run in 5 on
`process::tests::setsid_escapee_is_not_reaped_and_teardown_reclaims_readers`
`active_reader_probe` returning `None` at `process.rs:3179` ("live
runtime probe"). **Pre-existing and unrelated to #178:** that branch
did not touch `src/process.rs` at all, and the test passed 10/10
standalone; the observed
failures were during parallel full-suite runs. That localizes the
trigger to suite load or interaction, but does **not** distinguish
parallelism from another full-suite effect — no serial full-suite bite
was run. The leading code-path explanation is the known `drain_until`
trap: draining for `Started` also ticks, and a tick can reap the leader
before the following `active_reader_probe`. That is an inference from
the failure site and control flow, not yet a falsified root cause.
Discriminating it belongs here. Two unnamed CRDT failures in #178's
round-2 gating are a plausible match but remain **unattributed** — no
test names were captured.
- **A second standing obstacle for this lane:** `cargo clippy --workspace
--all-targets --features crdt -- -D warnings` **fails on `main`**
measured at `74301d1`: seven errors before the build aborts, four in
`src/daemon.rs` (`useless_conversion` at 3996, missing doc backticks at
4076, `too_many_lines` 112/100 at 4083, an unneeded `mut` at 4965) and
three in `tests/vterm_stage3_acceptance.rs` (`too_many_lines` at 637
and 793, a redundant `continue` at 843). **Treat that as a lower
bound, not an inventory:** Clippy abandons the remaining targets once
one fails, and a run on an older tree surfaced a further doc-backticks
error in `tests/auto_indent_crdt_acceptance.rs:42` that this run never
reached. The
standing gate list runs Clippy without `crdt`, so these lints have
never been enforced. Any CI job that compiles the `crdt` targets has to
fix them first or it will be red on arrival.
## Journey lane (P1) — STAGE 1a MERGED; STAGE 1b-1 IMPLEMENTED, PR OPEN
- **Branch `journey-stage1b1-compile-defaults`**, worktree
`../pmacs-journey-1b1`, based on `githubsucks/main` @ `22df6ab`.
`docs/journey-stage1b1-compile-defaults-framing.md` revision 2,
approved after one review round (two blocking, two major, all
accepted). **Implemented; PR open.**
- **ON MERGE OF #203, flip four places to landed.** The PR deliberately
ships them as *in flight*, because `COHERENCE.md` §25 says a grade
changes only on landed evidence and the PR is open: `COHERENCE.md`
§2's step-9 verdict row (Partial → **Works**), §2's post-table
keybinding-inversion paragraph (third example answered; the build is
no longer its uncontested example), §20 Priority 1 and its arc list (in
flight → done), and
`docs/agent-handoff.md` §1's arc bullet (IMPLEMENTED → LANDED).
**Recorded here because an unowned doc flip is exactly how this
ledger's drift starts** — the same rule-4 precondition that kept #176's
lane alive past its merge.
- **A lexical path expectation is wrong for anything detection touched.**
`pmacs.project.detect` canonicalizes before walking
(`canonicalize_or_passthrough`, `src/project.rs:509-511`) while the
suite's `canon()` is lexical, so the compile-directory assertions
passed on Ubuntu and **failed both macOS legs**, where `/var` is a
symlink to `/private/var`. Fixed with a `detected_root()` expectation
and pinned by a **symlinked fixture**, which reproduces the disagreement
on any platform — a Linux-only bite could not have caught it.
- **Bites found two vacuous pins of my own.** The nested-project pin
passed with the keybinding removed, because `minibuffer.contents()` is
`""` both for an empty prefill and for no minibuffer at all — `""`
compared with `""`. And the hostile-`defaults` pin called
`compile.run` directly, so it never consulted `defaults` and passed
with the guard removed. Both now assert their precondition.
- **Round 1's blocking finding is the Stage 1a lesson repeating.**
Sharing one cwd resolver between the prompt and the run is *not*
enough: `pmacs.minibuffer.read` is async, the active window can change
while the prompt is open, and `run` re-resolved at accept time — so
the prompt could offer `cargo build` for A and execute in B. The
interactive command now **captures** `context()` and passes its `cwd`
through to `run`, which is `commit_to`'s discipline on a smaller seam.
The second blocker was that no pin crossed the accept boundary at all,
so that defect passed every proposed pin.
- **Why this lane exists.** `COHERENCE.md` §20 Priority 1 names Journey
Stage 1b as the golden journey's remainder — "the compile binding +
Cargo defaults, LSP spawn guidance, and the welcome buffer" — and it
had no branch, no framing, and no lane. It is the only §20 priority
with nothing in flight.
- **What 1b-1 is.** Journey step 9 only: a global `C-c c` for
`compile.run`, and a first prompt prefilled from the detected project
kind instead of empty. Lua, tests, and docs; **no Rust change and no
protocol change**. 1b-2 (LSP spawn guidance, step 6) and 1b-3 (welcome
buffer, step 4) are separate stages — see the framing §7 for why the
three are not one PR.
- **`ProjectKind::Cargo` does not exist.** `COHERENCE.md` names it twice
(§2's step-9 row and §20 Priority 1); the variant is
`ProjectKind::Rust` (`src/project.rs:78`) and line 77 is its doc
comment. The audit read the comment. Lua matches on the **tag string**
`"rust"` that `pmacs.project.detect` returns, so no Rust primitive is
needed. The PR corrects both COHERENCE sites.
- **The suggestion and the run must share one cwd resolution.** Today
the cwd is computed *inside* `pmacs.compile.run`
(`builtin/runtime/compile.lua:765`), after the prompt has closed, so a
suggestion computed in the command's `fn` would obey a different rule
than the run. The stage extracts it and exposes
`pmacs.compile.context()`, consumed by both.
- **The trap the acceptance is designed around.** The last-resort cwd is
`std::env::current_dir()` evaluated at call time
(`pmacs-protocol/src/message.rs:1703-1706`), so in tests it is the
**test runner's cwd — the pmacs repo root, itself a Cargo project**.
`compile_mode_acceptance.rs:1721` already pins this. A pin asserting
"no Cargo suggestion" that reaches the fallback would report pmacs's
own `Cargo.toml`. Negative pins therefore use a fixture carrying a
*different* marker so the fallback is never consulted;
`set_search_boundary` does **not** help here, because it only clamps a
walk that starts below the boundary.
- **Known limitation, deliberately not fixed.** After `pmacs <dir>` the
active buffer is dired's and **pathless** (`pmacs.buffer.create` never
assigns a path; dired compensates through its own module-local
`handle_for_buffer`, `dired.lua:205-217`), so the cwd falls through to
the process cwd. Launched from elsewhere that is the wrong directory.
The fix is `COHERENCE.md` §8 (First-Class Execution Locations), a
model gap; reaching into dired's private table for one string would
add an interaction island.
- Recovery from a clean checkout — **the two-argument form does not
work** (`git worktree add <path> <remote-only-branch>` fails with
`fatal: invalid reference`, because after a bare fetch no local branch
exists):
```sh
git fetch githubsucks
git worktree add ../pmacs-journey-1b1 \
-b journey-stage1b1-compile-defaults \
githubsucks/journey-stage1b1-compile-defaults
```
## Journey Stage 1b-3 (P1) — FRAMING OPEN, revision 4
- **Branch `journey-stage1b3-welcome`**, worktree `../pmacs-journey-1b3`,
based on `githubsucks/main` @ `1f290d5`. **Framing only; no code, no
PR yet.** `docs/journey-stage1b3-welcome-framing.md` revision 4, three
review rounds closed (round 1: four findings; round 2: two acceptance
holes plus a doc correction; round 3: a visibility mismatch and an
unobservable assertion; all accepted). The last of the 1b split
(1b-1 landed #203, 1b-2 landed #204 at `5376af1`).
- **What it is.** Journey step 4 / `COHERENCE.md` §18: a fresh `pmacs`
greets the user with an empty buffer, an empty status line, and no
indication that `M-x` exists. The stage renders a three-line welcome
into `*scratch*` and adds a minimal `M-x help`.
- **`EditorState::new()` is NOT the no-argument entry point**, and
revision 1 rested every criterion on the assumption that it was.
`EditorState::open` calls it *before* resolving the target
(`src/editor.rs:944`), the daemon constructs one too, user config runs
*inside* it, and desktop restore happens much later
(`restore_desktop_if_armed`, `:3637`, inside `run()`'s `RunLocal`
arm). The stage now adds a **launch-finalization seam** called right
after desktop restore, with `had_file` — already threaded to that
point for the same kind of question — as the no-target signal.
**Round 2 closed the wiring hole rather than disclaiming it.**
Revision 2 called `run()`'s call to the seam an untestable residual —
but deleting that one line left every proposed pin green while
shipping no welcome, because the pins called the seam by hand. The
terminal-free prefix of `run()` (everything before `Frontend::new()`)
is now extracted into `prepare_startup`, which `run()` delegates to
and the pins drive. It is **`pub`**, not `pub(crate)`: the journey
suite is a separate integration crate and cannot reach crate-private
items, and `run`/`new`/`open`/`install_state_dirs`/
`restore_desktop_if_armed` are already public, so this completes that
surface rather than widening it for a test. The pin must assert
desktop restore was **unarmed** (an ambient `init.lua` calling
`desktop_mode(true)` would otherwise change the result) and must
assert buffer content only, since `install_state_dirs` resolves real
XDG/`PMACS_STATE_HOME` roots that tests cannot override
(`set_var` is unsafe and forbidden).
- **The step-2 pin is NOT amended.** Revision 1 analysed a status-line
welcome and then chose `*scratch*`, but kept the amendment — an
internal contradiction. The status stays empty, so the pin stays true,
and "no error text" has no defined predicate over an unstructured
status string anyway.
- **`C-h` is NOT free, and the reason is load-bearing.** It is bound to
`buffer.delete-word-backward` because non-kitty terminals cannot
disambiguate Ctrl+Backspace from Ctrl+H — both produce byte 0x08
(`builtin/keymaps/default.lua:78-86`). Rebinding it to a help prefix
would break Ctrl+Backspace on every legacy terminal, so §2's step-4
row calling it an oversight is wrong: it is a deliberate trade. The
help-prefix decision is deferred to §20 Priority 4's discovery arc
with the constraint recorded.
- **`*help*` already exists** (`builtin/commands/default.lua:1226`,
`show_help_text`, reused buffer, buffer-local `q`), used by
`editor.describe-command` / `-setting`, over `src/help.rs`'s
renderers and link resolution. Two gaps recorded rather than
inherited silently: it writes with `buf:delete`/`buf:insert` instead
of `set_generated_contents`, and it is **found by name**, so a
foreign `*help*` would be cleared.
- **Deliberately NOT `set_generated_contents` for the welcome** — that
lifts read-only, discards history and marks the buffer generated, all
wrong for a buffer step 5 requires the user to type into immediately.
The one place not adopting that invariant is correct, stated so a
later audit does not "fix" it.
- **Step 4 stays Partial, but there IS a §25 obligation** — revision 1
claimed there was none. The scorecard's row 18 and §18's ground truth
both read **Missing**, and both become false on merge: they move to
**Partial**. A stage can be too small to flip its journey step while
still falsifying a "missing entirely" grade.
- **The welcome renders from a structured entry list**, not from scraped
prose: `M-x help` mixes a chord with a command name and `C-c c` is two
chords whose boundary prose does not mark, so the binding checks run
`pmacs.keymap.lookup` over the same list that renders the text.
- **`pmacs.command.invoke` is NOT the M-x path** — it is the
programmatic API. M-x is `editor.execute-command`, a minibuffer with
the `commands` completion source that calls `invoke_interactive` on
accept. The `M-x help` pin dispatches the chord, enters the name and
accepts — and asserts **`pmacs.minibuffer.selected() == "help"`
BEFORE RET**, because a selected candidate shadows typed text (dired
refused a completion source for exactly this reason) and `accept()`
does `session.take()`, so nothing about the accepted value survives
afterwards.
- **Integrate late.** #204 has landed at `5376af1`; this lane still
touches `COHERENCE.md`, `docs/agent-handoff.md` and
`docs/active-work.md`, so merge `main` at PR time rather than opening
a standalone refresh PR.
```sh
git fetch githubsucks
git worktree add ../pmacs-journey-1b3 \
-b journey-stage1b3-welcome \
githubsucks/journey-stage1b3-welcome
```
## 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 2 COMPLETE; STAGE 3 IS THE LAST STEP
**Stage 1, the Stage 2 framing, and Stages 2A, 2B-1, 2B-2 and 2B-3 are
all on `main` @ `4cd4a7b`** (#155, #175, #177, #184, #187, #198). Stage 2
is complete. Durable facts are in `docs/agent-handoff.md` §1, including
the v20-baseline / v21-negotiated handshake that Stage 2B-3 made
compatible.
- **Stage 3 — the adopter default flip — is the arc's last step and is
not started.** This lane stays until it lands; it is not removed at
2B-3's merge.
- **DAP waits for Stage 2, not Stage 1** — that dependency is now
satisfied.
## Test ambient-root isolation — FRAMING OPEN, revision 4
- **Branch `test-ambient-config-isolation`**, worktree
`../pmacs-test-isolation`, based on `githubsucks/main` @ `4cd4a7b`.
**Framing only; no code, no PR yet.**
`docs/test-ambient-config-isolation-framing.md` revision 4, three review
rounds closed (eight blocking, six major, all accepted).
- **What it is.** Integration tests use the developer's real ambient
roots. `#[cfg(not(test))]` guards config loading against the crate's
own unit tests only, so the **65** files in `tests/` that construct an
editor load the real `init.lua` — and, separately, `EditorState::new` materializes bundled
packages unconditionally into the real `XDG_DATA_HOME`/`$HOME`.
**It is not read-only**: `~/.local/share/pmacs/builtin-packages/`
exists on the development machine.
- **Why it matters now.** `cargo test` is red on any machine with a real
`~/.config/pmacs/init.lua` (11 of 67 in `compile_mode_acceptance`) and
green in CI, so the failure is attributed to whatever branch is
checked out. Every local gate run in this repo currently needs all
five bootstrap-storage variables controlled as a workaround:
`XDG_CONFIG_HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME`,
`XDG_CACHE_HOME`, and `PMACS_STATE_HOME`.
- **Round 1's four blocking findings, all confirmed in code:** the
read-only assumption was already false; the population count was 18
when 65 of 96 files construct an editor, from a grep that did not
match `EditorState::new`; 5 files are both in-process and spawned, so
a file-level partition cannot work; and `EditorState::open` calls
`Self::new()` while `journey_acceptance` requires that exact entry
point.
- **Two constraints any fix must respect.** `std::env::set_var` is
`unsafe` and the crate forbids it, so in-process tests cannot isolate
themselves (precedent: `Installer::root_override`). And config loading
shares one block with `set_init_complete()`, which
`m8_2_acceptance.rs:75` explicitly depends on — skipping the block
would leave integration tests permanently in the init phase.
- Recovery from a clean checkout — **the two-argument form does not
work**, verified by running it (`git worktree add <path>
<remote-only-branch>` fails with `fatal: invalid reference`, because
after a bare fetch no local branch exists):
```sh
git fetch githubsucks
git worktree add ../pmacs-test-isolation \
-b test-ambient-config-isolation \
githubsucks/test-ambient-config-isolation
```
## Reap-ledger silent failures — IMPLEMENTED, PR OPEN
- **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:
```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:<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
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; 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.