# 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**, and
  it 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.
- `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 `classifyReaperSurface`, which owns every token rule), 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` over a one-pixel span) and `performArrangeDrop` (snap the drop time, then one `InsertMedia` per capture on the pointer's track, laid out sequentially, in ONE undo block, with the caller's track selection and edit cursor restored). This is the one shell in this directory that DOES place timeline items — and it is legitimate because the user dragged them there, the same class of act as `RunInsertSelected`. 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.**

## 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.
