From 30464bcffdeb1d75104d1847d9cf4ff979c4ca48 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Tue, 21 Jul 2026 19:16:27 -0400 Subject: [PATCH] docs: absorb the config registry (#127) into the handoff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merges canonical main up to 2e37c04 and records the second of the two arcs that landed while this lane was open. Vterm Stage 1 (#126) was already recorded; this adds the config registry and reconciles every claim the two merges falsified. Handoff §1: main pointer moved to 2e37c04, and a config-registry entry covering the parts a future agent cannot re-derive from the code --- the always-store rule and why the "equal-value set is a no-op" reading silently voids a buffer-local pin; the two-scope model and the no-ambient-buffer contract on get(name); explicit-dispose-only listener lifetime and the absence of any MetaMethod::Gc; the InitCompleteFlag freeze that kept editor.rs untouched; and the strict-registry / lenient-wrapper split that preserves trim_on_save("yes") and interval_ms(1500.7). Handoff §5 gains two lessons. Tab width is a rendering-parity bug, not a config gap: five sites across two crates with two different values, and no tab expansion at all on the GPU main text path, so editor.tab-width is the obvious-looking first adopter and is not one. And "a test that never runs passes" --- pmacs.editor.save() is the raw save while buffer.before-save fires inside the buffer.save COMMAND, and save() no-ops on an unmodified buffer, which made two review-round tests vacuous until the buffer was dirtied and the command invoked. Handoff §6: the three config-registry-blocked deferrals are resolved (the per-buffer auto-pair toggle shipped as editing.auto-pair), replaced by the registry's own named deferrals --- persistence, list-settings, a settings completion source, table-valued settings, the unmigrated scalar setters, and a scope = "global" flag, since set_local is currently accepted for autosave.interval-ms where a per-buffer value is meaningless. active-work.md: base pointer and recovery assertion moved to 2e37c04; the Vterm Stage 2 lane is told to cut from current main rather than 643d1e1; a closed-since-last-snapshot section records the merged lane and the parallel-lane result --- two arcs in sibling worktrees with the shared files assigned one lane each in advance rebased with zero conflicts, which is worth repeating and states its precondition. side-quest-backlog.md: both original north-star items have now shipped, so the board is re-ranked to locals-query processing, mode-system wiring (promoted --- every editor resolve still passes &[], making it the largest remaining scoping gap), and tab-width parity. The config-registry entry is struck and tab width is split out of it, since listing tab width as a config consequence is what made it look like a cheap adopter. Documentation only: the diff against main touches no runtime code. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/active-work.md | 26 ++++++++-- docs/agent-handoff.md | 101 ++++++++++++++++++++++++++++++++++--- docs/side-quest-backlog.md | 36 +++++++++---- 3 files changed, 142 insertions(+), 21 deletions(-) diff --git a/docs/active-work.md b/docs/active-work.md index 5c3181f..c30e04e 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -14,7 +14,7 @@ backlog. 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` @ `643d1e1` (#126 merged; protocol v18). + `githubsucks/main` @ `2e37c04` (#127 merged; protocol v18). - 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 @@ -48,7 +48,7 @@ git worktree list git status --short --branch ``` -The first command must expose `643d1e1` or a newer intentional main. +The first command must expose `2e37c04` or a newer intentional main. If it does not, stop and repair the remote/fetch configuration. @@ -57,6 +57,9 @@ If it does not, stop and repair the remote/fetch configuration. - Portable branch: `githubsucks/vterm-framing` - Approved framing head: `fb4f8f0` - Base: canonical `main` @ `643d1e1` (Vterm Stage 1 / PR #126 merged). + `main` has since advanced to `2e37c04` (config registry #127, no + runtime overlap with vterm); cut the Stage 2 lane from current `main`, + not from `643d1e1`. - State: `docs/vterm-framing.md` Revision 7 is framing-only, reviewed, and approved for implementation. It closes the final `at_bottom`, terminal `C-c` binding-reachability, and context-implicit Lua failure-mode findings. @@ -103,7 +106,24 @@ git worktree add --track \ 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 PR #123 or either feature framing branch. + 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 + +- **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 diff --git a/docs/agent-handoff.md b/docs/agent-handoff.md index b6c9aee..bb2fb8d 100644 --- a/docs/agent-handoff.md +++ b/docs/agent-handoff.md @@ -1,8 +1,9 @@ # Agent handoff — cross-machine continuity -**Last updated: 2026-07-21, after Vterm Stage 1 terminal core (#126) -landed on `main` atop completed Themes Arc 4 (#120/#124/#125). Vterm -Stages 2 and 3 are not implemented.** +**Last updated: 2026-07-21, after the config registry (#127) and Vterm +Stage 1 terminal core (#126) both landed on `main`, atop completed +Themes Arc 4 (#120/#124/#125). Vterm Stages 2 and 3 are not +implemented.** This file is the bridge between development machines. If you are an agent reading this on a fresh clone: this document plus the `docs/*-framing.md` @@ -16,9 +17,50 @@ commands, read `docs/active-work.md` immediately after this file. ## 1. Where the project stands (2026-07-21) -- `main` @ `643d1e1` (Vterm Stage 1 #126), protocol **v18** +- `main` @ `2e37c04` (config registry #127), protocol **v18** (`SUPPORTED=[6..18]`; v16 = `ThemeFacts`, v17 = `FontFacts`, v18 = `StatuslineSegments`). +- **Config registry LANDED — #127** (`docs/config-registry-framing.md` + rev 3; merge `2e37c04`; two review rounds). `pmacs.config` is the + typed, introspectable options registry the backlog ranked first, and + it closes the "config-registry-blocked" deferrals below. It was built + as a PARALLEL LANE alongside vterm in a sibling worktree; the files + were assigned per-lane up front and the rebase had zero conflicts. + - **Third registry** beside `CommandRegistry`/`HookRegistry` + (`src/config_registry.rs`), same R42/R50/duplicate-rejection/ + `SourceLocation` vocabulary; Lua surface in + `src/lua_bindings/config.rs`. **No protocol change (still v18) and + ZERO changes to `src/editor.rs`.** + - **An override is ALWAYS stored**, even when equal to the value it + shadows; only `value_epoch` and listener dispatch key on effective + change. The "equal-value set is a no-op" reading silently voids a + buffer-local pin: nothing is stored, and a later global `set` flips + the very buffer the user pinned. + - **Two scopes: global and buffer-local.** `get(name, buf)` resolves + local → global → default; **`get(name)` resolves the GLOBAL CHAIN + ONLY** and never consults an ambient buffer. Per-language and + per-project are *patterns* (a hook calling `set_local`), not scopes + the registry knows about. Mode scope is impossible until the mode + system is wired — every editor `KeymapStack::resolve` passes `&[]`. + - Buffer-locals live in a registry side table purged at + `after_buffer_removed`, beside the keymap purge. + - Listeners: commit → snapshot → **drop the borrow** → re-enter Lua; + a raising listener is logged without blocking the rest or rolling + back; a depth bound turns a cycle into a pointed error. **Explicit + dispose only** — there is no `MetaMethod::Gc` anywhere in the + codebase, and GC timing differs between the two Lua backends. + - `StartupOnly` freezes off the existing `InitCompleteFlag` at write + time (which is why no `editor.rs` call was needed). In `--lib` + builds `set_init_complete` never runs, so a post-freeze test must + flip the flag explicitly or it passes vacuously. + - Adopters own their own `define`, so `SourceLocation` names the + owning module: `editing.auto-pair` (pair.lua, read against the + typed edit's SOURCE buffer), `editing.trim-on-save` (editops.lua, + read against the buffer being saved), `autosave.interval-ms` + (autosave.lua, re-read per tick). **The migration wrappers keep + their legacy coercion** — the registry is strict, the legacy setters + stay lenient (`trim_on_save("yes")`, `interval_ms(1500.7)`). + - `M-x describe-setting` renders into `*help*`. - **Syntax-highlight / language-detection side-quest (#114–#118) LANDED** — a one-shot arc built in sibling worktrees off main while the user's themes lane (`theme-faces`) ran concurrently in the shared @@ -207,6 +249,12 @@ commands, read `docs/active-work.md` immediately after this file. live GPU font preferences (#124), statusline providers (#125). - **Arc 5 terminal stage ACTIVE** — compile mode (#113) and Vterm terminal core (#126) landed; Vterm TUI is the next formal stage. + - **Config registry COMPLETE (#127)** — not a numbered arc; it was the + cross-cutting substrate ranked first on + `docs/side-quest-backlog.md`'s north star, and it unblocks the + editing/indent/comment items that were config-blocked. + - Remaining ranked arcs: 6 folding, 7 DAP, 8 GPU splits, plus the + `.ipynb` arc (its JSON-grammar prerequisite shipped in #123). ## 2. How we work (the part that must not drift) @@ -362,6 +410,28 @@ acceptance. in per-session baselines; and any daemon-side reset needs its frontend mirror audited in the same round (the GPU snapshot arm missed search/menu/status the first time). +- **Tab width is a rendering-parity bug, NOT a config gap** (scouted at + `7bc0c61` while framing #127; still true). There are FIVE tab-width + sites across TWO crates with TWO different values: `TAB_WIDTH = 8` in + `src/text_view.rs`, `src/highlight.rs`, `src/diag.rs` and + `src/completion.rs`, versus `advance_minimap_col` in + `pmacs-gpu/src/main.rs` expanding to **4** — and the GPU's main text + path expands tabs *not at all* (buffer bytes reach the frontend raw, + so a literal `\t` is shaped by the font). `editor.tab-width` is + therefore the obvious-looking first config adopter and is not one: + defining the setting cannot make the GPU honor it. Doing it properly + needs frontend tab expansion plus a wire-or-frontend-local decision. + Deferred from #127 on exactly these grounds; don't re-plan it as a + config task. +- **A test that never runs passes.** Two #127 review-round tests passed + vacuously at first: `pmacs.editor.save()` is the RAW save, while + `buffer.before-save` fires inside the `buffer.save` COMMAND + (`builtin/commands/default.lua`), and `save()` no-ops on an + unmodified buffer — so a fixture that opens a file and saves it + asserts on bytes nothing rewrote. Dirty the buffer with a real edit + and go through `pmacs.command.invoke("buffer.save")`. Caught only + because the *other* case failed and the cause was chased instead of + the assertion adjusted. ## 6. Named deferrals (the standing backlog, consolidated) @@ -372,8 +442,9 @@ mid-line comment spans, comment-dwim append-at-EOL, per-language comment padding. Pairing (framing "Deferred"): wrap-region on opener, pair-aware backspace, RET-inside-pair closer-on-own-line, in-string/in-comment inhibit (needs node-at-byte `pmacs.parse`), -undo amalgamation (pair = one step), balance-aware quotes, -per-buffer toggle (config-registry-blocked). Editops deferrals (full +undo amalgamation (pair = one step), balance-aware quotes +(the per-buffer toggle SHIPPED in #127 as `editing.auto-pair`). +Editops deferrals (full list in its framing): recenter (blocked on viewport facts — the GPU never consumes daemon `view_top`), Unicode case/word classes, region-spanning move/duplicate, locale collation for sort-lines, @@ -389,7 +460,20 @@ origin-pinned `buffer.after-edit` fan-out (a context-switching intercept changes what later callbacks — LSP, completion — observe). LSP/persistence: hidden-buffer LSP attach, daemon desktop-restore, the *warning* half of external-change detection (verify-visited-file- -modtime), config registry (no unified config surface yet). +modtime). +Config registry (SHIPPED #127; these are its own named deferrals): +persistence of settings and the `custom-file` split-brain question, +`M-x list-settings` as a listview panel, a settings completion source +for the minibuffer (`minibuffer.read`'s `source` is a fixed Rust-side +vocabulary), table-valued settings (so `pmacs.lsp.config`, +`pmacs.pair.sets`, `pmacs.comment.strings` and the `pmacs.parse.*` +write-through proxies stay raw Lua), migrating the remaining scalar +setters (`async_config` ×2, `killring.max`, the `enable` booleans) and +`pmacs.gpu.set_font`, pending-set staging for names defined after +`init.lua` runs, and a `scope = "global"` define flag — `set_local` is +currently accepted for `autosave.interval-ms`, where a per-buffer +value is meaningless. +**Tab width is NOT a config gap** — see §5. Highlight/detection (from the #114–#118 side-quest + injections #122): locals-query processing (run each grammar's LOCALS_QUERY so `#is?`/`#is-not? local` is honored instead of the current fail-closed @@ -415,7 +499,8 @@ popup/menu/dropdown bg + selected-row faces, `ui.background` / palette (+`ui.selection` for peer rects), `ui.inlay_hint` (needs the epoch treatment on its producer), wire alpha, `Indexed` palette unification, named-theme registry / light theme / persistence -(config-registry-blocked), grid-vs-wire `default_style` asymmetry, +(the registry exists now, #127; theme persistence still waits on +settings persistence), grid-vs-wire `default_style` asymmetry, mask widening (gutter bg, wash glyph recolor, statusline bg echo surface, chrome bold/italic/underline re-shaping). Housekeeping: F-016 `lua_bindings/mod.rs` split paused mid-way diff --git a/docs/side-quest-backlog.md b/docs/side-quest-backlog.md index e09994c..ac31f4f 100644 --- a/docs/side-quest-backlog.md +++ b/docs/side-quest-backlog.md @@ -114,10 +114,20 @@ The direct continuation of the #114–#118 grammar/detection stack. ## Cross-cutting substrate (unblocks whole clusters — high leverage) -- **Config/settings registry** (`pmacs.config` / defcustom) — blocks - language-aware indent, the five hardcoded tab-width sites, per-buffer - auto-pair toggle, per-language comment padding, per-project compile - commands. +- ~~**Config/settings registry**~~ — **SHIPPED as #127.** `pmacs.config` + exists with global + buffer-local scopes. Unblocked: the per-buffer + auto-pair toggle (shipped in the same PR as `editing.auto-pair`), + language-aware indent, per-language comment padding, and per-project + compile commands — the last three are now ordinary work, expressed as + a `buffer.after-load` hook calling `set_local`, not blocked work. +- **Tab-width rendering parity** — was listed above as a config + consequence; it is not. `TAB_WIDTH = 8` appears four times in the + daemon (`text_view`, `highlight`, `diag`, `completion`), the GPU + minimap's `advance_minimap_col` uses **4**, and the GPU main text path + expands tabs *not at all* — raw `\t` reaches glyphon and is shaped by + the font. Defining `editor.tab-width` cannot make the GPU honor it; + this needs frontend tab expansion plus a wire-or-frontend-local + decision. Deferred from #127 on those grounds. - **Real `read_only` buffer flag** on both edit paths — true immutability for panels / REPL / generated buffers. - **Mode system wiring** — dispatch passes an empty mode list (`&[]`); @@ -227,14 +237,20 @@ guides (visual, not color). ## North star (highest-leverage first) -**Multi-language injections shipped (#122)** — one of the two original -north-star items is done, so the highest-leverage board is now: +**Both original north-star items have now shipped** — multi-language +injections (#122) and the config registry (#127) — and JSON + YAML +(#123) merged too. The remaining board: -1. **Config registry** — frees ~5 editing/indent/comment features at once. -2. **Finish JSON + YAML PR #123** — the one remaining gate on the - Jupyter `.ipynb` path now that injections exist. -3. **Locals-query processing** — restores `.builtin` styling for +1. **Locals-query processing** — restores `.builtin` styling for non-shadowed builtins, the last rough edge of the highlight stack. +2. **Mode-system wiring** — every editor `KeymapStack::resolve` still + passes `&[]`, so mode-scoped keybindings and any mode-scoped setting + remain unreachable. Promoted here because #127 made it the largest + remaining scoping gap. +3. **Tab-width rendering parity** — five constants across two crates + with two different values, and no tab expansion at all on the GPU + main text path. Explicitly NOT a config-registry task; see the entry + under "Cross-cutting substrate". Beyond those, the cleanest one-shots in the highlight family are **modeline detection** and the HTML/CSS grammars that light up more