pmacs/docs/active-work.md

1131 lines
66 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-28.** 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.
**No lane below is retained past its merge.** The PTY terminate
diagnostic (#176) was the last one — retained because rule 4 removes a
merged lane only *after* its durable facts reach
`docs/agent-handoff.md`, and that absorption was unowned. This snapshot
owns 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 this 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 describes a tree nobody has looked at since. **When a PR is
opened, give it a lane.** #174 has since merged, so per rule 4 its lane
is gone again and its durable lesson is in `docs/agent-handoff.md` §5;
#171's lane is below.
## 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` @ `0442d78` (the M4 config-sink race fix #174, atop
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 `7fd646d`, and **the
recovery floor advances with it**: the check below now requires
`0442d78` or newer, so a tree at `7fd646d` — or at `6bee09d` — 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 `0442d78` — 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.
## Bottom-panel lane (Arc 7) — 2B-1 MERGED; 2B-2 IN FLIGHT
Stage 1, the Stage 2 framing, Stage 2A, and **Stage 2B-1 are all on
`main`**. Framing revision 5's three-way split of 2B was explicitly
approved on 2026-07-27; revision 6 records PR #184's review correction.
**2B-2 — the daemon panel projection and epoch machine — is under way**
on branch `bottom-panel-stage2b2`, worktree `../pmacs-bp-stage2b2`,
branched fresh from `githubsucks/main` @ `6bee09d`. It is *not* stacked
on the 2B-1 branch, which is the rule for every slice in this arc. Note
that `main` has since advanced to `0442d78`; the only difference is the
test-only #174, so the slice's integration surface is unchanged.
**2B-2's boundaries, restated because they are easy to overrun:** the
production `Hello` stays at v20 and `panel_capable` stays `false`. The
slice is dark/test-only capability exactly as 2B-1 was. Compatible v21
activation, the GPU band, and the negotiated capability flip are all
2B-3's, and 2B-3 may **not** simply change the unsolicited `Hello` to
21.
- **Stage 2B-1 MERGED as #184** (`main` @ `6bee09d`, 2026-07-28; all
twelve checks green on the reviewed head `5539b6e`; two review rounds
plus a gate-found follow-up). Branch
`githubsucks/bottom-panel-stage2b` and worktree `../pmacs-bp-stage2b`
are retained and carry nothing unmerged. Durable facts — the
schema-support-versus-advertisement split, the shared `wire_grid`
boundary, authoritative `Absent`, and the two epochs — are in
`docs/agent-handoff.md` §1 per rule 3, not here.
- **What 2B-1 deliberately did not do**, because 2B-2 and 2B-3 must not
re-litigate it: no producer, no consumer, no capability change.
`panel_capable` is still `false` for every semantic session, so the
journey grade is unchanged and every shipped v20 client remains
attachable.
- **2B-3 inherits a hard constraint from 2B-1's review**: it owns a
*compatibility-preserving* v21 activation mechanism and may **not**
simply change the unsolicited `Hello` to 21. The handshake is
server-first, so that one-line change locks out every shipped v20
frontend before it can even send an `AttachRequest`.
- **Two review rounds, and what each cost.** Round 1: `PanelFrame`
needed an explicit `buffer_id`, the transport ratchet had to drive the
real attach path rather than a detached codec assertion, and shared
grid bounds needed one validator. Round 2: the server-first `Hello`
made the advertised v20↔v21 compatibility one-way; `COHERENCE.md` and
the handoff still named only v20 schema support; framing §9 named a
nonexistent aggregate 2B suite instead of the three exact slice
suites; and the panel plus copied-terminal "one byte over" fixtures
were actually two bytes over.
- **The full gate — not review — found two version-ladder omissions and
one probe contradiction.** The statusline and Vterm Stage 3 ladders
still pinned v20 and rejected v21. Separately, Vterm Stage 3's
headless probe exited its loop as soon as resize plus two nonuniform
composites were observed, while its acceptance later required the PTY
child's `VTERMROW` output in the final frame; the v20-compatible
handshake made that scheduling race deterministic, so the report
sampled a blank frame. The probe now waits for the exact child-output
observation its acceptance asserts.
- **The probe fix then leaked its own fixture, and that is the reusable
lesson.** The generic runner hard-coded the *producer* fixture's
`VTERMROW` breadcrumb, so the CAT input fixture could satisfy every
assertion yet never satisfy the loop exit — it waited out the
20-second safety deadline and passed on the deadline. Producer probes
now name their own required frame text while input probes finish on
the latched echo, and the report exposes `completion_observed` which
**both** paths assert, so a deadline-driven pass cannot hide a stall
again.
- **Final verification on the reviewed head:** `cargo fmt --check`;
strict workspace Clippy; library **1,849 passed + 3 ignored** default
and **2,034 passed + 4 ignored** CRDT; bottom-panel Stage 1 / 2A /
2B-1 **46 / 17 / 16**; folding Stage 2 **48**; GPU font **11**;
statusline **8 CRDT**; m11_5 semantic **2 CRDT**; GPU initial target
and invocation **15 / 15 CRDT**; the handshake consumers m5_5 / m5_7 /
mode-system wiring **36 / 7 / 1 CRDT**; Vterm Stages 1 / 2 / 3
**10 / 6 / 9 CRDT**, including the required real daemon + real PTY +
real wgpu probe; M4 **121 passed + 3 ignored + 1 filtered**; required
GPU **202/202**; the isolated-config one-invocation full workspace
sweep; and `git diff --check`.
- Retained as classification rather than erased: the first
required-GPU pass was **201/202** on
`a_fraction_draws_rule_pixels_between_its_operand_rows`, a rendering
test structurally outside a protocol-only diff, which passed
immediately in single-threaded isolation and **202/202** on the
mandatory complete rerun. Separately, library and Vterm attempts
*inside the restricted tool sandbox* produced `Operation not
permitted` failures in socket-based attach tests; the authoritative
outside-sandbox reruns passed.
- **Ordering for the rest of the arc is fixed:** 2B-2 branches from
landed `main`; 2B-3 branches only after 2B-2 lands; Stage 3 (the
adopter default flip) last. Each slice starts fresh from landed main.
- **Stage 2A MERGED as #177** (`main` @ `0a3fcd1`, 2026-07-26, all twelve
checks green at `8424172`, three review rounds). Branch
`githubsucks/bottom-panel-stage2a` and worktree `../pmacs-bp-stage2a`
are retained and carry nothing unmerged. 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.
- **Stage 2A verification on its 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 6**
is on branch `githubsucks/bottom-panel-stage2b` (revision 5 is commit
`56301ed` there),
worktree `../pmacs-bp-stage2b`. Revisions 14 remain on
`githubsucks/bottom-panel-stage2-framing` (head `4fbd47f`, four
framing commits, revision 4 at `49757e5`). 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. Revision 5 adds no decision; it records the approved
2B-1/2B-2/2B-3 implementation split. Revision 6 corrects the
server-first compatibility contract, durable protocol claims, exact
acceptance-suite names, and `limit + 1` fixture. The
parent framing `docs/bottom-panel-framing.md` (rev 4) remains
authoritative, **including its acceptance criteria 3755**.
- Retained, carrying nothing unmerged: branch `bottom-panel` and worktree
`../pmacs-bottom-panel`.
- **Stage 2 ships as four serial implementation slices**, each landing
before the next branches:
**2A** = classified §1.3 census routing + `paint_frame` per-window
painter extraction (with the active-window auto-scroll preparation), no
protocol change; **2B-1** = reserved protocol schema **v21**, with
production advertisement held at v20,
(`InstanceMessage::PanelFrame` plus
`FrontendEvent::{FrontendCellGeometry, PanelResizeRows, PanelPointer}`,
gated both directions, each extended enum byte-pinned on its own
previous final variant); **2B-2** = daemon panel projection and epoch
machine; **2B-3** = compatible v21 activation, 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).
## Resource-op delete guard lane — PR #186 OPEN, PROPOSED, DO NOT MERGE
- Portable branch: `githubsucks/resource-op-delete-guard`; worktree
`../pmacs-resource-op-delete`. **PR #186**, base `main`. Currently
framing only — `docs/resource-op-delete-guard-framing.md`, **revision
5** — plus this lane entry. No runtime code yet.
- **Measured 2026-07-28, `main` @ `7586905`:**
```
$ git rev-list --left-right --count HEAD...githubsucks/main
5 0
```
Five commits ahead, **0 behind** at the pushed revision-5 head. This
count includes the revision commit itself; revision 4 recorded the
pre-commit count and was therefore one short.
- **This PR becomes the implementation PR.** Revision 2 dropped rev 1's
framing-PR-then-implementation-PR plan as a one-feature/one-branch/
one-PR violation. The framing is revised in place; implementation
commits land on this same branch **only after explicit user
approval**.
- **Live data-loss bug, reproduced four ways against `ad41cf1`.**
`pmacs.buffer.apply_resource_op`'s delete arm removes the path from
disk and *then* drops any buffer bound to it, with no dirty check at
any link — not the arm, not `remove_buffer_and_fire`, and not
`BufferRegistry::remove`, whose only guard is `editing_in_progress`.
Reachable through any language server's `WorkspaceEdit`. The four
modes: (a) the plain case returns `Ok(())` with file and buffer both
gone; (b) `ignore_if_not_exists = true` does **zero** filesystem work
and still destroys the buffer; (c) `recursive = true` reconciles
**nothing**, so a whole tree leaves orphaned buffers — the most
destructive arm does the least reconciliation, and it bypasses any
exact-path guard; (d) removal is not `kill_buffer`, so windows are
left bound to a removed `BufferId` and the registry can be driven to
**empty**.
- **Approved in principle after review round 1; revision 5 closes round
4's two contract P1s and the ledger-ownership P1. Still PROPOSED,
still not approved for implementation.** Settled: refuse
unconditionally; take the delete side now. Withdrawn: rev 1's
buffer-first ordering. The design is `stat/no-op/refuse → enumerate
and validate → mutate filesystem → reconcile`, which keeps
`on_removed`'s "path already gone" invariant and makes a failed
deletion leave buffers intact automatically.
- **The stable cross-lane ownership split with #171:**
> #186 owns the urgent **pre-filesystem refusal** for synchronous
> `apply_resource_op`. #171 later owns **full post-delete lifecycle
> reconciliation**, including the **async race where a buffer becomes
> modified after dired dispatch**.
#186 additionally **owns the shared walk query** (scan every
path-bound buffer, normalize once, component-aware `Path::starts_with`)
under the boundary's "whichever lands first owns the query"; #171
adopts it and extends it to `reconcile_rename`. **Neither lane guards
`pmacs.fs.remove`** — zero production callers today, named out of
scope by both.
- **#171 owns its own lane entry.** Revision 4 rewrote that sibling
block and was stale before push when #171 revision 8 landed 67 seconds
earlier. Revision 5 restores the block to `main`'s tree, so #186's
diff no longer changes it. The one fact this lane depends on is the
policy split above, which is stable through #171's pushed revision 8
and independent of its commit count.
- **Standing rule this lane learned the expensive way.** A census is a
reading, not a constant. **Do not write an ahead/behind count, a line
count, or a call-site count into this file that you have not just
produced with a command whose output you can paste.** #186 shipped a
stale line count, then a stale commit count, then a stale ledger
citation, in three consecutive revisions — each time by carrying a
measurement across a base change instead of re-running it. The
specific trap: a count taken against `ad41cf1` was reported in
present tense after `main` had moved to `7586905`, which silently
converted "0 behind" into a falsehood.
- **Four facts a re-scout should not have to rediscover**, all verified
at `ad41cf1`:
- **No caller reliably surfaces a raise.** The server pump runs under
`pcall(handle_server_requests)` (`builtin/runtime/lsp.lua:1892`), so
a raise unwinds past the `send_response` and the server is never
answered; and the two user-initiated paths route uncaught coroutine
errors through `pmacs.error`, which is **undefined** (11 call sites
in `builtin/`, zero definitions). Refusals must travel as values.
- **A partial batch is already the status quo** — verified in-repo:
two delete ops, the second raises, the first stayed applied. Any
framing claiming batch atomicity here is wrong. **Do not justify
this from the LSP spec.** Revision 2 of #186 wrote that LSP 3.18
"assigns `FailureHandlingKind.Abort` to resource-op-bearing edits";
**it does not** — recovery is described by the client's advertised
`workspace.workspaceEdit.failureHandling`, `Abort` is one of four
strategies, only `TextOnlyTransactional` degrades to abort for
resource changes, and **pmacs advertises none of them**. The
justification is repository evidence plus the judgement that a
visible partial refactor beats unrecoverable unsaved work.
- **`find_by_path` is singular and duplicates are reachable.**
`BufferRegistry::find_by_path` returns the first match in insertion
order, `EditorCore::find_buffer_for_path` inherits that, and
`pmacs.buffer.from_file` creates path-bound buffers with **no
dedup** — so a clean first match can hide a modified second. The
guard needs a full scan with component-aware `Path::starts_with`.
- **pmacs advertises no `workspace.workspaceEdit` capability at all** —
`"applyEdit": true` but no `documentChanges`, no
`resourceOperations`, no `failureHandling`; `grep -rn
failureHandling` returns 0. Parked, not fixed here.
- **Ownership claim, concretely.** For this lane's duration #186 owns:
the pre-filesystem refusal inside synchronous `apply_resource_op`; the
**shared walk query**; and `builtin/runtime/lsp.lua`'s
`apply_workspace_edit` plus the `workspace/applyEdit` server-request
boundary. It does **not** own: full post-delete lifecycle
reconciliation, the dired async race between dispatch and
`remove_blocking`, the rename side of the walk, or `pmacs.fs.remove`.
Do not run the two lanes concurrently over `builtin/runtime/lsp.lua`
without re-splitting that claim.
- **Two residues #186 deliberately leaves for #171**, both named rather
than silent: after a successful *clean* recursive delete, descendant
buffers stay orphaned-and-clean (widening removal would promote the
dangling-window/empty-registry defect from exact-path to tree-wide);
and after a successful delete with several clean duplicates on one
path, only the first is reconciled. #186 validates **every** match but
reconciles **one**, which is today's behaviour preserved on purpose.
- Files the implementation will touch: `src/lua_bindings/mod.rs`,
`builtin/runtime/lsp.lua`, `tests/m4_acceptance.rs`,
`tests/lsp_dispatch_seams_acceptance.rs`,
`src/bin/pmacs_fake_lsp.rs`. **Not** `src/daemon.rs`,
`pmacs-protocol/`, `builtin/runtime/dired.lua`,
`docs/agent-handoff.md` or `COHERENCE.md`. No protocol change.
- Recovery from a clean checkout:
`git fetch githubsucks && git worktree add ../pmacs-resource-op-delete
-b resource-op-delete-guard githubsucks/resource-op-delete-guard`.
## dired Stage 2 framing lane — PR #171 AT REVISION 9, AWAITING APPROVAL
- Portable branch: `githubsucks/dired-stage2-framing`;
worktree `../pmacs-dired-stage1`. **PR #171**, base `main`, integrated
up to canonical `main` @ `7586905`. Framing only —
`docs/dired-stage2-framing.md`, **3,844 lines measured at this
revision**, no runtime code. This lane rides that PR — it has since
rev 6, when #185 merged; do not open a standalone ledger PR.
- **Status: PROPOSED, never approved.** Revision 5 was reviewed and NOT
approved (six findings, four P1); revision 6 answered them; **revision
7 resolved a cross-lane conflict with PR #186**; revision 8 was
reviewed and NOT approved (six findings, four P1); revision 9 answers
them. The framing is at revision 9 after seven review rounds; **that
is not the same as approval**, and it must not be read as one.
- **Round 8 (rev 9) made every rev-8 correction singular and
executable:**
- Q#DR26 no longer retains the withdrawn execution-order claim; it
owns only the homogeneous enum/vector shape and delegates sequence
semantics to Q#DR29: **bus arrival, not filesystem execution**.
- 2a owns acceptance **2338 and 5055, including every suffixed
item**, so rev 8's new tests cannot fall between implementation PRs.
- The LSP gate is one manager-owned, generation-scoped
`HashSet<(LspServerId, String)>`. Both
`absorb_publish_diagnostics` and the now-server-aware
`mark_document_stale` check the exact pair; `did_open` clears one
pair, and server teardown clears all and only that server's pairs.
It is reclaimed, not falsely called size-bounded. Acceptance covers
both writers and both reclamation paths.
- Buffer-name following now uses explicit
`BufferNameOrigin::{Explicit, PathDerived}` state. The acceptance
collision is a file `${cwd}/notes` explicitly named `notes`, which
defeats rev 8's path-equivalence heuristic.
- **Round 7 (rev 8) found five contract defects of ONE family, and the
family is the transferable lesson: a guarantee assumed from a
mechanism whose implementation was never read.** Reply order assumed
to be execution order; a refusal assumed to be inert; `forget_uri`
assumed to cover every writer. Three of the five were literally that.
The decisions:
- **Reply order is NOT execution order.** `AsyncRuntime::tick`
(`src/async_runtime.rs:1003`) is `while let Ok(env) =
self.main.try_recv()` — a bus drain with **no execution token**, so
a worker can finish, be descheduled before sending, and have a later
reply arrive first. The ordering guarantee was **withdrawn, not
engineered**: reconciliation is order-independent, and `fs.lua:155-165`
already recommends serialization. Rev 9 makes the public contract
exact: overlapping mutations dispatch serially, awaiting one before
the next. A lock was rejected — it would serialize every fs mutation
for a hazard absent from shipped in-tree callers.
- **`EditorCore::kill_buffer` damages editor state BEFORE it can
refuse.** It clears `round_trip_buffers`, closes side windows and
redirects ordinary windows, and only then can
`BufferRegistry::remove` return `ConcurrentEdit`. So "refused" is
not "nothing happened". Fixed by preflighting `editing_in_progress`
— sound because phase 1 is pure `EditorCore` and holds no Lua
handle, so nothing can re-enter between check and removal.
- **Buffer names are set from `path.display()` AS GIVEN** while only
`file_path` is normalized (`src/editor_core.rs:917`,
`src/lua_bindings/mod.rs:3112`), so a relative open leaves a short
name that a string-equality rule mistakes for user-chosen. Rev 8
moved to path-equivalence; rev 9 replaces that inference with
explicit provenance because a custom name can equal the path.
- **`diag_store` has ZERO correlated writers**, and
`DiagnosticStore.by_uri` is keyed by **URI alone** with no server
component (`src/diag.rs:198`) — so the store most needing protection
is the one a `pending_routes` purge cannot help. Rev 8 incorrectly
inferred that the tombstone must share the store's URI-only key;
rev 9 gates in `LspManager`, where `sid` is still available.
- **A first pass of rev 8 checked only `handle_notification` and
concluded `publishDiagnostics` was the only uncorrelated writer.
Wrong lens boundary** — `pub fn mark_document_stale`
(`src/lsp.rs:3108`) takes **no `LspServerId`** and creates URI keys
across three stores for every server. Rev 9 changes that surface and
its private Lua binding to `(sid, uri)` and pins it independently.
- **Both new hooks are `all-must-succeed`, not short-circuit** — a gap
the review did not raise and the sweep found. `src/hook.rs` defines
three `HookKind`s; registered short-circuit, one `resource.renamed`
subscriber returning falsey would silently stop every later
subscriber reconciling.
- **CROSS-LANE SPLIT WITH PR #186 — settled, recorded verbatim so the
two lanes cannot diverge again:**
> #186 owns the urgent **pre-filesystem refusal** for synchronous
> `apply_resource_op`. #171 later owns **full post-delete lifecycle
> reconciliation**, including the **async race where a buffer becomes
> modified after dired dispatch**. #171's revision 7 adopts the
> refusal and stops saying LSP intentionally deletes modified files.
**PR #186 (`resource-op-delete-guard`) owns the pre-filesystem
refusal.** Do not implement a competing dirty check from #171. Rev 7
adopts #186's Q#RD1 (refuse, do not prompt, do not save, do not back
up) and its Q#RD6 shared query (scan every path-bound buffer, not
first-match; normalize once; component-aware `Path::starts_with`);
whichever lane lands first owns that query and the other adopts it.
The two fit deliberately: #186's Q#RD5 keeps reconciliation
**exact-path** so it does not promote the dangling-window and
last-buffer defects tree-wide, and #171's Q#DR27 is the lane that
makes widening safe.
- **What rev 7 removed.** Rev 6 said an LSP-authored delete removes the
file and orphans the modified buffer as accepted residue. That is
withdrawn — **20 matched lines across 13 passages** were censused and
reclassified: 4 ownership claims reassigned to #186, 4 policy claims
deleted, 3 ground-truth statements kept but attributed, 2 async-race
claims kept and narrowed.
- **What rev 7 kept, and it is now one path not two.** The async race —
a buffer modified between dired's pre-dispatch check and
`remove_blocking`'s syscall — survives #186's refusal, because **dired
never goes through `apply_resource_op`**: it calls `pmacs.fs.remove`,
which dispatches a worker no in-applier guard can see. Rev 6 carried
this as "two paths, LSP and dired"; after #186 the LSP path has no
residue at all.
- **A gap neither lane closes, named in rev 7 §11:** `pmacs.fs.remove`
is public Lua API with **no dirty check of its own**. After both lanes
land the guards sit one layer above it on each side — at
`apply_resource_op` (#186) and in dired's policy layer (#171) — so a
third caller inherits neither. Latent, not live: it has zero
production callers today.
- **LSP failure-handling, verified against the 3.18 spec** because #186
got it wrong and this lane was checked for the same error. **This lane
never made the claim** — it asserts nothing about `WorkspaceEdit`
recovery. For the record: only `textOnlyTransactional` degrades to
abort for resource operations; `transactional` covers them; and the
specification states **no default** for a client that does not
advertise `failureHandling` (pmacs advertises none). So "Abort applies
by omission" does not follow.
- **Round 5's single theme, worth carrying because it will recur:** rev
5 changed the slice split and the ownership of a decision, and the
prose did not follow. Four of the six findings were that same defect
in different places — stale slice labels, an acceptance allocation
that contradicted the code split, and a PR body still describing
revision *1*'s two-slice plan. **When a cut changes, sweep for the
label, do not patch the instance.** Rev 6's own sweep found four more
the review had not cited, one of which was wrong in both halves
(`x`'s report tagged 2a, `remove_dir_all` tagged 2b).
- **Rev 4 was scouted at `c8ec8f3`, which is dired Stage 1's own merge
commit (#165)** — so dired Stage 1 and find-file (#162) were always
its base, not new arrivals. Eighteen PRs landed on top before the
re-scout. Rev 5 is that re-scout, against `6bee09d` and then
integrated forward.
- **Seven of rev 4's own claims about pmacs were wrong**, which is the
lane's most transferable lesson: a framing can verify all its external
facts and still be wrong about the codebase it is for. The load-bearing
one: §5 named `drain_external_cancelled` but cited `lsp.rs:1596`.
**Those are two different functions 35 lines apart**
`drain_external_cancelled` (`:1561`) is the unconditional
server-scoped drain and is the right precedent;
`drain_cancelled_externals` (`:1596`) is a per-tick token/timeout
sweep. A rename flips no token, so following the line number yields a
`forget_uri` whose drain half is a silent no-op and the awaiting
coroutine hangs forever — exactly the failure that step exists to
prevent.
- **The rename path-owner census is SIX, not five.** The sixth is
`lean.lua`'s `M.file_progress`, a URI-keyed Lua module table living in
no Rust store, so `forget_uri` structurally cannot reach it. That is
the first case outside dired proving the **hook**, not the Rust
method, is the mechanism that scales. The other five: buffer path,
buffer name, the URI-keyed LSP stores plus `DiagnosticView` (URI set
once at construction), dired's pathless handles, and a captured Lua
local no transaction can reach.
- **The journey ratchet is split across two files**, and both are gates:
`tests/journey_acceptance.rs` (24 tests, "stages add rows, none
removes them", seven rows asserting on dired) and
`gpu_invocation_acceptance.rs`, where #183 actually put the GPU row.
A scout that checks only the first will think #183 added nothing.
- **`open_directory` now commits under `pmacs.window.commit_to`, whose
scope REFUSES an `await`** (#182). That directly constrains the
serialize-and-await batch contract the framing proposes.
- **The typed-edit chain does not reach dired** — verified rather than
assumed. Its lessons bind the framing's two *new* hooks instead.
- **Scope MOVED OUT of this lane, and rev 6 records the handoff.**
Rev 5 added Q#DR25 to adopt `set_generated_contents` at the head of
2b, because dired's listing is a generated buffer whose paint bypasses
an intercept over a still writable rope. That turned out to be a
**class bug, not dired's** — the same idiom is in listview, compile
and search/grep, and **no Lua caller anywhere sets `read_only`**
because there is no Lua `set_read_only` to call — so it is owned by
the generated-buffer immutability lane. **Done: rev 6 withdraws
Q#DR25**, and §3.1 hands that lane what this one's re-scout found,
including the trap that
`dired_buffer_is_read_only_and_round_trips_input` passes **either
way** and is therefore not coverage of the adoption. Do not implement
Q#DR25 from this document.
- **Two substrate facts rev 6 added that outlive this lane.** (1) Buffer
removal is **two phases** and **no existing Rust path composes them**:
`EditorCore::kill_buffer` (`src/editor_core.rs:4590`) does windows and
registry, `after_buffer_removed` (`src/lua_bindings/mod.rs:1602`) does
keymaps/config/folds/`on_removed`, and only the Lua binding
`pmacs.buffer.kill` (`mod.rs:5476-5491`) runs both — while
`apply_resource_op`'s delete uses `remove_buffer_and_fire`, which
skips phase 1, **so a window displaying the deleted buffer keeps a
removed id**. That is a third defect on that arm beside the missing
dirty check and the first-match lookup. (2) The drain outcome must
carry deletions as well as renames in one `Vec<ResourceOp>` so every
kind is expressible, but its sequence is **bus-arrival order, not
filesystem execution order**; Q#DR29 requires callers with
interdependent mutations to serialize.
- Intended serial implementation once approved: **2a** rename/delete
reconciliation substrate with no dired UI, **2b** marks and
operations, **2c** mkdir/copy/recursive-delete primitives, then Stage
3 wdired. The re-scout re-examined this cut and it holds unchanged.
- **Ownership warning:** dired 2a overlaps `src/editor_core.rs`,
`builtin/runtime/lsp.lua`, and the URI-keyed LSP state with other
coherence work. Do not run it concurrently with Journey Stage 1b
without assigning those files to one lane first.
- **Two live bugs on `main` this lane confirmed but does NOT fix**, both
now owned elsewhere: `apply_resource_op`'s delete arm destroys unsaved
work (no dirty check anywhere on the path), and `View` still lacks
`rename_resource`.
## 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 — STALE, AND ITS DISPOSITION IS UNDECIDED
> **Measured 2026-07-28, not inferred:** `githubsucks/handoff-2026-07-20`
> is at `c11d7e7`, **1 commit ahead of `main` and 320 behind**. Its
> whole diff against `main` is four documentation files
> (`docs/active-work.md`, `docs/agent-handoff.md`,
> `docs/roadmap-2026-07.md`, `docs/vterm-framing.md`), every one of
> which has been rewritten repeatedly since by the landed-doc PRs
> #156/#168/#169/#172/#180. Rule 4 removes a lane on merge *or
> abandonment*, and this one looks abandoned in substance — but "looks
> abandoned" is not the same as a decision, and no PR was ever opened
> for it. **This snapshot deliberately annotates rather than deletes:
> whoever confirms the branch carries nothing unique removes the
> section.** The bullets below are its original claims, preserved as
> written and now unverified.
- 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
- **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: **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; 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.