From c66f44ba1f6776d667521403f34ae1ebbe39ba1c Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Sun, 26 Jul 2026 15:51:13 -0400 Subject: [PATCH] docs: archive Phase B (B1-B5, B-cap, R-B) to COMPLETED; reconcile CLAUDE.md --- CLAUDE.md | 8 +- COMPLETED.md | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++ PLAN.md | 200 -------------------------------------------- 3 files changed, 234 insertions(+), 203 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 83965cc..34d8744 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,6 +34,7 @@ Key targets (see CMakeLists.txt for the full list): | `mode_switch_tests` | executable | Pure unit tests for `mode_switch` — no REAPER, no DAW. | | `bank_book_tests` | executable | Pure unit tests for `bank_book` — no REAPER, no DAW. | | `wav_trim_tests` | executable | Pure unit tests for `wav_trim` — no REAPER, no DAW. | +| `owned_manifest_tests` | executable | Pure unit tests for `owned_manifest` — no REAPER, no DAW. | | `reaper_reasampler` | loadable module | The actual extension binary (`.dll` / `.dylib` / `.so`). | ### macOS / Linux: SWELL dialog resources @@ -56,17 +57,18 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde - `view_mode_model` — Design View mode system: mode registry, GUID-keyed membership, folder-tree-aware visibility derivation, snapshot-based park/restore planner, JSON round-trip. Mirror of `bank_model` for the Design View phase. - `view_tree` — pure `I_FOLDERDEPTH`→FolderTree helper for the Design View shell; no REAPER types at the boundary. - `mode_switch` — REAPER-free segment layout + hit-test math for the bank_panel's Design View mode switch; divides a header rectangle into N equal segments and hit-tests a point to a segment. Mirror of `bank_grid`. -- `bank_book` — multi-bank registry (Phase B): an ordered set of banks (pool seeded as bank-zero + named banks), each wrapping a `BankIndex`. Owns create/rename/reorder/delete of named banks, pool privileges (un-deletable/un-renamable/un-evacuable, never zero banks) enforced in-model, active-bank id, index-only move/copy of a sample between banks, JSON round-trip + legacy-`bank_index`→pool migration. Wraps `BankIndex` (bank_model untouched; no `bankId` on `Sample`). +- `bank_book` — multi-bank registry (Phase B): an ordered set of banks (pool seeded as bank-zero + named banks), each wrapping a `BankIndex`. Owns create/rename/reorder/delete of named banks, pool privileges (un-deletable/un-renamable/un-evacuable, never zero banks) enforced in-model, active-bank id, index-only move/copy/remove of a sample between or from banks, `hashReferencedElsewhere` cross-bank reference query, JSON round-trip + legacy-`bank_index`→pool migration. Wraps `BankIndex` (bank_model untouched; no `bankId` on `Sample`). +- `owned_manifest` — owned-file manifest seam (Phase B B-cap): the set of project-relative files the capture path itself created, persisted under the `"owned_files"` ext-state key, so Phase R prune can distinguish the bank system's own orphans from hand-dropped files. Deliberately decoupled from `bank_book` — tracks files created, not index membership. Phase R (R1/R2) consumes it; no prune logic here. - `wav_trim` — 32-bit-float WAV parse + header-aware truncate plan (RIFF/data size rewrite) for the realtime tail's PCM decay-scan trim (T2). Rejects WAVE_FORMAT_EXTENSIBLE with non-float SubFormat GUID. Depends on `peaks` for the `AudioSample` float alias. **REAPER-facing shells:** - `capture` — `ICaptureBackend` interface; `OfflineRenderBackend` (deterministic default) and `RealtimeRecordBackend`. Input: `CaptureRequest`. Output: finished file + populated `Sample` handed to `bank_model`. - `insert` — placement via `InsertMedia`; conform-to-project-tempo is an explicit opt-in flag, never silent stretching. - `bank_panel` — docked LICE-drawn grid: thumbnails, audition, multi-select, keyboard navigation. -- `persist` — project ext state (`SetProjExtState` / `GetProjExtState`, namespace `"reasampler"`) ↔ `bank_model` JSON (`"bank_index"` key) + `ViewModeModel` JSON (`"view_state"` key) + `TailSetting` JSON (`"tail_setting"` key); project-relative path resolution. +- `persist` — project ext state (`SetProjExtState` / `GetProjExtState`, namespace `"reasampler"`) ↔ `BankBook` JSON (`"banks"` key) + `ViewModeModel` JSON (`"view_state"` key) + `TailSetting` JSON (`"tail_setting"` key) + `OwnedManifest` JSON (`"owned_files"` key); project-relative path resolution. A `projectconfig` hook (`BeginLoadProjectState(isUndo)`) triggers a deferred session reload on undo/redo so Ctrl-Z/redo visibly restores book/view/tail/manifest in-session. - `view` — Design View shell: reads the folder tree via `view_tree`, snapshots flag values before parking, drives hide + CPU-park on inactive-mode leaves (`B_SHOWINTCP`/`B_SHOWINMIXER`/`B_MAINSEND`/`I_FXEN` + per-FX offline) and derived visibility on parents; restores from snapshot. Never touches master or `B_MUTE`/`I_SOLO`. - `track_guid` — shared `MediaTrack*` → canonical GUID-string formatter; single source of truth for membership keys used by both the view shell and the actions layer. -- `actions` — registers the capture/placement/slot action family and the Design View action family (toggle active mode, activate Arrange/Design, tag/untag selected tracks, show-both); routes each to the modules above via the `command_id`/`gaccel`/`hookcommand` contract. +- `actions` — registers the capture/placement/slot action family, the Design View action family (toggle active mode, activate Arrange/Design, tag/untag selected tracks, show-both), and the multi-bank action family (create/rename/reorder/delete bank, evacuate, activate, move/copy/remove selected samples); routes each to the modules above via the `command_id`/`gaccel`/`hookcommand` contract. 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. ## REAPER extension contract (src/main.cpp) diff --git a/COMPLETED.md b/COMPLETED.md index 72542b4..0c40657 100644 --- a/COMPLETED.md +++ b/COMPLETED.md @@ -654,3 +654,232 @@ items exempt; one undo block per action. ctest 14/14 green. lane-split marker has no natural cheap home in the bank panel; the mode switch already shows the active mode. Preserved as a deferred/backlog note in PLAN.md Phase D2 — not silently dropped. + +--- + +# Phase B — Multi-bank (parallel to the M0–M11 capture roadmap and Phase D) + +> **Separate phase namespace.** The M-numbers belong to the capture pillar +> (M0–M11); the D-letters belong to Design View. Multi-bank is a third orthogonal +> pillar — generalizing the single bank into a pool + named banks — so it takes its +> own **lettered** namespace (B1, B2, …). "B" reads for **Banks** and, like Phase D, +> keeps the roadmaps from colliding on numbering: Phase B is not "the twelfth +> capture step," it is a different pillar. Authoritative spec: **CONTEXT.md +> §Multi-bank**. Product framing: `docs/product/multi-bank.md`. + +## B1 — bank_book (pure) +**Goal:** REAPER-free bank registry wrapping N `BankIndex` instances: pool seeded + +privileged, create/rename/reorder/delete named banks, active-bank id, move/copy a +sample between banks, JSON round-trip + legacy-migration. The heart of the phase; +mirror of `bank_model` / `view_mode_model`; **`BankIndex` untouched (additive)**. +CONTEXT.md §Multi-bank (Module architecture — pure). +**Verify:** CTest green. Pool always present, un-deletable, un-renamable, +un-evacuable (rules rejected in-model). Active-bank defaults to pool. Move is +index-only (source loses entry, destination gains it) and observes destination +collapse-by-hash; copy leaves source intact. Delete drops member index entries. +Evacuate moves all members to the pool, leaving the bank empty. JSON round-trip +lossless across pool-as-bank-zero + named banks + per-bank indices + ordinals + +active id. Legacy `bank_index` JSON parses into `{ pool }` with zero named banks. + +- [x] Bank registry: ordered `{ bank id, display name, ordinal, BankIndex }`; pool + seeded with fixed id + fixed name; create / rename / reorder / delete named banks + (delete drops the bank's member index entries). +- [x] Pool-privilege rules enforced in-model: reject delete-pool, reject + rename-pool, reject evacuate-pool, never allow zero banks. +- [x] Active-bank id (get/set; defaults to pool); resolve active bank's `BankIndex`. +- [x] Move sample between banks (index-only; destination collapse-by-hash observed; + source entry removed). +- [x] Copy sample between banks (index-only; source entry retained; destination + collapse-by-hash observed). +- [x] Evacuate bank: move every member to the pool (index-only; destination + collapse-by-hash observed), leaving the bank empty; pool cannot be evacuated. +- [x] JSON round-trip: pool-as-bank-zero inside the blob + named banks + per-bank + indices + ordinals + active id. +- [x] Legacy migration: a bare `bank_index` JSON promotes to the pool's index with + zero named banks (one-way, lossless; blob authoritative thereafter). +- [x] Tests: pool privileges (delete/rename/evacuate rejected); move + source-loses/dest-gains; copy source-retained; evacuate empties source into pool + with dest collapse; cross-bank same-hash coexistence; dest collapse on move into a + bank already holding the hash; JSON lossless; legacy migration. + +> **Phase-B-wide undo (fork R-B, settled 2026-07-24 — batched REAPER undo points).** +> Every index verb across B1–B5 (create/rename/reorder/delete-bank, move, copy, +> evacuate, remove) wraps its bank/index mutation in a **batched REAPER undo point** +> (`Undo_BeginBlock` / `Undo_EndBlock`), so one bank operation is one Ctrl-Z. This is +> a cross-cutting decision that retro-touches B1–B4, not a B5-local one; the +> per-verb points above inherit it. **Must-verify before build:** confirm against +> `vendor/reaper-sdk` that `"reasampler"` ext-state mutations participate correctly +> in `Undo_BeginBlock`/`Undo_EndBlock` undo blocks — the whole approach depends on +> it. See CONTEXT.md §Sample removal (Guardrails) + product notes §Fork R-B. + +**Notes/decisions (R-B — Phase-B-wide undo, landed):** +- Every bank index verb — bindable action AND panel gesture (menu/drag/Delete key) — + is one batched REAPER undo point (`Undo_BeginBlock2`/`EndBlock2`, + `UNDO_STATE_MISCCFG`); ext-state participates in undo via `UNDO_STATE_MISCCFG` + ("extensions!"), SDK-verified. A `projectconfig` hook + (`BeginLoadProjectState(isUndo)`) triggers a deferred session reload so Ctrl-Z/redo + visibly restores book/view/tail/manifest in-session. Rejected/no-op ops open no + undo point; unsaved-project ops discard the empty block. + +--- + +## B-cap — owned-file manifest seam (capture writes; prune consumes in Phase R) +**Goal:** Capture writes each file it creates into an **owned-file manifest** +persisted in the `"reasampler"` ext-state, so Phase R prune can later distinguish +the bank system's own orphans from hand-dropped files. Consumed only in Phase R +(R1/R2) — landed early here because reconstructing the manifest retroactively is a +backfill cliff (fork R-D, settled 2026-07-24: *defer the feature, design the seam*). +CONTEXT.md §Prune (Settled decisions — orphan attribution) + product notes §Fork R-D. +**Verify:** every file the capture path creates is recorded in the owned-file +manifest; the manifest round-trips through the `"reasampler"` ext-state (Save / Save +As / reopen); relative-paths-only preserved. Prune's consumption of it is Phase R. +**Depends on:** the capture add-path (M7) + persist blob machinery (M4 / B2). + +- [x] Capture records each created file into an owned-file manifest (the set of + files the book has created), persisted in the `"reasampler"` ext-state (sibling + `owned_files` key — persistence shape resolved at build time: sibling key, not + folded into the `banks` blob). +- [x] Manifest round-trips: survives Save / Save As / reopen via the M4 blob + machinery; relative-paths-only. (Consumed by Phase R R1/R2 — not consumed here.) + +**Notes/decisions:** +- Pure `owned_manifest` module (`src/owned_manifest.{h,cpp}`): relative paths, dedup, + JSON round-trip. Deliberately decoupled from `bank_book` — tracks files created, not + index membership; sample-remove is not manifest-remove. Unit-tested via new + `owned_manifest_tests` CTest target. Persisted under the `"owned_files"` ext-state + key. Both capture commit paths (offline + realtime) record created files. Joins the + undo-reload set. + +--- + +## B2 — persist slice (banks ↔ project ext state) +**Goal:** Serialize the book under the `banks` key in `"reasampler"` alongside the +existing sections, with the pool folded in as bank-zero; migrate a legacy +`bank_index` key into the pool on first load and retire the legacy key; reload-on-open +and Save-As survival via the existing M4 machinery. CONTEXT.md §Multi-bank (persist). +**Verify (in DAW):** Banks + named banks + active bank + all per-bank samples survive +Save / Save As / close+reopen; **relative paths only**; bank travels with the `.rpp`; +a project saved before this phase (legacy `bank_index` only) loads as pool + zero +named banks with no sample loss, and after save carries `banks` with no `bank_index` +written. +**Depends on:** B1. (Persistence-key fork settled — fork 1 (a): pool inside the +`banks` blob, legacy key retired after one-way migration.) + +- [x] Serialize/deserialize the book under the `banks` key (pool-as-bank-zero inside + the blob; distinct section from `view_state`; no `bank_index` key written going + forward). +- [x] Legacy-migration path on load: absent `banks` + present `bank_index` → promote + into pool, mint the blob, treat blob as authoritative (legacy key retired). +- [x] Session exposes the book; the active bank's `BankIndex` is the capture add + target (route the M7 capture family through it — additive to M7, no M7 rewrite). +- [x] Confirm survival across Save / Save As; confirm legacy-project load path. + +--- + +## B3 — actions +**Goal:** Bindable action set for the multi-bank workflow. CONTEXT.md §Multi-bank +(actions). **Verify (in DAW):** Each action registered (bindable in Actions list); +bank-activate + move/copy + evacuate MIDI-bindable; create/rename/delete/evacuate +drive the B1 model via the B2-persisted session. +**Depends on:** B1, B2. + +- [x] Create bank / rename bank / delete bank (delete drops member index entries; + confirm-on-non-empty offered at the UI layer in B4). +- [x] Evacuate bank → pool (move all members back to the pool; refuses on the pool). +- [x] Activate bank (direct-by-id + cycle). +- [x] Move selected samples → bank / copy selected samples → bank (move is default). +- [x] Pool full-height / banks full-height toggles. +- [x] Register each (`command_id`/`gaccel`/`hookcommand`); bank-activate + move/copy + + evacuate MIDI-bindable. + +--- + +## B4 — bank_panel vertical split (UI) +**Goal:** The vertical-split bank window — pool on top, named-banks tab-page region +below, full-height toggles — extending the M5 docked grid. CONTEXT.md §Multi-bank +(bank_panel). **Verify (in DAW):** Pool grid renders on top; named-banks tab strip +below (empty when no named banks, one tab per named bank); active-bank **unmistakably** +indicated; both full-height toggles collapse the split correctly; sample move/copy +affordance works; non-empty delete confirms and offers evacuate; the Design View mode +switch in the header is unaffected. +**Depends on:** B1, B2, B3. (Tab rendering + move-affordance mechanics — fork 5 — +settled 2026-07-23: LICE-drawn tabs + both move affordances; see Phase B open questions +and product notes → *Fork 5 — settled*.) + +- [x] Vertical split: pool grid region (top) + named-banks tab-page region (bottom). +- [x] Named-banks tab strip: **LICE-drawn** (matching the M5 grid + Design View + segmented switch, not SWELL-native — fork 5a); one tab per named bank; empty state + when none. +- [x] Tab-strip overflow/scroll affordance (fork 5a): scroll/chevron overflow shipped + with the strip. +- [x] Pool full-height / banks full-height toggle affordances wired to B3. +- [x] Active-bank indicator — **visually unmistakable** (settled constraint). +- [x] Create / rename / delete / activate / evacuate affordances driving B3 actions. +- [x] Delete confirms on a non-empty bank, naming the evacuate alternative. +- [x] Sample move affordance — **both** (fork 5b): a "move to bank" menu on the current + selection (bindable front-end for the B3 move action) **and** drag-between-regions. + Copy is the deliberate secondary act, offered on the menu. +- [x] Drag mis-drop mitigation (fork 5b): clear drop-target highlighting on the + destination region/tab during a drag. + +**Notes/decisions:** +- New pure module `src/tab_strip.{h,cpp}`: named-banks tab-strip geometry (B4) — + strip rect + N tabs + scroll offset → per-tab rects (overflow-clipped), overflow + chevron reservation + maxScroll, and point → tab/chevron hit-test. Unit-tested via + `tab_strip_tests` CTest target. Mirror of `mode_switch`. +- Active-bank indicator placement (the open polish detail from the Phase B open + questions) was resolved at build time in the panel implementation. +- Post-landing: m11's console-chatter policy applied to Phase B messages (successes + silent, failures kept). + +--- + +## B5 — sample-remove (the missing sample-level verb) +**Goal:** Drop an individual `Sample`'s index entry from a bank or the pool — +the sample-level companion to move/copy/evacuate/delete-bank. Index-only, +non-destructive to the file; exposes the `BankIndex::remove` primitive that +`bank_model` already has (wires it, does not add it). CONTEXT.md §Sample removal. +Product framing + open forks: `docs/product/removal-and-prune.md` §Sample-remove. +**Verify (in DAW):** Remove drops the selected sample's entry from the target +bank; a same-hash entry in another bank is untouched (no cross-bank dedup); +pool *contents* are removable while pool-container privileges hold; removing the +last index reference to a file leaves that file on disk (orphaned until prune — +never deleted by remove); non-destructive (index + ext-state only, no file, no +timeline item). +**Depends on:** B1, B2, B3 (action set), B4 (panel affordance). + +- [x] Surface `BankIndex::remove` through `bank_book`: remove a `Sample` from a + bank's index; pool contents removable, pool-container privileges unchanged. +- [x] "Remove selected sample(s)" action (`command_id`/`gaccel`/`hookcommand`), + MIDI-bindable; carries a `scope: this-bank | all-banks` seam (fork R-A, settled + 2026-07-24: **this-bank** is the default and only surfaced affordance; all-banks + stays a latent seam-only parameter, not shipped). +- [x] `bank_panel` remove affordance on the current selection (reuse M5 selection + model, as move/copy do). +- [x] Confirm-on-last-reference guardrail: remove that orphans a file (no other + bank references it) confirms, naming the orphaned-until-prune consequence; + remove of a still-referenced sample does not confirm. +- [x] Tests: remove drops the target entry; same-hash entry in another bank + survives; remove-from-pool allowed; last-reference remove leaves an orphan (file + untouched); non-destructive (no file/timeline mutation). + +**Notes/decisions (B5 forks — settled 2026-07-24):** +- **R-A — remove scope.** Settled: **this-bank**. Removes the entry from the bank in + view only; the `scope: this-bank | all-banks` seam stays in the action signature + but all-banks is a latent parameter, not a surfaced verb. +- `hashReferencedElsewhere` cross-bank reference query added to `bank_book` to + support the confirm-on-last-reference guardrail without coupling to the panel. +- Both `bank_panel` context menu ("Remove selected sample(s)") and Delete key + affordance wired; panel gesture is also one batched undo point (R-B applies). + +--- + +## Phase B open questions — all resolved + +All five forks settled by Daniel (2026-07-23): persistence key = fold pool into `banks`, +retire legacy key (1a); delete drops members + add evacuate verb (2); move is the +default gesture (3); active-bank/shown-tab distinct with an unmistakable indicator (4); +LICE-drawn tabs + overflow, and both move affordances with drop-highlighting (5). +Active-bank indicator placement (the one residual polish detail) was resolved at +build time. B5 forks R-A and R-B settled 2026-07-24 (see B5 and B1 notes above). +Both in `docs/product/removal-and-prune.md` §Fork R-A / §Fork R-B. diff --git a/PLAN.md b/PLAN.md index 88d1adf..78ace0b 100644 --- a/PLAN.md +++ b/PLAN.md @@ -130,206 +130,6 @@ landed milestone. --- -# Phase B — Multi-bank (parallel to the M0–M11 capture roadmap and Phase D) - -> **Separate phase namespace.** The M-numbers belong to the capture pillar -> (M0–M11); the D-letters belong to Design View. Multi-bank is a third orthogonal -> pillar — generalizing the single bank into a pool + named banks — so it takes its -> own **lettered** namespace (B1, B2, …). "B" reads for **Banks** and, like Phase D, -> keeps the roadmaps from colliding on numbering: Phase B is not "the twelfth -> capture step," it is a different pillar. Authoritative spec: **CONTEXT.md -> §Multi-bank**. Product framing: `docs/product/multi-bank.md`. When a point lands, -> doc-keeper moves it to `COMPLETED.md`. - -## B1 — bank_book (pure) -**Goal:** REAPER-free bank registry wrapping N `BankIndex` instances: pool seeded + -privileged, create/rename/reorder/delete named banks, active-bank id, move/copy a -sample between banks, JSON round-trip + legacy-migration. The heart of the phase; -mirror of `bank_model` / `view_mode_model`; **`BankIndex` untouched (additive)**. -CONTEXT.md §Multi-bank (Module architecture — pure). -**Verify:** CTest green. Pool always present, un-deletable, un-renamable, -un-evacuable (rules rejected in-model). Active-bank defaults to pool. Move is -index-only (source loses entry, destination gains it) and observes destination -collapse-by-hash; copy leaves source intact. Delete drops member index entries. -Evacuate moves all members to the pool, leaving the bank empty. JSON round-trip -lossless across pool-as-bank-zero + named banks + per-bank indices + ordinals + -active id. Legacy `bank_index` JSON parses into `{ pool }` with zero named banks. - -- [ ] Bank registry: ordered `{ bank id, display name, ordinal, BankIndex }`; pool - seeded with fixed id + fixed name; create / rename / reorder / delete named banks - (delete drops the bank's member index entries). -- [ ] Pool-privilege rules enforced in-model: reject delete-pool, reject - rename-pool, reject evacuate-pool, never allow zero banks. -- [ ] Active-bank id (get/set; defaults to pool); resolve active bank's `BankIndex`. -- [ ] Move sample between banks (index-only; destination collapse-by-hash observed; - source entry removed). -- [ ] Copy sample between banks (index-only; source entry retained; destination - collapse-by-hash observed). -- [ ] Evacuate bank: move every member to the pool (index-only; destination - collapse-by-hash observed), leaving the bank empty; pool cannot be evacuated. -- [ ] JSON round-trip: pool-as-bank-zero inside the blob + named banks + per-bank - indices + ordinals + active id. -- [ ] Legacy migration: a bare `bank_index` JSON promotes to the pool's index with - zero named banks (one-way, lossless; blob authoritative thereafter). -- [ ] Tests: pool privileges (delete/rename/evacuate rejected); move - source-loses/dest-gains; copy source-retained; evacuate empties source into pool - with dest collapse; cross-bank same-hash coexistence; dest collapse on move into a - bank already holding the hash; JSON lossless; legacy migration. - -> **Phase-B-wide undo (fork R-B, settled 2026-07-24 — batched REAPER undo points).** -> Every index verb across B1–B5 (create/rename/reorder/delete-bank, move, copy, -> evacuate, remove) wraps its bank/index mutation in a **batched REAPER undo point** -> (`Undo_BeginBlock` / `Undo_EndBlock`), so one bank operation is one Ctrl-Z. This is -> a cross-cutting decision that retro-touches B1–B4, not a B5-local one; the -> per-verb points above inherit it. **Must-verify before build:** confirm against -> `vendor/reaper-sdk` that `"reasampler"` ext-state mutations participate correctly -> in `Undo_BeginBlock`/`Undo_EndBlock` undo blocks — the whole approach depends on -> it. See CONTEXT.md §Sample removal (Guardrails) + product notes §Fork R-B. - -## B-cap — owned-file manifest seam (capture writes; prune consumes in Phase R) -**Goal:** Capture writes each file it creates into an **owned-file manifest** -persisted in the `"reasampler"` ext-state, so Phase R prune can later distinguish -the bank system's own orphans from hand-dropped files. Consumed only in Phase R -(R1/R2) — landed early here because reconstructing the manifest retroactively is a -backfill cliff (fork R-D, settled 2026-07-24: *defer the feature, design the seam*). -CONTEXT.md §Prune (Settled decisions — orphan attribution) + product notes §Fork R-D. -**Verify:** every file the capture path creates is recorded in the owned-file -manifest; the manifest round-trips through the `"reasampler"` ext-state (Save / Save -As / reopen); relative-paths-only preserved. Prune's consumption of it is Phase R. -**Depends on:** the capture add-path (M7) + persist blob machinery (M4 / B2). - -- [ ] Capture records each created file into an owned-file manifest (the set of - files the book has created), persisted in the `"reasampler"` ext-state (sibling - key or folded into the `banks` blob — persistence shape is a small build-time - residual, not a fork). -- [ ] Manifest round-trips: survives Save / Save As / reopen via the M4 blob - machinery; relative-paths-only. (Consumed by Phase R R1/R2 — not consumed here.) - -## B2 — persist slice (banks ↔ project ext state) -**Goal:** Serialize the book under the `banks` key in `"reasampler"` alongside the -existing sections, with the pool folded in as bank-zero; migrate a legacy -`bank_index` key into the pool on first load and retire the legacy key; reload-on-open -and Save-As survival via the existing M4 machinery. CONTEXT.md §Multi-bank (persist). -**Verify (in DAW):** Banks + named banks + active bank + all per-bank samples survive -Save / Save As / close+reopen; **relative paths only**; bank travels with the `.rpp`; -a project saved before this phase (legacy `bank_index` only) loads as pool + zero -named banks with no sample loss, and after save carries `banks` with no `bank_index` -written. -**Depends on:** B1. (Persistence-key fork settled — fork 1 (a): pool inside the -`banks` blob, legacy key retired after one-way migration.) - -- [ ] Serialize/deserialize the book under the `banks` key (pool-as-bank-zero inside - the blob; distinct section from `view_state`; no `bank_index` key written going - forward). -- [ ] Legacy-migration path on load: absent `banks` + present `bank_index` → promote - into pool, mint the blob, treat blob as authoritative (legacy key retired). -- [ ] Session exposes the book; the active bank's `BankIndex` is the capture add - target (route the M7 capture family through it — additive to M7, no M7 rewrite). -- [ ] Confirm survival across Save / Save As; confirm legacy-project load path. - -## B3 — actions -**Goal:** Bindable action set for the multi-bank workflow. CONTEXT.md §Multi-bank -(actions). **Verify (in DAW):** Each action registered (bindable in Actions list); -bank-activate + move/copy + evacuate MIDI-bindable; create/rename/delete/evacuate -drive the B1 model via the B2-persisted session. -**Depends on:** B1, B2. - -- [ ] Create bank / rename bank / delete bank (delete drops member index entries; - confirm-on-non-empty offered at the UI layer in B4). -- [ ] Evacuate bank → pool (move all members back to the pool; refuses on the pool). -- [ ] Activate bank (direct-by-id + cycle). -- [ ] Move selected samples → bank / copy selected samples → bank (move is default). -- [ ] Pool full-height / banks full-height toggles. -- [ ] Register each (`command_id`/`gaccel`/`hookcommand`); bank-activate + move/copy - + evacuate MIDI-bindable. - -## B4 — bank_panel vertical split (UI) -**Goal:** The vertical-split bank window — pool on top, named-banks tab-page region -below, full-height toggles — extending the M5 docked grid. CONTEXT.md §Multi-bank -(bank_panel). **Verify (in DAW):** Pool grid renders on top; named-banks tab strip -below (empty when no named banks, one tab per named bank); active-bank **unmistakably** -indicated; both full-height toggles collapse the split correctly; sample move/copy -affordance works; non-empty delete confirms and offers evacuate; the Design View mode -switch in the header is unaffected. -**Depends on:** B1, B2, B3. (Tab rendering + move-affordance mechanics — fork 5 — -settled 2026-07-23: LICE-drawn tabs + both move affordances; see Phase B open questions -and product notes → *Fork 5 — settled*.) - -- [ ] Vertical split: pool grid region (top) + named-banks tab-page region (bottom). -- [ ] Named-banks tab strip: **LICE-drawn** (matching the M5 grid + Design View - segmented switch, not SWELL-native — fork 5a); one tab per named bank; empty state - when none. **Verify LICE tab draw against the M5 reference before use.** -- [ ] Tab-strip overflow/scroll affordance — **in scope from the start** (fork 5a): a - naive fixed-width LICE strip breaks down at ~8–12 tabs, so ship scroll/chevron - overflow with the strip, do not defer it. -- [ ] Pool full-height / banks full-height toggle affordances wired to B3. -- [ ] Active-bank indicator — **visually unmistakable** (settled constraint); - placement (per-region header / single readout / lit-tab) is the residual polish - detail. -- [ ] Create / rename / delete / activate / evacuate affordances driving B3 actions. -- [ ] Delete confirms on a non-empty bank, naming the evacuate alternative. -- [ ] Sample move affordance — **both** (fork 5b): a "move to bank" menu on the current - selection (bindable front-end for the B3 move action) **and** drag-between-regions. - Copy is the deliberate secondary act, offered on the menu. -- [ ] Drag mis-drop mitigation (fork 5b): clear drop-target highlighting on the - destination region/tab during a drag; a mis-drop is recoverable by design (move is - index-only and reversible). **Verify the drag hit-test doesn't collide with the M5 - grid's multi-select drag.** - -## B5 — sample-remove (the missing sample-level verb) -**Goal:** Drop an individual `Sample`'s index entry from a bank or the pool — -the sample-level companion to move/copy/evacuate/delete-bank. Index-only, -non-destructive to the file; exposes the `BankIndex::remove` primitive that -`bank_model` already has (wires it, does not add it). CONTEXT.md §Sample removal. -Product framing + open forks: `docs/product/removal-and-prune.md` §Sample-remove. -**Verify (in DAW):** Remove drops the selected sample's entry from the target -bank; a same-hash entry in another bank is untouched (no cross-bank dedup); -pool *contents* are removable while pool-container privileges hold; removing the -last index reference to a file leaves that file on disk (orphaned until prune — -never deleted by remove); non-destructive (index + ext-state only, no file, no -timeline item). -**Depends on:** B1, B2, B3 (action set), B4 (panel affordance). - -- [ ] Surface `BankIndex::remove` through `bank_book`: remove a `Sample` from a - bank's index; pool contents removable, pool-container privileges unchanged. -- [ ] "Remove selected sample(s)" action (`command_id`/`gaccel`/`hookcommand`), - MIDI-bindable; carries a `scope: this-bank | all-banks` seam (fork R-A, settled - 2026-07-24: **this-bank** is the default and only surfaced affordance; all-banks - stays a latent seam-only parameter, not shipped). -- [ ] `bank_panel` remove affordance on the current selection (reuse M5 selection - model, as move/copy do). -- [ ] Confirm-on-last-reference guardrail: remove that orphans a file (no other - bank references it) confirms, naming the orphaned-until-prune consequence; - remove of a still-referenced sample does not confirm. -- [ ] Tests: remove drops the target entry; same-hash entry in another bank - survives; remove-from-pool allowed; last-reference remove leaves an orphan (file - untouched); non-destructive (no file/timeline mutation). - -## Phase B open questions -All five forks settled by Daniel (2026-07-23): persistence key = fold pool into `banks`, -retire legacy key (1a); delete drops members + add evacuate verb (2); move is the -default gesture (3); active-bank/shown-tab distinct with an unmistakable indicator (4); -LICE-drawn tabs + overflow, and both move affordances with drop-highlighting (5). -Folded into CONTEXT.md §Multi-bank + the B1–B4 points above. Phase B is fully settled and -ready to scope into implementation waves. One polish detail remains: - -- **Active-bank indicator placement** — per-region headers vs. single header readout - vs. lit-tab. "Unmistakable" is settled; only placement is open. Polish detail. - (touches B4) - -**B5 sample-remove forks — settled 2026-07-24:** -- **R-A — remove scope.** Settled: **this-bank**. Removes the entry from the bank in - view only; the `scope: this-bank | all-banks` seam stays in the action signature - but all-banks is a latent parameter, not a surfaced verb. Folded into the B5 action - point above. -- **R-B — undo model (Phase-B-wide).** Settled: **batched REAPER undo points** - (`Undo_BeginBlock`/`Undo_EndBlock`), one bank op = one Ctrl-Z. Applies across - B1–B5 (retro-touches B1–B4) — captured as the cross-cutting note under B1 above, - with the ext-state-participation SDK check as a must-verify-before-build. -Both in `docs/product/removal-and-prune.md` §Fork R-A / §Fork R-B. - ---- - # Phase R — Reclaim (file lifecycle: the prune path) > **New pillar, own lettered namespace.** Prune is the file-lifecycle path the