diff --git a/docs/active-work.md b/docs/active-work.md index 6663937..4ba8b9f 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -1,10 +1,22 @@ # Active work — cross-machine resume ledger -**Snapshot: 2026-07-30.** This file records volatile work that has not +**Snapshot: 2026-08-01.** This file records volatile work that has not landed on `main`. Read it after `docs/agent-handoff.md`. Remove completed entries when their PR merges; do not let this become a second permanent backlog. +**This snapshot is an absorption pass, taken with ZERO open PRs** — +the one window in which a ledger refresh has nothing to re-conflict +with, and taken deliberately before a machine move. Nine PRs landed +since the previous anchor (#199–#207). Two arcs completed and their +lanes are **removed**, their durable facts now in +`docs/agent-handoff.md` §1: **Journey Stage 1** (1a plus the whole 1b +split) and **test ambient-root isolation**. Discovery and reap-ledger +merged a stage each and keep lanes rewritten to what remains. +`docs/agent-handoff.md` §1a now carries the whole board — every arc, +open lane, deferred item and standing hazard in one view. + +*(Earlier note, retained because its reasoning still governs:)* **This snapshot is an absorption pass.** Eight PRs merged on 2026-07-29 and 2026-07-30 (#188, #190, #191, #194, #195, #196, #197, #198) and the ledger had drifted to 1,854 lines carrying six lanes whose work was @@ -62,19 +74,17 @@ lesson, §1 for the two framings). 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` @ `fbcf235` (the reap-ledger diagnostic #202, atop - the test ambient-root isolation framing #201, the reap-ledger framing - #200, the ledger absorption #199, the M5.5 protocol-version pin #198, - the docs-only coherence listview correction #189, 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). + `githubsucks/main` @ `cfc1710` (discovery Stage 1 #207, atop the + ambient-root isolation implementation #206, Journey Stage 1b-3 #205, + 1b-2 #204 and 1b-1 #203, the reap-ledger diagnostic #202, the + isolation framing #201, the process-signal diagnostic #200, the ledger + absorption #199, and the previously recorded landed work). **Protocol schema support is `v6..=v21`; the production server-first `Hello` still advertises v20** — two different facts, and #184 landed only the first. The - previous snapshot named `7586905`, and **the + previous snapshot named `fbcf235`, and **the recovery floor advances with it**: the check below now requires - `fbcf235` or newer, so a tree at `7586905` no longer passes. That is + `cfc1710` or newer, so a tree at `fbcf235` no longer passes. That is deliberate — the floor moves with the base, because a check that accepts an older commit than the declared base passes on a tree the rest of this file does not describe. @@ -113,13 +123,22 @@ git worktree list git status --short --branch ``` -The `git log` command must expose `fbcf235` — the base named above — or a +The `git log` command must expose `cfc1710` — the base named above — or a newer intentional main. Keep this threshold and the canonical-base line in step: a recovery check that accepts an older commit than the base it declares canonical will pass on a tree the rest of this file does not describe. If it does not, stop and repair the remote/fetch configuration. +**This path was exercised, not asserted, at this snapshot** — ahead of a +machine move. From an empty directory: `git clone` the canonical URL, +add the `githubsucks` alias, `git fetch githubsucks --prune`, confirm +`cfc1710` is an ancestor of `githubsucks/main`, and recover a lane with +the three-argument `git worktree add -b githubsucks/` +form. All four steps ran clean. **The two-argument form still does not +work** for a remote-only branch (`fatal: invalid reference`), which is +why every lane below spells out the `-b` form. + ## 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 @@ -255,230 +274,50 @@ If it does not, stop and repair the remote/fetch configuration. 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) — 1a, 1b-1, 1b-2 MERGED; 1b-3 PR #205 OPEN +## Discovery lane (P4) — STAGE 1 MERGED (#207); STAGE 2 IS NEXT **Rewritten, not removed.** Rule 4 removes a lane when its ARC is done; -the journey arc is not — 1b-3 is the last stage of the 1b split and is -still open. Stages 1a (#182/#183), 1b-1 (#203) and **1b-2 (#204)** are -all on `main` and their durable facts are in `docs/agent-handoff.md` §1, -which is rule 4's precondition satisfied rather than deferred — so their -per-stage blocks are gone from here rather than left to rot as -"PR OPEN". +this arc is not — Stage 1 built the command surface and everything that +needs a Rust change is still ahead. Stage 1's durable facts are in +`docs/agent-handoff.md` §1. -**#203's merge obligation is DISCHARGED** on this branch: `COHERENCE.md` -§2's step-9 row reads **Works**, §2's keybinding-inversion paragraph -records all three examples answered (the quote itself deliberately -unchanged), §20 Priority 1 and its arc list say "landed", and the -handoff bullet says LANDED. **#204's is discharged too**: §2's step-6 -row names it as landed and stays **Partial** for a reason that landing -did not touch — a server that starts and then *crashes* is still -unsurfaced. Both rode this branch rather than standalone docs PRs, -which would have re-conflicted on every merge. - -### Stage 1b-3 — IMPLEMENTED, PR #205 open - -- **Branch `journey-stage1b3-welcome`**, worktree `../pmacs-journey-1b3`, - based on `githubsucks/main` @ `1f290d5`, **integrated with `main` @ - `5376af1`** (#204). **Implemented; PR #205 open.** - `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. -- **Integrated with `main` @ `5376af1`** (#204). The journey suite's - conflict was additive on both sides — step 4 (this lane) and step 6 - (#204) — and both are kept: **44 pins, covering steps 2, 3, 4, 5, 6 - and 9**. -- **§18 and the scorecard move Missing → Partial ON THIS PR**, per §25; - §2's step-4 row stays Partial because `C-h` and the tutorial remain. - No deferred flip is owed at merge — unlike 1b-1's. -- **Bites, all directed.** Deleting the production `run()` wiring fails - the two greeting pins — **the mutation revision 2's design would have - survived entirely**. Removing `mark_clean` fails the editable/clean - pin; the `had_file` guard fails the directory pin (not the file pin, - because a file buffer is active by then while the dired listing is - async); the emptiness guard fails the existing-content pin; the - active-buffer requirement fails the backgrounded pin; and advertising - an unbound key fails the binding pin. - - ```sh - git fetch githubsucks - git worktree add ../pmacs-journey-1b3 \ - -b journey-stage1b3-welcome \ - githubsucks/journey-stage1b3-welcome - ``` - -## Discovery Stage 1 (P4) — IMPLEMENTED, PR OPEN - -- **Branch `discovery-stage1-commands`**, worktree `../pmacs-p4-discovery`, - based on `githubsucks/main` @ `54a092e`. **Implemented; PR open.** - `docs/discovery-stage1-command-family-framing.md` revision 6, - three review rounds closed (round 1: two blocking, two major; round 2: - two blocking, two major; round 3: two factual corrections; all - accepted), Q#D2 / Q#D3 decided by the user, and the final review's - acceptance corrections applied. -- **What it is.** `COHERENCE.md` §20 Priority 4 — "almost pure wiring, - the best payoff-per-effort in this document". **Eleven commands under - one `help.*` prefix**: nine new (describe-key/mode/hook/buffer, - where-is, list-commands, list-keybindings, list-settings, apropos) - plus `editor.describe-command` / `editor.describe-setting` renamed, - with the old names retained as **forwarders** so nothing documented - breaks. Typing `help` at M-x surfaces the whole family, which is the - discoverability win the arc exists for (Q#D2). -- **`apropos` matches by SUBSTRING, not fuzzy** (Q#D3). `fuzzy_score` - is subsequence-based and descriptions are long sentences, so fuzzy - would match nearly every command. Pinned by a - `test.apropos-subsequence-fixture` whose `qzjx` letters occur as `q z - j x`, only after asserting no registered name or description contains - `qzjx` as a substring; it must find nothing, whereas fuzzy finds the - fixture. -- **It adds no Rust.** `pmacs.describe.*`, `pmacs.keymap.list()`, - `pmacs.command.list()` and `pmacs.config.list()` already return - everything needed, and `parse_completion_source` accepts a **Lua - `Function`** (`CompletionSource::Custom`), so even the prompts need no - new Rust. -- **Completion is ASSISTANCE, not validation.** - `resolve_accepted_value` returns the **literal typed text** whenever - no candidate is selected, so a non-matching typo still reaches - `on_accept` and the existing error path — and a fuzzy near-miss can - silently describe a *different* setting, a new failure mode. - Closed-set acceptance ("refuse a non-candidate") is Rust work and is - deferred. The custom source needs a **mapper**: `config.list()` - yields descriptor *tables* while `Custom` consumes a sequence of - strings. **It does not control display order** — - `recompute_candidates` runs `filter_and_sort` (fuzzy score, lexical - tiebreak); sorting the pool matters only because `.take( - CANDIDATE_LIMIT)` runs *before* the sort, so pool order decides which - candidates survive truncation. -- **`invoke_interactive` is NOT the M-x path** — the error #205 - corrected, repeated one PR later. The path is dispatch `M-x` → - `editor.execute-command` → assert the selected candidate **before** - RET (`accept()` does `session.take()`) → accept → `invoke_interactive`. - Six of the eleven canonical commands (`help.describe-command`, - `help.describe-setting`, `help.describe-key`, `help.describe-hook`, - `help.where-is`, and `help.apropos`) open a **second** prompt the pins - must drive too; a pin that stops after the first RET has tested the - palette. -- **One owner for `*help*` writes — NOT a one-site migration.** - `src/help.rs` has semantic renderers for command/key/buffer/mode/ - hook/view and **none for settings, lists or apropos**, and - `_show_help` takes already-flattened text, so a later migration still - changes each command's subject-specific logic. What the funnel buys is - the shared policy in one place: reuse-by-name, wholesale - delete+insert, the `q` binding, and the foreign-`*help*` hazard. - **`*help*` is ordinary editable content** — no read-only intercept, no - generated-content invariant. And read-only would **not** fix the - foreign-buffer hazard either: a user's own `*help*` carries no - intercept of ours, so the renderer still finds it by name and clears - it. The missing guarantee is **ownership identity**, which `listview` - and dired both carry and this mechanism does not. Each command's rendering - is a named per-subject function so the future Rust work is enumerated - per-subject (three new renderers) rather than discovered per-call-site. -- **Deliberately deferred, each with a reason:** richer M-x rows - (`MinibufferPrompt.candidates` is `Vec` — protocol change); - `Command` gaining title/category/flags (~147 definition sites); - predicate evaluation (**read only at `src/help.rs:76` and one test** — - a behaviour change); help-layer unification; closed-set acceptance; - and the **help prefix key**, which this stage does not touch because - #205 recorded why `C-h` is not free. -- **Implementation notes.** `runtime/help.lua` is new and owns the - family plus the `help` index, which **moved out of `welcome.lua`** so - the greeting file keeps only the greeting; it loads after welcome.lua - so the index can read `pmacs.welcome.entries`. -- **The seam-counting pin caught a real bypass immediately.** The two - renamed commands were still calling the file-local `show_help_text`, - so the funnel the framing promised was fiction for exactly the two - commands that predate the seam. They now call - `pmacs.editor._show_help`, with a comment saying why the in-scope - local is deliberately not used. -- **Bites, all directed** — six mutations, each failing exactly one pin: - fuzzy apropos, name-only apropos, static where-is, a dropped - forwarder, an unindexed family command, and one command writing - `*help*` itself (seam count 10 vs 11). -- **§5 moves substrate-without-surface → Partial ON THIS PR** per §25, - with the remaining gaps named in the row: packages and workers have no - surface, `Command` still lacks title/category/flags, M-x rows are bare - names, and the Rust help layer is still orphaned. -- Recovery: - - ```sh - git fetch githubsucks - git worktree add ../pmacs-p4-discovery \ - -b discovery-stage1-commands \ - githubsucks/discovery-stage1-commands - ``` +- **Landed:** eleven `help.*` commands over the existing registries, + indexed by `M-x help`, with `editor.describe-command` / + `editor.describe-setting` kept as forwarders. No Rust, no protocol + change. §5 moved substrate-without-surface → **Partial**. +- **Stage 2 candidates, in rough dependency order:** + 1. **Richer M-x rows** — a **protocol change**: + `MinibufferPrompt.candidates` is `Vec`, while + `CompletionPopupRow` already carries `kind`/`detail`, so the wire + pattern is solved and the bump is the work. + 2. **`Command` gains title / category / aliases / flags / + arg-schema** — a Rust type change across ~147 definition sites. + MCP currently works around the missing schema by stuffing rendered + JSON into the description string. + 3. **Predicate evaluation.** `Command.predicate` is read at + `src/help.rs:76` and one test, and **evaluated nowhere**. Starting + to evaluate it makes commands stop being invocable, so it needs its + own decision about what "unavailable" means at each call site — + M-x, dispatch, menu. `discovery_acceptance`'s `d9` pins today's + behaviour with a *raising* predicate, so that stage must change the + pin knowingly. + 4. **Help-layer unification.** `src/help.rs` is still orphaned. Stage 1 + funnels every command through `pmacs.editor._show_help` and renders + via named per-subject functions, so the work is enumerated: + replace the four subjects `src/help.rs` covers (key, mode, hook, + buffer) and **write three new Rust renderers** for settings, lists + and apropos. + 5. **The help prefix.** Deliberately untouched by Stage 1 — the + decision is one for the whole family, and `C-h` is **not** free + (non-kitty terminals cannot disambiguate Ctrl+Backspace from + Ctrl+H; both produce byte 0x08). `F1` / `C-c ?` / a rebind are the + candidates. +- **Two Stage-1 facts a Stage-2 author needs.** Completion is + **assistance, not validation** — `resolve_accepted_value` returns the + literal typed text when no candidate is selected, so closed-set + acceptance is unbuilt Rust work. And **`invoke_interactive` is not the + M-x path**; the forwarders learned that the hard way in CI, since + `pmacs.command.invoke` is a real caller of the old names. ## Generated-buffer immutability lane (Arc: workbench primitives) — STAGE 1 MERGED; STAGE 2 IS NEXT @@ -514,72 +353,6 @@ compatible. - **DAP waits for Stage 2, not Stage 1** — that dependency is now satisfied. -## Test ambient-root isolation — IMPLEMENTATION OPEN - -- **Framing MERGED (#201)**; - `docs/test-ambient-config-isolation-framing.md` **revision 5** - (revision 4 approved after three review rounds — eight blocking, six - major, all accepted; revision 5 rides the implementation PR and records - findings, not a new design round). -- **PR #206 OPEN**, one review round closed. **§7's branch plan was - consciously exceeded**: the whole-corpus migration rides this PR rather - than a follow-up lane, because splitting would either leave 65 suites - writing the real data root or ship acceptance 12's ratchet with a - ~65-file allowlist that does not ratchet. Recorded in framing revision - 5; accepted in review. -- **Implementation branch `test-ambient-isolation-impl`**, worktree - `../pmacs-test-isolation-impl`, based on `githubsucks/main` @ - `54a092e`. The framing-only worktree `../pmacs-test-isolation` - (branch `test-ambient-config-isolation`) is spent — its doc is on - `main`. -- **What landed on the branch.** `pmacs::bootstrap::BootstrapRoots` - (config/data/state/cache, `ambient()` for production), reachable from - `EditorState::new_with_roots` **and** `open_with_roots` and consulted - again by `install_state_dirs`; all 342 in-process construction sites - in 65 files migrated; `journey_acceptance` keeps the ambient `open` - and re-execs itself per test with controlled roots instead; the shared - daemon and PTY spawners now set all five storage variables; - `tests/ambient_isolation_acceptance.rs` carries the hostile-environment - proof and the adoption ratchet. -- **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 - ` fails with `fatal: invalid reference`, because - after a bare fetch no local branch exists): - - ```sh - git fetch githubsucks - git worktree add ../pmacs-test-isolation-impl \ - -b test-ambient-isolation-impl \ - githubsucks/test-ambient-isolation-impl - ``` - ## Reap-ledger silent failures — MERGED (#202); kept for its parked follow-ons - **Branch `reap-ledger-silent-failures`**, worktree diff --git a/docs/agent-handoff.md b/docs/agent-handoff.md index 48a6605..e958bc7 100644 --- a/docs/agent-handoff.md +++ b/docs/agent-handoff.md @@ -58,16 +58,114 @@ reads it the way you just did. For volatile branches, checkpoints, verification, and recovery commands, read `docs/active-work.md` immediately after this file. -## 1. Where the project stands (2026-07-30) +## 1. Where the project stands (2026-08-01) -- **`main` @ `4cd4a7b`.** Eight PRs landed since the previous anchor, in - this order: the generated-buffer immutability **framing** #188, the - resource-op delete-guard **implementation** #190, silent-skip arming - #192/#193/#194, CI timeouts and concurrency #195, the process teardown - stdin-deadlock fix #197, dired Stage 2a #196, generated-buffer - immutability **Stage 1** #191, and bottom-panel **Stage 2B-3** #198. - Each has its own bullet below; this line is the head-of-`main` anchor - and nothing else. +- **`main` @ `cfc1710`.** Nine PRs landed since the previous anchor, in + this order: the ledger absorption #199, the process-signal diagnostic + #200, the test ambient-root isolation **framing** #201, the + reap-ledger diagnostic #202, Journey Stage **1b-1** #203, **1b-2** + #204 and **1b-3** #205, the ambient-root isolation **implementation** + #206, and discovery Stage 1 #207. Each has its own bullet below; this + line is the head-of-`main` anchor and nothing else. +- **Two arcs completed in that run, and their lanes are gone from + `docs/active-work.md` accordingly** (rule 4 removes a lane once its + arc is done *and* its facts are here): **Journey Stage 1** — 1a plus + the whole 1b split — and **test ambient-root isolation**. Discovery + and reap-ledger merged a stage each and keep rewritten lanes. +### 1a. Outstanding work — the whole board, 2026-08-01 + +**Read this before picking anything up.** It is the only place the +remaining work is enumerated in one view; the per-arc bullets below give +the detail. Nothing here is in flight: **zero PRs are open** at this +anchor, so every item is startable. + +#### Arc state, against `COHERENCE.md` §20's priority order + +| P | Arc | State | What is next | +|---|---|---|---| +| 1 | **Journey** | **Stage 1 COMPLETE** (1a #182/#183; 1b-1 #203, 1b-2 #204, 1b-3 #205) | Journey runs to step 10. The thin end is now steps **1** (install — that is P8), **11** (background work: visible but no ownership model, §9) and **12** (session restore: desktop-save is opt-in *and* a documented no-op under a daemon) | +| 2 | Workspace + location | Missing; model gap | The long-lead arc. Start before a fifth subsystem grows its own root convention — four have already diverged (§7) | +| 3 | Extension ownership | Missing; prerequisite-shaped | **`pmacs.hook.remove` does not exist.** That one bug-sized gap blocks §13's disable/uninstall, §10's trust classes, and package-scoped cancellation | +| 4 | **Discovery** | **Stage 1 MERGED (#207)** | Stage 2 candidates, in rough dependency order: richer M-x rows (**protocol change** — `MinibufferPrompt.candidates` is `Vec`; `CompletionPopupRow` already proves the pattern), `Command` gaining title/category/aliases/flags/arg-schema (~147 definition sites), predicate evaluation, help-layer unification, and the help-prefix decision | +| 5 | Workbench convergence | Partial, best trajectory | Bottom panel done both frontends; **Stage 3 = flip the adopter default**. Then the tree primitive — build it *before* dired and the worker tree invent two | +| 6 | Config productization | Foundation only | Value provenance, then layering, then adoption migration (**table-valued settings are the hard prerequisite** — `ConfigValue` is four scalars) | +| 7 | Package lifecycle | Not started | Correctly sequenced after P3 | +| 8 | Distribution | Zero | Independent of everything, startable any time. **Every other priority's value is invisible until this one exists** | + +#### Open lanes (branch exists, work not finished) + +- **CRDT half of the corpus is dark in CI — still no lane, no owner.** + CI never enables `crdt`, so those tests are *not compiled*, not merely + skipped. **Re-measure before quoting a number**; the ledger's figure + moves with every merge. +- **Generated-buffer immutability** — Stage 1 merged (#191); Stage 2 + not started. Four writer mechanisms have still not adopted + `set_generated_contents`; key the inventory by *writer*, not buffer. +- **Bottom panel** — Stage 2 complete; Stage 3 is the adopter default + flip. +- **Folding** — Stages 1–2 merged; Stage 3 (GPU) next, with + mirror-clear-on-snapshot a named obligation. +- **Reap-ledger** — diagnostic merged (#202); every *disposition* change + is still parked (below). +- **Kill-ring browser + persistence** — parked by choice. + +#### Deferred work, by the framing that parked it + +Each was a deliberate decision with a stated reason; none is a to-do +someone forgot. + +- **From LSP guidance (#204):** build `pmacs.error` (**fifteen guarded + call sites reporting through a channel that does not exist**); + promote the spawn-failure record into Rust's status model so `*lsp*` + shows failures natively; surface `LspEventKind::Crashed` — a server + that *started then died* is unsurfaced and is a different message. +- **From the welcome (#205):** the help prefix — **`C-h` is not free**; + it deletes a word because non-kitty terminals cannot disambiguate + Ctrl+Backspace from Ctrl+H (both byte 0x08), so rebinding breaks + Ctrl+Backspace on every legacy terminal. Also: make `show_help_text` + adopt the generated-buffer write invariant; onboarding proper (§18's + ten-step sequence). +- **From discovery (#207):** everything in the P4 row above, plus + settings **value provenance** (§11) and **closed-set acceptance + semantics** — completion today is *assistance*, not validation: + `resolve_accepted_value` returns the literal typed text when no + candidate is selected, so a typo still reaches the handler. +- **From the reap ledger (#202):** Q#RL1's strict-`ESRCH` probe and + Q#RL2's retry policy — **neither can move alone**, because + `shutdown()`'s loop exits when the ledger empties, so a strict probe + without a loop change converts a silent early exit into a guaranteed + 2 s stall at every editor exit. Also parked: retargeting to the + measured pgid, `signal_target`'s read-then-kill of `tcgetpgrp` (the + "most likely real fix site"), and **why** a group-directed `kill` + returns `EPERM` against a live owned child — still unknown. +- **From ambient isolation (#206):** production still resolves ambient + roots by default (only *construction* gained a parameter); nothing + audits what previously wrote into `~/.local/share/pmacs`. + +#### Side quests and standing hazards + +- **`pmacs.error` is undefined in production.** Fifteen `if pmacs.error + then` guards make the silence look deliberate. Report through + `pmacs.editor.set_status` until the channel is built. +- **Flakes that are not your change.** Judge a red run against these + before bisecting: `process::tests::a_successful_signal_disposition_...` + (macOS-only, signal timing); `a33_headless_terminal_frame_paints_...` + (GPU under parallel load, "0 blue pixels"); `m6_8_supervisor_reaps_...` + (load-sensitive). **Rerun before concluding.** +- **`basedpyright` hangs forever** — always + `cargo test --test m4_acceptance -- --skip basedpyright`. +- **The crdt sweep needs `cargo build --workspace` first**, or twelve + `gpu_invocation_acceptance` tests fail on a missing `pmacs-gpu` + binary. +- **A green a37 means nothing on its own** — the vterm real-daemon + acceptance returns `ok` without running unless `pmacs-gpu` is built. + +- **Previous anchor, retained for provenance:** `4cd4a7b` (the + generated-buffer immutability framing #188, the resource-op + delete-guard implementation #190, silent-skip arming #192/#193/#194, + CI timeouts and concurrency #195, the process teardown stdin-deadlock + fix #197, dired Stage 2a #196, generated-buffer immutability Stage 1 + #191, and bottom-panel Stage 2B-3 #198). - **Previous anchor, retained for provenance:** `6c9e765` (the dired Stage 2 framing #171 and the resource-op delete guard framing #186 — both framing-only, no runtime code, no