Ψ-W1-T3 review remediation: soften the double-fire claim to unspecified-by-SDK, mark the unload mirror [verify — DAW], fix stale comment, drop redundant assertions, dedupe hook-partitioning comments, drop what-comment; file the action_registry test-seam deferral.

This commit is contained in:
2026-08-01 20:13:13 -04:00
parent 72b870459c
commit d589b99705
6 changed files with 48 additions and 24 deletions
+1 -1
View File
@@ -181,7 +181,7 @@ Comments carry *why*, and context where non-obvious — never *what* the code al
2. `rec->Register("gaccel", &accel)` — puts the action in the Actions list. 2. `rec->Register("gaccel", &accel)` — puts the action in the Actions list.
3. `rec->Register("hookcommand", ...)` — receives every action fired; claim only your own id, return `false` otherwise. 3. `rec->Register("hookcommand", ...)` — receives every action fired; claim only your own id, return `false` otherwise.
4. On unload (`rec == nullptr`), mirror-unregister everything with the same strings prefixed by `'-'`. 4. On unload (`rec == nullptr`), mirror-unregister everything with the same strings prefixed by `'-'`.
- **Non-main sections use a different mechanism.** `gaccel_register_t` carries no section field — `command_id` + `gaccel` can only ever produce a Main-section action. To publish into another section (Media Explorer = 32063, MIDI editor = 32060, MIDI event list = 32061, MIDI inline = 32062), register a `custom_action_register_t{uniqueSectionId, idStr, name, extra}` under `"custom_action"`; it returns the command id, or **0 on failure** (e.g. a duplicate `idStr`) — which the caller must tolerate rather than half-register. `idStr` must be unique **across all sections**, so an action published into both Main and a non-main section needs a SECOND id string; the FOREVER-STABLE contract binds it identically from the moment it ships. `custom_action_register_t` has no `ACCEL`, so a non-main entry ships no default keybinding. Dispatch for these ids arrives through `"hookcommand2"` (`bool(KbdSectionInfo*, int command, int val, int val2, int relmode, HWND)`) — `"hookcommand"` runs for the main section only. The two hooks must partition the ids between them: claiming one command in both double-fires it. On unload, mirror with `"-custom_action"` and `"-hookcommand2"`. - **Non-main sections use a different mechanism.** `gaccel_register_t` carries no section field — `command_id` + `gaccel` can only ever produce a Main-section action. To publish into another section (Media Explorer = 32063, MIDI editor = 32060, MIDI event list = 32061, MIDI inline = 32062), register a `custom_action_register_t{uniqueSectionId, idStr, name, extra}` under `"custom_action"`; it returns the command id, or **0 on failure** (e.g. a duplicate `idStr`) — which the caller must tolerate rather than half-register. `idStr` must be unique **across all sections**, so an action published into both Main and a non-main section needs a SECOND id string; the FOREVER-STABLE contract binds it identically from the moment it ships. `custom_action_register_t` has no `ACCEL`, so a non-main entry ships no default keybinding. Dispatch for these ids arrives through `"hookcommand2"` (`bool(KbdSectionInfo*, int command, int val, int val2, int relmode, HWND)`) — `"hookcommand"` runs for the main section only. The two hooks must partition the ids between them; what happens when a command is claimed by both is unspecified by the SDK (`hookcommand2`'s doc says a `true` return prevents further hooks/actions from running, which is in tension with a clean double-fire either way), so nothing may rely on either outcome. On unload, mirror with `"-custom_action"` `[verify — DAW]` (the header confirms the `-` prefix for "most" registration types and spells out only `-pcmsrc` by name; `custom_action` itself is unconfirmed) and `"-hookcommand2"`.
## Product design docs ## Product design docs
+4 -1
View File
@@ -2412,7 +2412,10 @@ must not touch `main.cpp`; this track is why.
(`custom_action` / `hookcommand2` / `-custom_action` mirror) — a deliverable of this (`custom_action` / `hookcommand2` / `-custom_action` mirror) — a deliverable of this
track. track.
- **DAW-verification obligation:** Daniel adds the action to the Media Explorer toolbar - **DAW-verification obligation:** Daniel adds the action to the Media Explorer toolbar
and imports from it; also confirms the Main-section binding still fires. and imports from it; also confirms the Main-section binding still fires. Also: unload,
reload, confirm no duplicate Media Explorer entry — the `-custom_action` unload mirror
is unconfirmed against the SDK header (root `CLAUDE.md` §"REAPER extension contract",
`[verify — DAW]`), and if unsupported the entry leaks across a reload.
**Open questions.** `[verify]` re-verify `custom_action_register_t` and `hookcommand2` **Open questions.** `[verify]` re-verify `custom_action_register_t` and `hookcommand2`
argument order/types against the SDK headers at implementation time (root `CLAUDE.md` argument order/types against the SDK headers at implementation time (root `CLAUDE.md`
+37
View File
@@ -445,3 +445,40 @@ today.
bitmap and confirmed to place the stroke correctly, or it is redesigned to rasterize at bitmap and confirmed to place the stroke correctly, or it is redesigned to rasterize at
physical rather than logical resolution once `IPlugViewContentScaleSupport` (or physical rather than logical resolution once `IPlugViewContentScaleSupport` (or
equivalent) makes scaling real. equivalent) makes scaling real.
## `ingestHandleSectionCommand` has no unit test
**Context (what shipped — Ψ-W1-T3, media-explorer-section).** The Media-Explorer
import now dispatches through two hooks — `ingestHandleCommand` (Main,
`"hookcommand"`) and `ingestHandleSectionCommand` (Media Explorer,
`"hookcommand2"`). Both live in `ingest.cpp`, which compiles straight into the
`reaper_reasampler` MODULE target.
**The wart.** No `shell/` translation unit in this repo has a test target — every
`<module>_tests` executable is a `core/` pure-module target. `ingestHandleSectionCommand`
is a two-line command-id comparison; correctness here rests on code review, not CTest.
Review verified this constraint is real and the deferral correct.
**Intended fix.** Make `action_registry` a linkable library and give it the repo's
first `shell/` test target, driven by a fake `reaper_plugin_info_t`. Its own header
(`reaper_plugin.h:153-172`) shows `Register` is a plain member-function pointer on the
struct, not a REAPER API pointer resolved through `REAPERAPI_LoadAPI` — a fake instance
needs no live REAPER process to exercise `rec->Register(...)` calls. Once
`action_registry` is test-covered, move the Media-Explorer section registration into it.
**The constraint the fix MUST handle.** The extraction alone buys nothing:
`action_registry` has no test target today either, so lifting `ingestHandleSectionCommand`
into it without also standing up the test target just relocates the untested code. The
same follow-up could collapse `ingest.cpp:466-472`'s hand-rolled `command_id`+`gaccel`
pair onto `action_registry::registerAction`, which already does exactly that dance for
the Q-W6 table.
**Priority / risk.** Low / deferred. `ingestHandleSectionCommand` is a two-branch
comparison, reviewed and correct at this scope; the gap is the missing test seam, not a
known defect.
**Done looks like.** `action_registry` is a linkable library with its own `shell/`-first
CTest target driven by a fake `reaper_plugin_info_t`; the Media-Explorer section
registration and `ingestHandleSectionCommand` move into it and gain unit coverage; and
`ingest.cpp`'s own `command_id`+`gaccel` registration collapses onto
`action_registry::registerAction` where the shapes match.
+2 -2
View File
@@ -230,8 +230,8 @@ static bool OnHookCommand(int command, int /*flag*/)
} }
// "hookcommand" covers the main section only, so actions we published into another // "hookcommand" covers the main section only, so actions we published into another
// section arrive here instead. Claim ONLY those — every main-section id stays with // section arrive here instead. Partitioning contract: root `CLAUDE.md` §"REAPER
// OnHookCommand, so no command can be claimed by both hooks. // extension contract".
static bool OnHookCommand2(KbdSectionInfo* /*sec*/, int command, int /*val*/, int /*val2*/, static bool OnHookCommand2(KbdSectionInfo* /*sec*/, int command, int /*val*/, int /*val2*/,
int /*relmode*/, HWND /*hwnd*/) int /*relmode*/, HWND /*hwnd*/)
{ {
+3 -3
View File
@@ -479,7 +479,7 @@ void ingestRegisterActions(reaper_plugin_info_t* rec, ReaSamplerSession* session
g_customImportMediaExplorer.uniqueSectionId = kSectionMediaExplorer; g_customImportMediaExplorer.uniqueSectionId = kSectionMediaExplorer;
g_customImportMediaExplorer.idStr = g_idImportMxStr.c_str(); g_customImportMediaExplorer.idStr = g_idImportMxStr.c_str();
g_customImportMediaExplorer.name = g_labelImportStr.c_str(); g_customImportMediaExplorer.name = g_labelImportStr.c_str();
g_customImportMediaExplorer.extra = nullptr; // reserved g_customImportMediaExplorer.extra = nullptr;
g_cmdImportMediaExplorerMx = g_cmdImportMediaExplorerMx =
rec->Register("custom_action", (void*)&g_customImportMediaExplorer); rec->Register("custom_action", (void*)&g_customImportMediaExplorer);
if (!g_cmdImportMediaExplorerMx) if (!g_cmdImportMediaExplorerMx)
@@ -496,8 +496,8 @@ bool ingestHandleCommand(int command) {
bool ingestHandleSectionCommand(int command) { bool ingestHandleSectionCommand(int command) {
if (command == 0 || !g_session) return false; if (command == 0 || !g_session) return false;
// ONLY the Media Explorer id. The Main entry has its own distinct id and is claimed by // ONLY the Media Explorer id — partitioning contract: root `CLAUDE.md`
// ingestHandleCommand off "hookcommand"; claiming it here too would double-fire it. // §"REAPER extension contract".
if (command == g_cmdImportMediaExplorerMx) { doImportFromMediaExplorer(); return true; } if (command == g_cmdImportMediaExplorerMx) { doImportFromMediaExplorer(); return true; }
return false; return false;
} }
+1 -17
View File
@@ -159,7 +159,7 @@ static void testMediaExplorerImportIdsAreDistinctAndChannelIsolated() {
// The Media-Explorer import publishes into TWO action sections, and a custom_action // The Media-Explorer import publishes into TWO action sections, and a custom_action
// idStr must be unique across all sections — so the two entries carry two suffixes. // idStr must be unique across all sections — so the two entries carry two suffixes.
// Both are FOREVER-STABLE per channel. Composed from the SHIPPED constants and checked // Both are FOREVER-STABLE per channel. Composed from the SHIPPED constants and checked
// against spelled-out literals, so a suffix edit in ingest.cpp fails here. // against spelled-out literals, so a suffix edit in ingest.h fails here.
const std::string mainId = channelCommandId(kIngestImportMediaExplorerId); const std::string mainId = channelCommandId(kIngestImportMediaExplorerId);
const std::string mxId = channelCommandId(kIngestImportMediaExplorerMxId); const std::string mxId = channelCommandId(kIngestImportMediaExplorerMxId);
@@ -174,22 +174,6 @@ static void testMediaExplorerImportIdsAreDistinctAndChannelIsolated() {
CHECK(channelActionName("import Media Explorer file into selected track") == CHECK(channelActionName("import Media Explorer file into selected track") ==
"ReaSampler: import Media Explorer file into selected track"); "ReaSampler: import Media Explorer file into selected track");
} }
// Distinctness is what keeps the two dispatch hooks from both claiming one command:
// hookcommand owns the Main id, hookcommand2 owns the Media Explorer id. A suffix
// collapse would silently double-fire the import.
CHECK(mainId != mxId);
// Cross-channel isolation: this build's ids must NOT collide with the other channel's,
// or a beta install would rebind the stable keymap's entries. Asserted against the
// opposite channel's literals, which is the collision that would actually occur.
if (isBeta()) {
CHECK(mainId != "CEREBELLUM_REASAMPLER_INGEST_IMPORT_MEDIA_EXPLORER");
CHECK(mxId != "CEREBELLUM_REASAMPLER_INGEST_IMPORT_MEDIA_EXPLORER_MX");
} else {
CHECK(mainId != "CEREBELLUM_REASAMPLER_BETA_INGEST_IMPORT_MEDIA_EXPLORER");
CHECK(mxId != "CEREBELLUM_REASAMPLER_BETA_INGEST_IMPORT_MEDIA_EXPLORER_MX");
}
} }
static void testStampClassifiesAsStampedOnOwnChannel() { static void testStampClassifiesAsStampedOnOwnChannel() {