docs: absorb the config registry (#127) into the handoff

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) <noreply@anthropic.com>
This commit is contained in:
Levi Neuwirth 2026-07-21 19:16:27 -04:00
parent 1aa19c3bb5
commit 30464bcffd
3 changed files with 142 additions and 21 deletions

View File

@ -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

View File

@ -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

View File

@ -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