docs(multi-bank): frame Phase B — pool + named banks spec

Add the multi-bank pillar as Phase B (lettered, parallel to M-line and
Phase D): CONTEXT.md §Multi-bank spec, PLAN.md Phase B block (B1 bank_book
pure / B2 persist / B3 actions / B4 panel split), and docs/product/multi-bank.md
framing. Additive only — BankIndex, the M0–M11 roadmap, and Phase D untouched.
This commit is contained in:
2026-07-23 12:52:06 -04:00
parent 43ca5e4069
commit d9081090fd
3 changed files with 616 additions and 0 deletions
+103
View File
@@ -79,3 +79,106 @@ landed milestone.
in int64 but exceed `INT_MAX` are implementation-defined. Hardening candidate
— add bounds check before the cast when integer-field validation is in scope.
---
# Phase B — Multi-bank (parallel to the M0M11 capture roadmap and Phase D)
> **Separate phase namespace.** The M-numbers belong to the capture pillar
> (M0M11); 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 (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. JSON round-trip lossless across 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.
- [ ] Pool-privilege rules enforced in-model: reject delete-pool, reject
rename-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).
- [ ] JSON round-trip: 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).
- [ ] Tests: pool privileges (delete/rename rejected); move source-loses/dest-gains;
copy source-retained; cross-bank same-hash coexistence; dest collapse on move into
a bank already holding the hash; JSON lossless; legacy migration.
## B2 — persist slice (banks ↔ project ext state)
**Goal:** Serialize the book under the `banks` key in `"reasampler"` alongside the
existing sections; migrate a legacy `bank_index` key into the pool on first load;
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.
**Depends on:** B1. Resolve the `bank_index` retirement-vs-retention fork first
(see Phase B open questions).
- [ ] Serialize/deserialize the book under the `banks` key (shared blob, distinct
section from `bank_index` / `view_state`).
- [ ] Legacy-migration path on load: absent `banks` + present `bank_index` → pool.
- [ ] 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 MIDI-bindable; create/rename/delete drive the B1 model via
the B2-persisted session.
**Depends on:** B1, B2.
- [ ] Create bank / rename bank / delete bank (delete honors the resolved
member-disposition rule — see open questions).
- [ ] Activate bank (direct-by-id + cycle).
- [ ] Move selected samples → bank / copy selected samples → bank.
- [ ] Pool full-height / banks full-height toggles.
- [ ] Register each (`command_id`/`gaccel`/`hookcommand`); bank-activate + move/copy
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 indicated;
both full-height toggles collapse the split correctly; sample move/copy affordance
works (drag and/or menu); the Design View mode switch in the header is unaffected.
**Depends on:** B1, B2, B3.
- [ ] Vertical split: pool grid region (top) + named-banks tab-page region (bottom).
- [ ] Named-banks tab strip: one tab per named bank; empty state when none.
- [ ] Pool full-height / banks full-height toggle affordances wired to B3.
- [ ] Active-bank indicator.
- [ ] Create / rename / delete / activate affordances driving B3 actions.
- [ ] Sample move/copy affordance (drag between regions and/or "send to bank" menu).
## Phase B open questions
- **`bank_index` key retirement vs. retention** — (a) fold the pool into the `banks`
blob and retire the legacy key, vs (b) keep `bank_index` as the pool's canonical
slot and store only named banks under `banks`. Leaning (b) for additive-minimalism
against in-flight M7/M8 persist work. Resolve before B2. (blocks B2)
- **Named-bank delete → member disposition** — (i) reabsorb into pool, (ii)
orphan-check by file reference, (iii) forbid non-empty delete. Leaning (i).
(touches B1/B3)
- **Move affordance + tab rendering** — drag vs. menu as primary; SWELL-native vs.
LICE-drawn tabs. Verify SWELL tab-control availability against the M5 reference.
(touches B4)
- **Active-bank indicator placement** — per-region headers vs. single header
readout. (touches B4)