Integrate landed Vterm Stage 2 before the approved modeline merge. Preserve the
active modeline lane in the volatile ledger and record the full integrated gate
results.
Parse bounded Emacs and Vim modelines, normalize common aliases, and give
explicit file metadata precedence over inferred language. Pin one fresh-load
language decision for syntax, LSP, pairing, comments, and initial major mode,
while preserving the LSP path guard and explicit mode overrides.
Cover supported forms, rejection boundaries, precedence, unknown modes,
shebang and modeline pinning, reopen behavior, and pathless buffers.
Match Vim's real direct and set-form tokenization, reject nonexistent colon
assignments, clarify suffix scanning and trust, extend aliases, and pin the
shebang-edit regression in acceptance.
Update the durable handoff and active-work ledger with the second-review
fix checkpoint and exact final gate evidence.
Co-authored-by: OpenAI Codex <codex@openai.com>
Advance the canonical base after the mode-system handoff merge and preserve
the draft modeline framing branch, checkpoint, scope, and recovery command.
Define bounded Emacs and Vim modeline parsing, alias normalization,
explicit precedence, one pinned language decision shared by syntax and LSP,
and the acceptance contract for load-time mode detection.
Move mode-system wiring from the active ledger into the durable handoff,
refresh the side-quest priorities, and preserve the macOS acceptance lessons
from the final CI review round.
Align the durable handoff with the approved escape-prefix contract, record the performance and lifecycle hardening, update the Stage 2 verification map, and publish exact final gate evidence in the active-work ledger.
Require the fixed C-c escape before editor-local terminal bindings, reject context-implicit Lua operations from document windows, and make controller replacement atomic per frontend. Borrow screen rows during view projection instead of deep-cloning retained history, preserve view anchors through zero-area layouts, and remove redundant detach paths.
Add focused child-input coverage for unescaped bound keys and C-c C-c, plus controller, zero-area, context-error, and clone-free projection assertions.
Co-authored-by: OpenAI Codex <codex@openai.com>
Give the daemon its normal five-second handshake window before switching
the mode-system acceptance client to short frame polling. Document reload
and session-persistence boundaries and correct stale describe-key guidance.
Resolve post-main integration drift in authenticated routing, terminal view projection, Lua installation, and inherited acceptance callers. Preserve the terminal statusline provider alongside the landed Themes provider and record the final Stage 2 gate evidence.
Co-authored-by: OpenAI Codex <codex@openai.com>
Store a detected major mode on each buffer and expose it through Lua.
Resolve mode-scoped bindings in dispatch, describe-key, and help links,
including exact encoded mode context after entering the help buffer.
Initialize modes once at buffer load, preserve explicit overrides and
clears across switches, and publish the mode through a per-window
statusline provider. Add daemon acceptance for the complete mode lifecycle.
Add cross-surface Lua, shared-view, clipboard, authenticated routing,
BEL, resize, and real-host PTY acceptance. Ensure terminal-local keymaps
run before raw child transport and document the criterion-to-test map.
Co-Authored-By: Claude <noreply@anthropic.com>
Resolve terminal commands and buffer switches against the authenticated invoking
frontend, publish copied selections to that frontend, and wire daemon rendering,
resize, focus, paste, bell, and detach lifecycle through exact view identities.
Co-Authored-By: Claude <noreply@anthropic.com>
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>
Install strict owned terminal specification parsing, fresh global state tables,
default-name uniquification, durable view/controller lifecycle, and the builtin
terminal command/statusline surface. Route daemon key and mouse input by the
authenticated source and add non-replaying per-frontend BEL baselines.
Co-Authored-By: Claude <noreply@anthropic.com>
Publish retained row metadata, alternate-screen identity, cursor, title, and
cells through the same synchronized-output generation gate. This gives each
Stage 2 view one coherent projection without borrowing or mirroring mutable
screen state.
Co-Authored-By: Claude <noreply@anthropic.com>
Add exact frontend/window/buffer view identities, logical-cell selections,
durable controller ownership, lifecycle cleanup, default-name uniquification,
and a nested authenticated interactive-command origin shared with Lua. These
contracts let the terminal core and TUI lanes proceed without inventing
parallel state.
Co-Authored-By: Claude <noreply@anthropic.com>
Carry the reviewed Revision 7 framing onto the Stage 2 implementation branch
as its first commit. The contract defines durable per-view state, authenticated
input authority, TUI composition, strict Lua operations, and acceptance 15-27.
Co-Authored-By: Claude <noreply@anthropic.com>
Add the portable Revision 7 branch, exact approved checkpoint, current state,
next implementation lane, and cross-machine recovery command. Preserve the
separate documentation-lane ownership boundary.
Co-Authored-By: Claude <noreply@anthropic.com>
Review round 1, findings 2-4 plus doc notes. Finding 1 landed in fd80bcb.
Finding 3 --- spec fields meaningless for the declared type are now
rejected. DEFINE_SPEC_FIELDS whitelists all nine keys for every type and
the kind parser only reads its own arm's fields, so
`{ type = "string", choices = {...} }` silently defined a string that
accepts anything (the author meant enum) and `min` on a boolean was
dropped. These are typo-shaped bugs the R50 whitelist structurally
cannot see: the key is spelled correctly, it is on the wrong type.
`check_fields_relevant_to_kind` closes it with a pointed error naming
the misplaced field, and a companion test pins that each field is still
accepted where it belongs, including `min`/`max` on number as well as
integer.
Finding 4 --- the after_buffer_removed purge had no end-to-end test.
Every existing test called ConfigRegistry::remove_buffer directly, so
deleting the three lines wired into mod.rs would have left the whole
suite green. The new acceptance test kills a buffer through
pmacs.buffer.remove (the real remove_buffer_and_fire route) and asserts
the locals are gone; bite-verified by removing the hunk and watching it
fail.
Finding 2 (the half with a natural buffer) --- editing.trim-on-save is
now resolved against the buffer being saved rather than the global
chain. Reading globally meant set_local was accepted, stored, and
reported by describe, then never consulted: a pin the user believes in
that does nothing, which is the shape F1 exists to prevent. Two tests,
one for the override and one for the global fallback the change could
have broken; the override test fails against the old global read.
Both new save tests initially passed VACUOUSLY and were rewritten:
pmacs.editor.save() is the raw save, while buffer.before-save fires
inside the buffer.save COMMAND (default.lua:224), and save() no-ops on
an unmodified buffer --- so the original form asserted on a file that
was never rewritten. They now insert content to dirty the buffer and go
through pmacs.command.invoke("buffer.save").
The other half of finding 2 --- a per-buffer autosave.interval-ms is
semantically meaningless yet still accepted --- is recorded as a named
deferral proposing a define-time `scope = "global"` flag, alongside
deferrals for bound-parse field naming and StartupOnly reset symmetry.
Also recorded: interval_ms(1e30) now raises instead of storing a
nonsense float, an improvement but a real divergence from "the wrapper's
shape stays exactly as it was".
Doc: the module header cited framing revision 2; the shipped doc is
revision 3, whose corrections are what the code implements.
Gates: fmt, clippy -D warnings, --lib (1691), --lib --features crdt
(1865), lua54 backend, config_registry_acceptance (16), editops (72),
autosave (29), PMACS_REQUIRE_GPU=1 pmacs-gpu (109), and the full
workspace sweep (2806 tests, exit 0). git diff --check clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review round 1, finding 1. `i64::MAX as f64` rounds UP to 2^63 =
9223372036854775808.0, one greater than `i64::MAX`. The guard used `>`,
so a value of exactly 2^63 passed validation and `v as i64` then
saturated it to 9223372036854775807 --- the registry silently stored a
different number than the caller wrote.
The bounds are asymmetric on purpose, and the fix must not be applied to
both ends: `i64::MIN as f64` IS exactly -2^63 and round-trips, so the
lower comparison stays `<`. Tightening it in sympathy would wrongly
reject a legitimate value.
Three tests: the boundary is rejected (fails against the `>` form,
bite-verified), `i64::MIN` is still accepted, and the largest
representable integer below the boundary (2^63 - 1024) still converts.
Also from review round 1, finding 2, which does NOT reproduce: the
concern was that because `set_local` never calls
`maybe_freeze_after_init`, a `set_local` as the first post-init
operation would defer the lazy freeze and let a later `StartupOnly`
write slip through. It cannot --- `set` and `reset` call
`maybe_freeze_after_init` as their first statement, ahead of the
definition lookup and the mutator, so the freeze always lands before the
check within the same call. No code change; the ordering is now pinned
by a test that drives exactly that sequence (post-init `set_local` on a
Live key, then a `StartupOnly` write) and asserts the write is still
refused and that the refused set is itself what triggered the freeze.
Gates: fmt, clippy -D warnings, --lib (1687), --lib --features crdt
(1861), lua54 backend, config_registry_acceptance (13), and the full
workspace sweep (2799 tests, exit 0). git diff --check clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A third registry beside CommandRegistry and HookRegistry, per
docs/config-registry-framing.md. Unblocks the per-buffer auto-pair
toggle, the first of the five backlog items the missing config surface
was gating.
Substrate (src/config_registry.rs):
* ConfigRegistry keyed by name with definition order preserved, R42
mandatory descriptions, R50 typo detection, duplicate rejection,
and SourceLocation provenance -- the command/hook vocabulary.
* Closed scalar kinds: boolean, integer, number, string, enum. Owned
Rust values; Lua tables, functions and userdata are never stored.
Integer exactness is checked by value, never math.type, so the
luajit and lua54 builds agree.
* Two scopes. get(name, buf) resolves buffer-local -> global ->
default; get(name) with no buffer resolves the global chain only
and never consults an ambient buffer. Buffer-locals live in a
registry-owned side table purged at after_buffer_removed, beside
the keymap purge already there.
* An override is ALWAYS stored, even when equal to the value it
shadows; only value_epoch and listener dispatch key on effective
change. Without this a buffer pinned to the current value stores
nothing and a later global set flips it -- the pin silently never
existed. equal_valued_local_override_is_still_stored_and_shields_buffer
fails against the naive reading.
Bindings (src/lua_bindings/config.rs):
* define/get/set/set_local/reset/is_set/describe/list/on_change.
Spec tables are read raw, so neither an unknown key nor a
metatable-provided value can smuggle a field in.
* Listeners commit inside the borrow, snapshot, drop the borrow, and
only then re-enter Lua -- verified by holding the borrow and
watching the test panic with "RefCell already borrowed". A raising
listener is logged without blocking later ones or rolling back, and
a depth bound turns an accidental cycle into a pointed error.
Listeners persist until explicitly disposed; there is no Gc path,
matching the rest of the codebase.
* StartupOnly freezes off the existing InitCompleteFlag at write
time, so this arc adds no editor.rs call at all.
Adopters, each defining its own key so SourceLocation names the owning
module: editing.auto-pair (pair.lua, read per-buffer against the typed
edit's SOURCE buffer), editing.trim-on-save (editops.lua),
autosave.interval-ms (autosave.lua). No public function is removed or
deprecated, and both migration wrappers keep their legacy coercion --
trim_on_save("yes") still enables, interval_ms(1500.7) still floors to
1500 -- coercing before handing the strict registry a conforming value.
M-x describe-setting renders into *help*, modeled on describe-command.
Framing revision 3 records four defects implementation found in the
document itself: acceptance 30 and 31 contradicted each other; the
planned builtin/runtime/config.lua had nothing to hold and would have
broken the source-location contract had it held the one helper it might
have; F5 asked define to police a call it cannot see, moved to
set_local; and list() ordering was underspecified.
No protocol change; SUPPORTED stays [6..18]. No wire surface. Zero
changes to src/editor.rs.
Gates: fmt, clippy -D warnings, --lib (1683), --lib --features crdt
(1857), the new config_registry_acceptance (13) plus auto_pair (45),
editops (72), autosave (29) and m9_6 (25), m4 --skip basedpyright
(114), PMACS_REQUIRE_GPU=1 pmacs-gpu (109), the lua54 backend build,
and the full workspace sweep (2795 tests, exit 0). git diff --check
clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cross-cutting substrate ranked first on the side-quest north star.
A third registry beside CommandRegistry and HookRegistry, with two
scopes (global and buffer-local), a closed scalar value vocabulary,
post-commit listeners, and no wire surface.
Ground truth established by scout at 7bc0c61, including three findings
that shaped the design:
* Tab width is five constants across two crates with two different
values -- four TAB_WIDTH = 8 in the daemon, 4 in the GPU minimap,
and no tab expansion at all on the GPU main text path. It is a
rendering-parity bug, not a config gap, so it is deferred to
stage 2 rather than made the proving adopter.
* The mode system is unwired: every editor KeymapStack::resolve
passes an empty active-modes slice. A mode scope would ship a knob
that silently never fires.
* load_user_config and set_init_complete both run inside
EditorState::new under one cfg(not(test)) block, so the
StartupOnly freeze point covers the daemon and local entry points
uniformly -- and never runs in --lib test builds.
Revision 2 records review round 1 (F1-F11). The load-bearing fix is
F1: an override is now always stored, with only value_epoch and
listener dispatch keyed on effective-value change. The prior
"equal-value set is a true no-op" reading meant an equal-valued
buffer-local override stored nothing, so a later global set would flip
the very buffer the user had pinned -- silently voiding the per-buffer
toggle this arc exists to deliver.
Also carried by revision 2: listener dispatch semantics pinned across
scopes, GC-collected listeners dropped (no MetaMethod::Gc precedent
exists), migration wrappers keep their legacy coercion, StartupOnly x
set_local rejected at define time, string-list dropped, describe's
`local` field renamed buffer_local (Lua keyword), and the direct-remove
leak corrected to permanent-but-non-aliasing since BufferIds are never
reused.
No implementation. Framing only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Synchronize the documentation continuity lane with #126, preserve the parked
work inventory, and record the landed terminal-core state and next sequential
TUI/GPU stages.
Advance canonical main to the #126 merge, retire the completed active lane, and
record the landed headless terminal core plus the remaining TUI/GPU stages.