Ψ-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:
@@ -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.
|
||||
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 `'-'`.
|
||||
- **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
|
||||
|
||||
|
||||
+4
-1
@@ -2412,7 +2412,10 @@ must not touch `main.cpp`; this track is why.
|
||||
(`custom_action` / `hookcommand2` / `-custom_action` mirror) — a deliverable of this
|
||||
track.
|
||||
- **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`
|
||||
argument order/types against the SDK headers at implementation time (root `CLAUDE.md`
|
||||
|
||||
@@ -445,3 +445,40 @@ today.
|
||||
bitmap and confirmed to place the stroke correctly, or it is redesigned to rasterize at
|
||||
physical rather than logical resolution once `IPlugViewContentScaleSupport` (or
|
||||
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
@@ -230,8 +230,8 @@ static bool OnHookCommand(int command, int /*flag*/)
|
||||
}
|
||||
|
||||
// "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
|
||||
// OnHookCommand, so no command can be claimed by both hooks.
|
||||
// section arrive here instead. Partitioning contract: root `CLAUDE.md` §"REAPER
|
||||
// extension contract".
|
||||
static bool OnHookCommand2(KbdSectionInfo* /*sec*/, int command, int /*val*/, int /*val2*/,
|
||||
int /*relmode*/, HWND /*hwnd*/)
|
||||
{
|
||||
|
||||
@@ -479,7 +479,7 @@ void ingestRegisterActions(reaper_plugin_info_t* rec, ReaSamplerSession* session
|
||||
g_customImportMediaExplorer.uniqueSectionId = kSectionMediaExplorer;
|
||||
g_customImportMediaExplorer.idStr = g_idImportMxStr.c_str();
|
||||
g_customImportMediaExplorer.name = g_labelImportStr.c_str();
|
||||
g_customImportMediaExplorer.extra = nullptr; // reserved
|
||||
g_customImportMediaExplorer.extra = nullptr;
|
||||
g_cmdImportMediaExplorerMx =
|
||||
rec->Register("custom_action", (void*)&g_customImportMediaExplorer);
|
||||
if (!g_cmdImportMediaExplorerMx)
|
||||
@@ -496,8 +496,8 @@ bool ingestHandleCommand(int command) {
|
||||
|
||||
bool ingestHandleSectionCommand(int command) {
|
||||
if (command == 0 || !g_session) return false;
|
||||
// ONLY the Media Explorer id. The Main entry has its own distinct id and is claimed by
|
||||
// ingestHandleCommand off "hookcommand"; claiming it here too would double-fire it.
|
||||
// ONLY the Media Explorer id — partitioning contract: root `CLAUDE.md`
|
||||
// §"REAPER extension contract".
|
||||
if (command == g_cmdImportMediaExplorerMx) { doImportFromMediaExplorer(); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ static void testMediaExplorerImportIdsAreDistinctAndChannelIsolated() {
|
||||
// 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.
|
||||
// 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 mxId = channelCommandId(kIngestImportMediaExplorerMxId);
|
||||
|
||||
@@ -174,22 +174,6 @@ static void testMediaExplorerImportIdsAreDistinctAndChannelIsolated() {
|
||||
CHECK(channelActionName("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() {
|
||||
|
||||
Reference in New Issue
Block a user