docs: archive Phase R (R1-R3) to COMPLETED; reconcile CLAUDE.md + CONTEXT.md

This commit is contained in:
2026-07-26 19:27:03 -04:00
parent daa4338218
commit 813c9f5cc6
4 changed files with 162 additions and 112 deletions
+7 -3
View File
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Repo identity and current state
The CMake project and binary are now named `reaper_reasampler`. This is **ReaSampler** — a per-project audio sample-bank capture tool. The MPE modules (`mpe_model`, `mpe_view`) have been removed. M0M8 are complete (bank_model, peaks, capture offline+realtime, persist, bank_panel, insert, capture action family, RealtimeRecordBackend, tail T1+T2+T1-followons). Phase B multi-bank (B1B5, B-cap), Phase D1/D2 Design View (D1D5, D2-W1W3-B), Phase V versioning/beta-channel (V1/V3, V4), and M10 provenance + re-capture from source have all landed. M9 slots, M11 polish, and Phase R prune remain. The *discipline* — pure REAPER-free testable core split from REAPER-facing shells — is **preserved** throughout.
The CMake project and binary are now named `reaper_reasampler`. This is **ReaSampler** — a per-project audio sample-bank capture tool. The MPE modules (`mpe_model`, `mpe_view`) have been removed. M0M8 are complete (bank_model, peaks, capture offline+realtime, persist, bank_panel, insert, capture action family, RealtimeRecordBackend, tail T1+T2+T1-followons). Phase B multi-bank (B1B5, B-cap), Phase D1/D2 Design View (D1D5, D2-W1W3-B), Phase V versioning/beta-channel (V1/V3, V4), M10 provenance + re-capture from source, and Phase R Reclaim (R1R3: prune-reconcile core, dry-run shell, guarded deletion + action + panel button) have all landed. M9 slots and M11 polish remain. The *discipline* — pure REAPER-free testable core split from REAPER-facing shells — is **preserved** throughout.
CONTEXT.md is the authoritative spec and build roadmap. Read it first for any non-trivial task. Every REAPER API name cited there is correct-by-intent; verify argument order, types, and flag values against `vendor/reaper-sdk/sdk/reaper_plugin_functions.h` before use.
@@ -37,6 +37,8 @@ Key targets (see CMakeLists.txt for the full list):
| `owned_manifest_tests` | executable | Pure unit tests for `owned_manifest` — no REAPER, no DAW. |
| `app_version_tests` | executable | Pure unit tests for `app_version` — no REAPER, no DAW. |
| `provenance_tests` | executable | Pure unit tests for `provenance` — no REAPER, no DAW. |
| `prune_reconcile_tests` | executable | Pure unit tests for `prune_reconcile` — no REAPER, no DAW. |
| `prune_button_tests` | executable | Pure unit tests for `prune_button` — no REAPER, no DAW. |
| `reaper_reasampler` | loadable module | The actual extension binary (`.dll` / `.dylib` / `.so`). |
### Beta channel build (Phase V, V4)
@@ -73,16 +75,18 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde
- `app_version` — REAPER-free version/channel identity (Phase V, V1+V4): CMake-sourced semver constant (`appVersion()`), ext-state stamp value (`stampVersion()` — numeric triple only, no channel suffix), `parseVersion`/`versionLess`/`classifyWritingVersion`, and the full set of channel-derived identity accessors (`extStateNamespace()`, `commandIdPrefix()`, `actionDisplayPrefix()`, `binaryName()`, `dockTitle()`, `dockIdent()`, `channelCommandId()`, `channelActionName()`). All channel strings derive from the one `REASAMPLER_CHANNEL_IS_BETA` bit threaded in via `configure_file``version_generated.h`; no scattered `#ifdef`s in the shells.
- `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.
- `provenance` — capture-recipe fingerprint (M10): build/encode/compare a `rsprov1` length-prefixed fingerprint of scope, exact range, tail, rate/channels, track GUIDs, and order-sensitive FX-chain identity; parse/compare for drift detection on re-capture. A thin reproducibility fingerprint — NOT a serialized chain to restore. Drives `BankIndex::updateInPlace` / `BankBook::updateSampleInPlace` (order-preserving, id-stable) on re-capture.
- `prune_reconcile` — Phase R pure prune core: `pruneOrphans(present, referenced, owned)` computes `(owned ∩ present) referenced` (exact-string path match); `buildPruneReport` tallies count/bytes/display-capped file list; `pruneDeletePlan` produces the confirm-time staleness intersection (`confirmed ∩ freshOrphans`). REAPER-free, filesystem-free. The safety-critical "which files are orphans" decision — hard-tested here before any I/O exists. Also: `BankBook::referencedPaths()` additive const union query (all banks incl. pool, de-duped) added to `bank_book`.
- `prune_button` — Phase R pure layout/hit-test for the `bank_panel` footer Prune button: `computePruneButton` (right-anchored, suppressed gracefully when footer is too narrow) + `hitTestPruneButton`. Mirror of `mode_switch` / `tab_strip`.
**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"`) ↔ `BankBook` JSON (`"banks"` key) + `ViewModeModel` JSON (`"view_state"` key) + `TailSetting` JSON (`"tail_setting"` key) + `OwnedManifest` JSON (`"owned_files"` key) + writing-version stamp (`"version"` key, written via `stampVersion()` on every `saveToActiveProject()`); 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.
- `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) + writing-version stamp (`"version"` key, written via `stampVersion()` on every `saveToActiveProject()`); 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. Hosts `ReaSamplerSession::pruneDryRun()` (read-only orphan enumeration via M4 project-relative resolution) and `pruneOrphanSet()` (full-set query for the R3 delete path); supplies `referencedPaths()` + `owned().paths()` to the `prune_reconcile` pure core.
- `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.
- `provenance_shell` — FX-chain identity queries via `TrackFX_*` / `TakeFX_*` APIs; collects source-item paths and parent-detection inputs to feed the pure `provenance` fingerprint builder. Stamps `Sample.provenance` on capture when every resolving source item maps by exact normalized path (case-folded on Windows) to exactly one bank sample; ambiguous/mixed cases record nothing conservatively.
- `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.
- `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), the multi-bank action family (create/rename/reorder/delete bank, evacuate, activate, move/copy/remove selected samples), and the Phase R prune action (`BANK_PRUNE_FOLDER` — dry-run-first, confirm-with-manifest, then `pruneDeletePlan`-guarded deletion; **the ONLY file-deletion authority in the system**); 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. The prune action writes no ext state and opens no undo point (file deletion is not REAPER-undoable).
## REAPER extension contract (src/main.cpp)
+149
View File
@@ -1034,3 +1034,152 @@ 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.
---
# 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.
>
> **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 files present in the
bank folder, the files referenced by the book (unioned across all banks, pool
included), and the **owned-file manifest** (fork R-D, written from capture onward by
B-cap), compute the orphan set `(owned ∩ present) referenced`. 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 `(owned ∩ 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); a present-but-not-owned file (a
hand-dropped file) is never an orphan.
- [x] Prune-reconcile pure function: `(present, referenced, owned) → orphans`,
computing `(owned ∩ present) referenced`; referenced unioned across the whole
book (copies keep a file alive).
- [x] Tests: prune null test (all-referenced → empty); orphan = (owned∩present)
referenced; a copied file referenced by a second bank survives; a present-but-
unowned (hand-dropped) file is never reclaimed; empty folder / empty book / empty
manifest edge cases.
**Notes/decisions:**
- New pure module `src/prune_reconcile.{h,cpp}`: exports `pruneOrphans(present,
referenced, owned)` (the safety-critical set algebra), `buildPruneReport`
(count/bytes/display-capped list, unit-testable), and `pruneDeletePlan` (the R3
confirm-time staleness intersection — `confirmed ∩ freshOrphans` in confirm order).
Exact-string path match throughout (no case-folding, no separator normalization).
`BankBook::referencedPaths()` additive const union query (all banks incl. pool,
de-duped) added to `bank_book`. New `prune_reconcile_tests` CTest target.
---
## 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 the **owned-file manifest** (from B-cap) 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.
- [x] Prune shell: enumerate the resolved current bank folder; feed the pure core.
- [x] Session supplies the referenced-set (union across the book) **and the
owned-file manifest** (written by B-cap); resolve the bank folder via the M4
project-relative machinery.
- [x] Dry-run manifest: orphan count + reclaimed size (+ files for a small set);
**no deletion in this wave.**
**Notes/decisions:**
- `ReaSamplerSession::pruneDryRun()` (read-only, non-throwing) enumerates the
resolved current bank folder, unioning `book().referencedPaths()` and
`owned().paths()`, feeds `pruneOrphans`, and calls `buildPruneReport` with a 64-file
display cap. Pure `bankRelativeForName` (`capture_paths`) normalizes the folder-
enumeration spelling to match the index convention so the pure core's exact-string
match lines up. Forever-stable `BANK_PRUNE_FOLDER` action registered (dry-run report
to console in R2; deletion wired in R3 behind the same action id).
---
## R3 — deletion + action (the destructive step, guarded)
**Goal:** The confirmed deletion step, the bindable "Prune bank folder" action, and
a `bank_panel` prune button: dry-run-first, confirm-with-manifest, then reclaim the
orphan set — via OS trash where portably available (fork R-C), else unlink.
CONTEXT.md §Prune (guardrails, API).
**Verify (in DAW):** "Prune bank folder" (action or panel button) 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; deletions route to OS trash where available; non-bank and capture invariants
untouched.
**Depends on:** R2 (and B-cap's owned-file manifest). All forks settled 2026-07-24.
- [x] "Prune bank folder" action (`command_id`/`gaccel`/`hookcommand`),
dry-run-first, confirm-to-delete.
- [x] `bank_panel` prune button (fork R-E) that fires the "Prune bank folder"
action through the existing command-id contract — the panel affordance alongside
the bindable action; split: button hit-test/layout is pure (mirror of
`mode_switch`/`bank_grid`), draw + dispatch is bank_panel shell.
- [x] Deletion mechanism (fork R-C, settled trash-preferred): route to OS trash
where a portable move-to-trash is verified available, else unlink behind the
dry-run/confirm guardrail.
- [x] Orphan attribution (fork R-D, settled owned-file manifest): reclaim only
`(owned ∩ present) referenced` — the bank system's own leavings, never a
hand-dropped folder file. (Manifest written by B-cap; consumed via R1/R2.)
**Notes/decisions:**
- Deletion is guarded: dry-run → REAPER `ShowMessageBox` confirm (count+bytes+files)
→ `pruneDeletePlan` staleness intersection (confirmed ∩ fresh pure-core output) →
delete exactly the plan. Zero ext-state writes, no undo point (file deletion is not
REAPER-undoable by design).
- **Windows:** routes to Recycle Bin via `SHFileOperationW` + `FOF_ALLOWUNDO`
(verified against SDK 10.0.26100). **macOS / Linux:** no portable SWELL trash
surface; falls back to `unlink` behind the dry-run/confirm guardrail.
- Manifest entries are deliberately NOT removed on deletion (the owned-file manifest
algebra self-cleans: a deleted file will drop from `present` on the next prune scan,
and `pruneOrphans` returns `(owned ∩ present) referenced` — the absent file
contributes nothing regardless).
- New pure module `src/prune_button.{h,cpp}`: layout (`computePruneButton`) and
hit-test (`hitTestPruneButton`) for the footer prune button, right-anchored,
suppressed gracefully when the footer is too narrow. Mirror of `mode_switch` /
`tab_strip`. New `prune_button_tests` CTest target.
- `bank_panel` footer button dispatches `BANK_PRUNE_FOLDER` via `Main_OnCommand`
through the registered command id (the same action as the bindable menu entry —
no duplicate logic).
## Phase R forks — settled 2026-07-24
- **Fork R-C — deletion mechanism.** Settled: **trash-preferred, unlink fallback.**
Route to OS trash where a portable move-to-trash is available (recoverable), else
unlink behind strong dry-run/confirm. Per-platform trash surface verified at build:
Windows `SHFileOperationW` + `FOF_ALLOWUNDO` (SDK 10.0.26100); macOS/Linux no
portable SWELL trash surface → unlink fallback. Folded into R3.
- **Fork R-D — orphan attribution.** Settled: **owned-file manifest**,
`(owned ∩ present) referenced`; folder-sweep rejected as unsafe. **Seam lands
early** — the manifest is written from capture onward (new **B-cap** point in
Phase B), not reconstructed at prune time; R1/R2 consume it. Persistence shape
(sibling `"reasampler"` key vs. `banks` blob) resolved at build time: sibling
`"owned_files"` key.
- **Fork R-E — trigger.** Settled: **manual action + `bank_panel` button**,
dry-run-first, confirm-to-delete. No background sweep. The earlier optional
delete-time "…and prune now" convenience was **not** selected — out of scope.
Folded into R3.
Both docs of record: `docs/product/removal-and-prune.md` §Fork R-C/R-D/R-E and
CONTEXT.md §Prune (Settled forks).
+6 -1
View File
@@ -958,7 +958,9 @@ pure/shell split as `bank_model` / `view_mode_model` / `bank_book`.
back to unlink — behind the dry-run + confirm guardrail — only where the platform
affords no portable trash. "Delete where possible" means recoverable-trash-
preferred, never plain unlink-by-default. The move-to-trash surface is an explicit
per-platform **to-verify** (see REAPER/platform API surface).
per-platform **to-verify** (see REAPER/platform API surface). (R3 verified: Windows
`SHFileOperationW` + `FOF_ALLOWUNDO` confirmed against SDK 10.0.26100; macOS/Linux
unlink fallback — no portable SWELL trash surface.)
- **Manual, explicit trigger (fork R-E, SETTLED 2026-07-24 — manual action + panel
button).** Prune runs via a bindable manual action (dry-run-first, confirm-to-
delete) **and** a `bank_panel` button that fires that same action — never a silent
@@ -1000,6 +1002,9 @@ No new REAPER *audio* API. New surfaces to verify before use:
`SHFileOperation`, macOS `NSFileManager trashItemAtURL:`, Linux XDG trash spec).
This is a **must-verify per platform** before use, not an assumed capability;
where it is unavailable, fall back to unlink behind the dry-run/confirm guardrail.
(R3 verified: Windows routes to Recycle Bin via `SHFileOperationW` + `FOF_ALLOWUNDO`,
verified against SDK 10.0.26100. macOS / Linux: no portable SWELL trash surface
found — fall back to `unlink` behind the dry-run/confirm guardrail, as specified.)
- **Owned-file manifest persistence (fork R-D, settled)** — a new tracked set in
the `"reasampler"` ext-state (a sibling key or folded into the `banks` blob —
build-time residual); shared M4 blob machinery, new data only. **Written from
-108
View File
@@ -88,114 +88,6 @@ landed milestone.
---
# 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 files present in the
bank folder, the files referenced by the book (unioned across all banks, pool
included), and the **owned-file manifest** (fork R-D, written from capture onward by
B-cap), compute the orphan set `(owned ∩ present) referenced`. 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 `(owned ∩ 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); a present-but-not-owned file (a
hand-dropped file) is never an orphan.
- [ ] Prune-reconcile pure function: `(present, referenced, owned) → orphans`,
computing `(owned ∩ present) referenced`; referenced unioned across the whole
book (copies keep a file alive).
- [ ] Tests: prune null test (all-referenced → empty); orphan = (owned∩present)
referenced; a copied file referenced by a second bank survives; a present-but-
unowned (hand-dropped) file is never reclaimed; empty folder / empty book / empty
manifest 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 the **owned-file manifest** (from B-cap) 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) **and the
owned-file manifest** (written by B-cap); 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, the bindable "Prune bank folder" action, and
a `bank_panel` prune button: dry-run-first, confirm-with-manifest, then reclaim the
orphan set — via OS trash where portably available (fork R-C), else unlink.
CONTEXT.md §Prune (guardrails, API).
**Verify (in DAW):** "Prune bank folder" (action or panel button) 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; deletions route to OS trash where available; non-bank and capture invariants
untouched.
**Depends on:** R2 (and B-cap's owned-file manifest). All forks settled 2026-07-24.
- [ ] "Prune bank folder" action (`command_id`/`gaccel`/`hookcommand`),
dry-run-first, confirm-to-delete.
- [ ] `bank_panel` prune button (fork R-E) that fires the "Prune bank folder"
action through the existing command-id contract — the panel affordance alongside
the bindable action; split: button hit-test/layout is pure (mirror of
`mode_switch`/`bank_grid`), draw + dispatch is bank_panel shell.
- [ ] Deletion mechanism (fork R-C, settled trash-preferred): route to OS trash
where a portable move-to-trash is verified available, else unlink behind the
dry-run/confirm guardrail. **Verify the platform move-to-trash surface before use
(per platform).**
- [ ] Orphan attribution (fork R-D, settled owned-file manifest): reclaim only
`(owned ∩ present) referenced` — the bank system's own leavings, never a
hand-dropped folder file. (Manifest written by B-cap; consumed via R1/R2.)
## Phase R forks — settled 2026-07-24
- **Fork R-C — deletion mechanism.** Settled: **trash-preferred, unlink fallback.**
Route to OS trash where a portable move-to-trash is available (recoverable), else
unlink behind strong dry-run/confirm. Per-platform trash surface (SWELL / Win
`SHFileOperation`·`IFileOperation` / macOS `trashItemAtURL:` / Linux XDG) is a
**must-verify before use**. Folded into R3.
- **Fork R-D — orphan attribution.** Settled: **owned-file manifest**,
`(owned ∩ present) referenced`; folder-sweep rejected as unsafe. **Seam lands
early** — the manifest is written from capture onward (new **B-cap** point in
Phase B), not reconstructed at prune time; R1/R2 consume it. Persistence shape
(sibling `"reasampler"` key vs. `banks` blob) is a small build-time residual.
- **Fork R-E — trigger.** Settled: **manual action + `bank_panel` button**,
dry-run-first, confirm-to-delete. No background sweep. The earlier optional
delete-time "…and prune now" convenience was **not** selected — out of scope.
Folded into R3.
Both docs of record: `docs/product/removal-and-prune.md` §Fork R-C/R-D/R-E and
CONTEXT.md §Prune (Settled forks).
---
# Phase S — MIDI-playback instrument (native VST3 sampler; a second build artifact)
> **New pillar, own lettered namespace, and — uniquely — a second build artifact.**