Scope sample-remove (B5) and prune (Phase R) into the plan

Adds the two missing removal capabilities: sample-level remove (Phase B
point B5, index-only) and the prune file-lifecycle path (new Phase R, the
sole file-deleting op), resolving the dangling capture/prune reference.
Adds CONTEXT.md spec, PLAN.md points, and a product note; surfaces forks
R-A..R-E for decision.
This commit is contained in:
2026-07-23 20:21:50 -04:00
parent b9046b2842
commit b931dada0a
3 changed files with 776 additions and 10 deletions
+148 -10
View File
@@ -118,23 +118,34 @@ landed milestone.
> spec. Product framing: `docs/product/design-view.md` §Two-canvas direction. When a
> point lands, doc-keeper moves it to `COMPLETED.md`.
>
> **D2-W1 (pure lane extension) and D2-W2 (shell: lane application + new-content detection) have landed** — see `COMPLETED.md`.
> **D2-W1 (pure lane extension), D2-W2 (shell: lane application + new-content
> detection), and D2-W3-A (lane minting + item→lane assignment + persist
> round-trip) have landed** — see `COMPLETED.md`.
## D2-W3 — actions, persist wiring, panel UI
**Goal:** Any new lane/mode-management actions, the persist slice serializing the
lane-ownership index alongside the membership index, and any panel UI indicator.
## D2-W3-Btwo-canvas actions + panel UI indicators
**Goal:** Any new lane/mode-management actions (bindable in the Actions list), and
any panel UI indicator for lane/mode state. The persist slice and the
lane-ownership index round-trip were completed in D2-W3-A; this wave closes the
remaining UI and action surface.
See CONTEXT.md §Two-canvas sub-phase (Module architecture — persistence).
**Verify (in DAW):** The lane-ownership index survives Save / Save As / reopen
(rides in the `"reasampler"` `view_state` alongside the membership index);
lane/mode-management actions are registered and bindable.
**Depends on:** D2-W2.
**Verify (in DAW):** Lane/mode-management actions are registered and bindable in
the Actions list; the panel UI indicator reflects the current lane/mode state.
**Depends on:** D2-W3-A.
- [ ] Any new lane/mode-management actions (`command_id`/`gaccel`/`hookcommand`);
bindable in the Actions list.
- [ ] Persist slice: serialize/deserialize the lane-ownership index in the
`"reasampler"` `view_state` section, alongside the membership index.
- [ ] Any panel UI indicator for lane/mode state.
**D2-W3-A code-review polish (fold into this wave):**
- [ ] Simplify the redundant per-mint `I_NUMFIXEDLANES` re-read in `view.cpp`
(`applyMintPlan`): a single grow-and-track pass removes the second
`GetMediaTrackInfo_Value` call inside the mint loop.
- [ ] Optional shared item-read helper to remove duplicated `itemGuid`/`itemLaneName`
read logic between `view.cpp` and `bank_panel.cpp`.
- [ ] Optional defensive note/check in `reconcileManagedLanes` for the edge case
where a managed lane name encodes an unregistered mode id (log and skip rather
than silently record an orphaned ownership entry).
---
# Phase B — Multi-bank (parallel to the M0M11 capture roadmap and Phase D)
@@ -254,6 +265,34 @@ and product notes → *Fork 5 — settled*.)
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 — default
this-bank until Daniel settles).
- [ ] `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
@@ -265,3 +304,102 @@ 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 (NEW, unsettled — need Daniel):** **R-A** — remove scope
(this-bank | all-banks | both); spec-of-record is this-bank-primary with a
scope-parameter seam. **R-B** — undo model for `"reasampler"` ext-state index
mutations (affects *all* of Phase B, surfaced by remove; move/copy/evacuate/
delete-bank/remove are not on REAPER Ctrl-Z as specced). Lean: a single-snapshot
ReaSampler-internal "undo last bank change." Both in `docs/product/removal-and-prune.md`.
---
# Phase R — Reclaim (file lifecycle: the prune path)
> **New pillar, own lettered namespace.** Prune is the file-lifecycle path the
> capture and multi-bank specs forward-reference throughout ("files persist on disk
> until prune") but that had no phase, module, or point. It is the **only** operation
> in ReaSampler that deletes bytes off disk. Namespaced **`R` (Reclaim)** alongside
> `M`/`D`/`B` because it is a distinct pillar — it serves *every* orphan-producing
> path (delete-bank, sample-remove B5, potentially M10 re-capture), not just
> Multi-bank, and it carries a new risk class (file deletion) with its own
> invariants. Authoritative spec: **CONTEXT.md §Prune — file-lifecycle spec**.
> Product framing + phase-placement justification + forks:
> `docs/product/removal-and-prune.md` §Prune. When a point lands, doc-keeper moves it
> to `COMPLETED.md`.
>
> **Boundary (load-bearing):** *remove creates orphans; prune reclaims them.* No
> operation other than prune deletes a file; prune deletes only files no index
> references. A bank op that deletes a file is still a bug.
>
> **Depends on:** B1, B2 (needs the multi-bank book to union the referenced-set
> across all banks) and B5 conceptually (sample-remove is a primary orphan-producer,
> so remove-then-prune is the coherent pair — mirror of evacuate-then-delete). Does
> **not** depend on the B3/B4 UI.
## R1 — prune-reconcile core (pure)
**Goal:** REAPER-free, filesystem-free reconciler — given the set of files present
in the bank folder and the set of files referenced by the book (unioned across all
banks, pool included), compute the orphan set. The mirror of
`ViewModeModel::reconcile(liveGuids)`, one level down (files instead of GUIDs).
CONTEXT.md §Prune (Module architecture — pure).
**Verify:** CTest green. **Prune null test:** a folder whose every file is
referenced deletes nothing; prune returns exactly `present referenced` and
nothing else. Referenced-set unioned across every bank (a file referenced by any
bank — including via a copy — is never an orphan).
- [ ] Prune-reconcile pure function: `(present, referenced) → orphans`, referenced
unioned across the whole book (copies keep a file alive).
- [ ] Tests: prune null test (all-referenced → empty); orphan = presentreferenced;
a copied file referenced by a second bank survives; empty folder / empty book edge
cases.
## R2 — prune shell + persist wiring (filesystem I/O, thin)
**Goal:** Enumerate the current project bank folder (M4 project-relative resolution),
supply the referenced-set (and, per fork R-D, the owned-file set) from the session,
feed the pure core, and produce a dry-run manifest. No deletion in this wave — the
report path only. CONTEXT.md §Prune (persist / prune shell).
**Verify (in DAW):** Dry-run reports the orphan count + reclaimed size (+ file list
for a small set) against the resolved current bank folder; resolves paths the same
way the index does (survives a Save-As relocation); deletes nothing.
**Depends on:** R1, B1, B2.
- [ ] Prune shell: enumerate the resolved current bank folder; feed the pure core.
- [ ] Session supplies the referenced-set (union across the book); resolve the bank
folder via the M4 project-relative machinery.
- [ ] Dry-run manifest: orphan count + reclaimed size (+ files for a small set);
**no deletion in this wave.**
## R3 — deletion + action (the destructive step, guarded)
**Goal:** The confirmed deletion step and the bindable "Prune bank folder" action:
dry-run-first, confirm-with-manifest, then reclaim the orphan set — via OS trash if
portably available (fork R-C), else unlink. CONTEXT.md §Prune (guardrails, API).
**Verify (in DAW):** "Prune bank folder" reports first, deletes only on explicit
confirm, and reclaims exactly the orphan set — never a referenced file, never a
hand-dropped non-bank file; the referenced/owned-set safety holds; non-bank and
capture invariants untouched.
**Depends on:** R2. **Forks R-C / R-D / R-E must be settled before this wave.**
- [ ] "Prune bank folder" action (`command_id`/`gaccel`/`hookcommand`),
dry-run-first, confirm-to-delete.
- [ ] Deletion mechanism (fork R-C): OS trash if a portable move-to-trash is
verified available, else unlink with the dry-run/confirm guardrail. **Verify the
platform move-to-trash surface before use.**
- [ ] Orphan attribution (fork R-D): reclaim only the bank system's own leavings,
not hand-dropped folder files (owned-file manifest per the lean — see below).
- [ ] Trigger (fork R-E): manual-primary; optional "…and prune now" offered at the
delete-bank confirmation; **no** background sweep.
## Phase R open questions (unsettled — need Daniel)
- **Fork R-C — deletion mechanism (OS trash vs. unlink).** Lean: trash if portably
available (recoverable), else unlink with strong dry-run/confirm. Needs Daniel +
a platform to-verify (SWELL / per-platform trash APIs).
- **Fork R-D — orphan attribution (owned-file manifest vs. index-diff vs.
folder-sweep).** Lean: **owned-file manifest**. **Design-the-seam call:** the
manifest is cheap to maintain from capture onward but a backfill cliff to
reconstruct later — so **start tracking owned files as part of capture / Phase B
now, even though prune ships in Phase R.** Folder-sweep rejected as unsafe (deletes
hand-dropped files). Needs Daniel (touches the persisted shape + ideally lands
earlier than R3).
- **Fork R-E — trigger (manual-only vs. offer-on-orphaning vs. periodic).** Lean:
manual-primary + optional delete-time "prune now"; no background sweep. Needs Daniel.