From 5cc1a83583f2eae1f0e06b7e6d2fe81597a7516b Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Fri, 31 Jul 2026 20:31:00 -0400 Subject: [PATCH] fix(help): forwarders must work programmatically, not only from M-x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI caught this on all four test legs. The forwarder body called `pmacs.command.invoke_interactive`, which raises when the alias is reached through `pmacs.command.invoke` — and `tests/config_registry_acceptance.rs` does exactly that, three times. The acceptance pin passed throughout because it drives the M-x path, which is the path the framing spent three review rounds getting right. Being right about one entry point is not the same as covering the command, and a rename touches every caller of the old name regardless of how it is reached. Plain `invoke` is also the correct semantics rather than merely the working one: the interactive-command boundary is rotated once, by whatever entry point the user actually used, for the name they actually typed. Rotating again on the inner call would record a second boundary for a command the user never invoked. Adds `d8c`, which invokes both forwarders programmatically. Bitten by restoring `invoke_interactive`: the new pin fails alongside the three config-registry tests that found it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T --- builtin/runtime/help.lua | 12 +++++++++++- tests/discovery_acceptance.rs | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/builtin/runtime/help.lua b/builtin/runtime/help.lua index 5ac5096..0e31b56 100644 --- a/builtin/runtime/help.lua +++ b/builtin/runtime/help.lua @@ -380,11 +380,21 @@ pmacs.command.define { -- the bounded price of not breaking documented commands, and it carries -- a deprecation path a later stage can take. +-- `invoke`, NOT `invoke_interactive`. The forwarder must work however it +-- was itself reached, and `pmacs.command.invoke('editor.describe-setting')` +-- is a real caller (`tests/config_registry_acceptance.rs`) — CI caught +-- that, because the acceptance pin here only drove the M-x path. +-- +-- Plain `invoke` is also the correct semantics, not merely the working +-- one: the interactive-command boundary is rotated once, by whatever +-- entry point the user actually used, for the name the user actually +-- typed. Rotating again on the inner call would record a second +-- boundary for a command the user never invoked. local function forward(old_name, new_name) pmacs.command.define { name = old_name, description = string.format("Deprecated alias for `%s`.", new_name), - fn = function() pmacs.command.invoke_interactive(new_name) end, + fn = function() pmacs.command.invoke(new_name) end, } end diff --git a/tests/discovery_acceptance.rs b/tests/discovery_acceptance.rs index 3eea579..8385b13 100644 --- a/tests/discovery_acceptance.rs +++ b/tests/discovery_acceptance.rs @@ -434,6 +434,29 @@ fn d8_preservation_the_old_names_forward() { ); } +/// **P (acceptance 8, cont.)** — the forwarders also work when invoked +/// **programmatically**, not only from M-x. +/// +/// This pin exists because its absence shipped a bug: the forwarder body +/// used `invoke_interactive`, which raises when the alias is reached +/// through `pmacs.command.invoke` — a real caller in +/// `config_registry_acceptance`. The M-x pin above passed throughout, +/// because M-x is not the only way in. +#[test] +fn d8c_preservation_the_forwarders_work_programmatically() { + let s = editor(); + for old in ["editor.describe-command", "editor.describe-setting"] { + let ok: bool = eval( + &s, + &format!("return pcall(pmacs.command.invoke, {old:?}) and true or false"), + ); + assert!( + ok, + "{old} must be invocable programmatically, not only from the palette" + ); + } +} + /// **P (acceptance 8, cont.)** — the untouched list commands still work. #[test] fn d8b_preservation_list_buffers_and_workers_are_untouched() {