pmacs/builtin/runtime
Levi Neuwirth 6c1631eaa8
fix(listview): tear the panel down when a `keys` bind is refused
PR #227 review, P2. `check_key_collisions` compared RAW TOKENS, but the
key parser canonicalizes first: `parse_key_code` (src/key.rs) uppercases
and folds `RET`/`RETURN`/`ENTER`, `SPC`/`SPACE`, `ESC`/`ESCAPE`,
`BS`/`BACKSPACE`, `DEL`/`DELETE` onto one `KeyCode`. So
`keys = { RETURN = ... }` compared unequal to every fixed token, passed
preflight, and was refused by `Keymap::bind` instead --- after the
buffer had been created, made read-only, marked round-trip and given the
fixed keymap, and before the panel was registered.

The old rollback unbound only the newly added keys, so the BUFFER
survived, owned by no `panels` record: unreachable, un-editable, and
findable by name --- which made the next `open` for that name
disambiguate itself to `<2>`. A rejected `keys` table silently renamed
the panel.

The comment directly above the check claimed the opposite guarantee ---
"Reject collisions BEFORE anything is created or bound, so a bad `keys`
table leaves no half-built panel behind" --- and that is corrected here
too, since a comment stating a belief is not code enforcing it.

APPROACH: the second of the two the review offered --- full teardown ---
rather than canonicalizing in preflight. Two reasons, both in the code:

* **A Lua canonicalizer would be a second copy of a Rust rule.** It
  would have to restate `parse_key_code`'s alias table, and the day the
  Rust table gains a name the Lua copy silently stops seeing that alias
  --- reintroducing exactly this bug for it. Deferring to `Keymap::bind`
  cannot go stale, because it IS the thing that decides.
* **There is no Lua-reachable canonicalization to use anyway.**
  `display_sequence` escapes only through `describe.key` and
  `keymap.list`, both of which require the sequence to be BOUND already.
  Reported rather than worked around, and no new binding added.

So the raw-token preflight stays, demoted to what it actually is: a
first pass that buys a better message ("that is the panel's own `g`")
and not safety. The construction block is now all-or-nothing, and the
teardown is `pmacs.buffer.kill`, which through `after_buffer_removed`
prunes the buffer's keymap scope, config locals and folds. `install_keys`
drops its own per-key rollback: two cleanup mechanisms for one failure
is how the weaker one came to be the only one that ran.

Witness: `g6_10c_an_alias_spelling_is_rejected_and_leaves_no_orphan_buffer`
walks every alias the parser folds onto a key the panel owns, asserting
after EACH that the buffer count is unchanged, then that a subsequent
legitimate open gets the plain name rather than `<2>`.

Bite: removing the teardown while keeping the raise fails it at the
buffer count (2 vs 1). Asserting only the error message would have
passed on the broken code --- it raised too; it just left wreckage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
2026-08-09 15:52:17 +02:00
..
async.lua feat(journey): open a directory, on one path 2026-07-26 16:39:59 -04:00
autosave.lua feat(config): typed configuration registry with buffer-local scope 2026-07-21 18:29:18 -04:00
comment.lua feat(lean4): editing surface + Stage 1 acceptance (Q#LN5, LN6, LN17) 2026-07-25 09:59:48 -04:00
compile.lua review round 1: the crdt suite Stage 3 missed, and two stale explanations 2026-08-04 12:08:28 +02:00
completion.lua fix(completion): address TUI-validation findings (LSP query gaps, scoping, prefix keys, window scope) 2026-07-07 16:54:14 -04:00
desktop.lua fix(persistence): reliable daemon gate, unarm, per-pane after-load 2026-07-08 22:27:24 -04:00
dired.lua refactor(panel): one shared rule for the adopter `display` vocabulary 2026-08-01 20:25:27 -04:00
editops.lua fix(config): reject wrongly-typed spec fields; make trim-on-save buffer-aware 2026-07-21 18:29:18 -04:00
fold.lua feat(fold): Arc 6 Stage 1 — instance fold engine (headless) 2026-07-23 12:14:00 -04:00
fs.lua wip(stage2a): the Lua half — hooks, LSP subscribers, applier origin 2026-07-29 18:09:03 -04:00
git.lua fix(git): reserve the refresh generation at the command, not on arrival 2026-08-09 15:52:17 +02:00
help.lua fix(help): forwarders must work programmatically, not only from M-x 2026-07-31 20:31:00 -04:00
indent.lua fix(edit): PR #109 round 1 — shared search invalidation, daemon anchor clear, bounded indent scan 2026-07-10 15:46:36 -04:00
killring.lua feat(edit): editing-conveniences pack (editops) 2026-07-12 15:33:17 +01:00
lean.lua fix(lsp): close round-six Lean fallback gaps 2026-07-25 22:32:48 -04:00
lean_abbrev.lua feat(lean4): the Unicode input method (Arc 8 Stage 4b) 2026-07-26 16:24:06 -04:00
lean_input.lua fix(lean4): count fan-outs where a claim cannot skip the count 2026-07-26 17:15:24 -04:00
linewrap.lua feat(view): horizontal scroll, text and decorations together 2026-08-07 22:43:17 +02:00
listview.lua fix(listview): tear the panel down when a `keys` bind is refused 2026-08-09 15:52:17 +02:00
lsp.lua feat(listview): the tree primitive — depth, collapse, and identity 2026-08-05 14:31:31 +02:00
mcp.lua M9 ship gate 2026-05-09 15:04:23 -04:00
pair.lua feat(typed-edit): the typed-edit consumer chain (Arc 8 Stage 4a) 2026-07-26 13:00:03 -04:00
recentf.lua fix(persistence): symlink confinement, real test-inertness, view_top restore 2026-07-08 18:21:46 -04:00
saveplace.lua fix(persistence): symlink confinement, real test-inertness, view_top restore 2026-07-08 18:21:46 -04:00
syntax.lua feat(lean4): editing surface + Stage 1 acceptance (Q#LN5, LN6, LN17) 2026-07-25 09:59:48 -04:00
terminal.lua fix(buffer): fan out generated writes, and clear the history that exists 2026-07-26 13:43:22 -04:00
typed_edit.lua fix(typed-edit): close round-8 review on the consumer chain 2026-07-26 13:39:33 -04:00
welcome.lua feat(help): the discovery command family — P4 Stage 1 2026-07-31 19:04:48 -04:00
window.lua feat(window): adopter placement opt-in and the Stage 1 acceptance suite 2026-07-24 14:10:45 -04:00
zoom.lua docs(zoom): the ties both round up, they do not oppose 2026-08-06 17:50:03 +02:00