Files
reasampler/src/shell/actions/CLAUDE.md
T
daniel 081b6f1028 package: one bank leaves the project as one .rsbank, or the export refuses and says why
Pure planner classifies missing/unreadable/unrepresentable and repairs transport
names; the verb digests, streams and commits atomically over a const session.
2026-08-02 17:19:30 -04:00

7.0 KiB

src/shell/actions — bindable REAPER actions, drag/drop shells, ingest

Scope

The bindable action families routed through REAPER's command_id/gaccel/ hookcommand contract (Design View toggle actions, bank actions, the prune action, and the shared registration plumbing/table), plus the three drag-out outcome shells (OS hand-off, instrument drop, arrange drop), plus the extension-side ingest-through-the-bank shell. This is where user-facing REAPER actions and OS-level drag/drop live; the underlying mutation logic (bank verbs, prune's orphan computation, view-mode reconciliation) is owned by other directories and only skinned here.

Invariants

  • Ingest is an extension act; the instrument is a read-only bank consumer. Any instrument code path that captures, imports, inserts a timeline item, or writes back into the bank is a bug — the instrument reads and plays only.
  • arrange_drop_win is the only timeline-placing shell IN THIS DIRECTORY — the claim scopes here, not to the system: shell/capture holds two more (RunInsertSelected and render_in_place, the third verb). arrange_drop_win places because the USER dragged a card onto the arrange. Root CLAUDE.md's capture/placement separation forbids a CAPTURE placing an item; a deliberate drop is placement on demand. No other module here may grow an InsertMedia call.
  • Ingest NEVER inserts a timeline item. Arrange capture→bank→assign reuses the existing capture add-path and assigns the resulting Sample id to the target instance; it never places anything on the timeline — capture/placement separation is load-bearing here same as everywhere else. Only the arrange-capture surface writes the assignment_request wire; Media-Explorer import and file-drop onto the bank panel do not, and neither affects a live instance's selection.
  • Every bank index verb wraps its mutation in a batched REAPER undo point (Undo_BeginBlock2/EndBlock2, UNDO_STATE_MISCCFG) so one bank operation is one Ctrl-Z.
  • The prune action is the ONLY file-deletion action in the system; it opens no undo point (file deletion is not REAPER-undoable). It halts on blockedByTracking and prints whichever blockers fired — the malformed ledger, the offending rsusage_* key names, or both.

Modules

  • shell/actions (action_registry / design_view_actions / bank_actions / prune_action) — the bindable action families, all routed via the command_id/gaccel/hookcommand contract. action_registry owns the shared registration plumbing (interned channel-qualified id strings; register and mirror-unregister present the identical pointer) and the Q-W6 registration TABLE: main.cpp's own family (capture scopes, panel toggle, insert, batch, realtime, recapture, version) is one ActionTableRow array — suffix, phrase, flat function-pointer handler — that registration, hookcommand dispatch, and the unload mirror-unregister all iterate, so adding an action touches the table only (OCP). Bank mutations flow through the promptless shell/bank_ops verbs (bankOp* + persistBankOp, taking ReaSamplerSession&), which the panel menus and bank_actions consume as thin UX skins. Every bank index verb wraps its mutation in a batched REAPER undo point (Undo_BeginBlock2/EndBlock2, UNDO_STATE_MISCCFG) so one bank operation is one Ctrl-Z. The prune action (prune_action, BANK_PRUNE_FOLDER) is the ONLY file-deletion action in the system; it opens no undo point (file deletion is not REAPER-undoable). BANK_PRUNE_FOLDER halts on blockedByTracking and prints each blocker that fired, with recovery instructions.
  • package_export_action — the "export bank as package" skin: survey and report first, confirm what is absent (and, separately, a destination being replaced), pick a destination, write. Every prompt in the flow lives here so shell/package/export_bank stays promptless. Read-only against the project — it holds the session by const&, so no ext-state write, generation bump or undo point is reachable. Registration rides main.cpp's action table (EXPORT_BANK_PACKAGE); the panel's tab menu is the second skin over the same body.
  • drag_out_win — OS drag-out shell: Windows OLE DoDragDrop/CF_HDROP, copy-only (DROPEFFECT_MOVE not offered); macOS/Linux via SWELL_InitiateDragDropOfFileList.
  • instrument_drop_win — instrument-drop shell: probeDropTarget resolves a screen point to a track + a ReaperSurface (via the pure wire::classifyReaperSurface, whose token rules core/wire/CLAUDE.md owns), and the drop half adds a ReaSampler 9000 instance and applies the dragged capture's state via a transient .vstpreset + TrackFX_SetPreset (the former TrackFX_SetNamedConfigParm "vst_chunk" write was silently unappliable for VST3). Exposes loadInstrumentOntoTrack (inner half, no own undo block) and performInstrumentDrop (wraps in its own undo block). Never captures, never writes the bank, never inserts a timeline item.
  • arrange_drop_win — the drag-out gesture's arrange outcome: arrangeTimeAtScreenX (pointer column → time via GetSet_ArrangeView2's one-pixel-span reading — inferred, not SDK-documented) and performArrangeDrop (snap the drop time, then one InsertMedia per capture on the pointer's track — assumed, not confirmed, to land end-to-end via REAPER's own cursor advance — in ONE undo block, counting only InsertMedia's reported successes, with the caller's track selection and edit cursor restored). The one timeline-placing shell here, per the invariant above; it never captures and never writes the bank.
  • ingest — ingest-through-the-bank shell on the EXTENSION side: three surfaces — (1) arrange capture→bank→assign (bindable action), (2) Media-Explorer import→bank→instrument on the selected track, (3) file drop onto the bank panel→bank only. Only surface (1) writes the assignment_request ext-state wire. ingest NEVER inserts a timeline item. Surface (2)'s action is the one in this directory published into a NON-main action section (Media Explorer) as well as Main — two ids, one handler, two dispatch hooks; see root CLAUDE.md §"REAPER extension contract" for the mechanism.

Gotchas

  • Structural wart, partly closed: ingest.cpp / ingest.h now live in this directory. ext_keys.h and resource.h still sit at src/ root: ext_keys.h is consumed mostly from shell/instrument/, so it is not this directory's to claim, and resource.h is a build input paired with src/resource.rc (the SWELL resgen step) rather than a shell module.
  • Media-Explorer import is single-file, pull-on-action (OpenMediaExplorer + MediaExplorerGetLastPlayedFileInfo) — there is no enumerate-selected-files or register-a-drop-handler API on the Media Explorer surface.
  • REAPER exposes no drag-drop registration API; drop handling is only on ReaSampler's own HWNDs (WM_DROPFILES/IDropTarget on the docked bank_panel). A drop onto the VST3 editor window relaying to the extension is an unproven spike, not a shipped path.