docs: settle removal/prune forks R-A..R-E; add owned-file manifest seam

R-A this-bank; R-B batched REAPER undo points (Phase-B-wide, SDK must-verify);
R-C trash-preferred w/ unlink fallback; R-D owned-file manifest, landed early as
new B-cap capture point; R-E manual action + bank_panel button.
This commit is contained in:
2026-07-24 04:59:28 -04:00
parent 960c5166ee
commit 878d807b12
3 changed files with 296 additions and 206 deletions
+93 -63
View File
@@ -771,13 +771,12 @@ model capability.
container. Individual samples **can** be removed from the pool — otherwise the
pool would be a one-way trap. Remove-from-pool is the pool's own "drop this
sample" verb and is allowed.
- **Remove scope (fork R-A, OPEN — needs Daniel).** Whether "remove" drops the
entry from *this* bank only (leaving copies elsewhere), purges the sample from
*every* bank, or offers both. Spec-of-record until settled: **remove-from-this-
bank** is the core verb (it is the model primitive and composes into
from-everywhere); design the action to carry a `scope: this-bank | all-banks`
parameter so from-everywhere is a later flag, not a rewrite. See product notes
§Fork R-A.
- **Remove scope (fork R-A, SETTLED 2026-07-24 — this-bank).** Remove drops the
entry from *this* bank only, leaving copies in other banks untouched — the core
and only shipped verb. The action carries a `scope: this-bank | all-banks` seam,
but **this-bank is the settled default and the only surfaced affordance**;
all-banks stays a latent parameter (promotable later behind the seam without a
rewrite), never a surfaced verb now. See product notes §Fork R-A.
## Precision / invariant implications
@@ -796,12 +795,18 @@ model capability.
remove of a sample still referenced by another bank is cheap and re-derivable
(re-copy it back) and needs no confirmation. The confirmation is *earned by
actual orphan risk*, not fired on every remove.
- **Undo (fork R-B, OPEN — shared across all of Phase B).** REAPER's undo stack
does not cover `"reasampler"` ext-state mutations, so remove — like
move/copy/evacuate/delete-bank — is not on REAPER's Ctrl-Z as specced. The undo
model is a Phase-B-wide decision, not a remove-local one; see product notes
§Fork R-B. Surfaced here because remove is the first verb whose *only* effect is
index-entry destruction with no relocation, so it is where the gap first bites.
- **Undo (fork R-B, SETTLED 2026-07-24 — batched REAPER undo points, Phase-B-wide).**
Bank/index mutations integrate into REAPER's undo system as **batched undo points**
(`Undo_BeginBlock` / `Undo_EndBlock`): the related index mutations of one bank
operation are batched into a single undo point, so one bank operation is one
Ctrl-Z. This is a **Phase-B-wide** decision — it applies to
create/rename/reorder/delete-bank, move, copy, evacuate, *and* remove, retro-
touching B1B4, not just B5. **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. Surfaced with remove because remove is the first verb whose *only* effect is
index-entry destruction with no relocation, so it is where the gap first bit; the
fix is shared. See product notes §Fork R-B.
## Module architecture (preserve the pure/shell split)
@@ -821,13 +826,14 @@ No new REAPER API. Pure model + a new action command-id string under the sampler
family prefix + a panel affordance on the existing M5 LICE surface. Verify the
command-id/gaccel/hookcommand usage against `main.cpp` (unchanged contract).
## Open questions
## Settled forks (Daniel, 2026-07-24)
- **Fork R-A — remove scope (this-bank | all-banks | both).** Open; needs Daniel.
Spec-of-record: this-bank primary with a scope-parameter seam. Product notes
§Fork R-A.
- **Fork R-B — undo model for index mutations.** Open; a Phase-B-wide decision
surfaced by remove. Product notes §Fork R-B.
- **Fork R-A — remove scope.** Settled: **this-bank** (this-bank-primary, all-banks
a latent seam-only parameter). Folded into Settled decisions above.
- **Fork R-B — undo model for index mutations.** Settled: **batched REAPER undo
points** (`Undo_BeginBlock`/`Undo_EndBlock`), Phase-B-wide (retro-touches B1B4),
with the ext-state-participation SDK check as a must-verify-before-build. Folded
into Guardrails above and the Phase B / B1 plan points.
---
@@ -902,9 +908,19 @@ pure/shell split as `bank_model` / `view_mode_model` / `bank_book`.
irreversible file-delete.
- **Scope is the bank system's own leavings, not the folder at large.** Prune
reclaims files that *were* bank files and are now unreferenced — never a file a
user hand-dropped into the folder. How prune attributes ownership is fork R-D
(below). Prune is a reclaimer of ReaSampler's own orphans, not a general folder
cleaner.
user hand-dropped into the folder. Prune is a reclaimer of ReaSampler's own
orphans, not a general folder cleaner.
- **Orphan attribution is an owned-file manifest (fork R-D, SETTLED 2026-07-24).**
The book tracks the set of files it has created (an **owned-file manifest**);
prune reclaims `(owned ∩ on-disk) referenced`. This is the honest encoding of
"reclaim only our own leavings" and rejects folder-sweep (which would delete
hand-dropped files). **The seam lands early:** because the manifest is cheap to
maintain from capture onward but a backfill cliff to reconstruct later, **capture
writes each file it creates into the owned-file manifest starting in Phase B**,
even though prune consumes it only in Phase R. R1/R2 consume the manifest; they do
not build it. The manifest is persisted in the `"reasampler"` ext-state; the exact
persistence shape (a sibling key vs. folded into the `banks` blob) is a small
build-time residual, but the manifest-now decision is firm.
## Precision / invariant implications
@@ -928,35 +944,41 @@ pure/shell split as `bank_model` / `view_mode_model` / `bank_book`.
- **Never a referenced file; never a non-bank file.** The union-across-all-banks
rule protects referenced files; the ownership-attribution rule (fork R-D)
protects hand-dropped files.
- **Safest platform deletion available (fork R-C, OPEN — needs Daniel + verify).**
Route deletions to the platform recycle bin / trash if a portable move-to-trash
is available (recoverable outside the app); fall back to unlink with the dry-run +
confirm guardrail if not. Default to the safest deletion the platform affords.
- **Manual, explicit trigger (fork R-E, OPEN).** Prune is a deliberate action the
user invokes, dry-run-first — never a silent background sweep. An optional "…and
prune now" convenience may be offered at the delete-bank confirmation; a periodic
background sweep is rejected (silent irreversible file-deletion violates the
- **Safest platform deletion available (fork R-C, SETTLED 2026-07-24 — trash-
preferred, unlink fallback).** Route deletions to the platform recycle bin / trash
wherever a portable move-to-trash is available (recoverable outside the app); fall
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).
- **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
background sweep. The earlier optional "…and prune now at the delete-bank
confirmation" convenience was **not** selected and is out of scope; a periodic
background sweep remains rejected (silent irreversible file-deletion violates the
guardrails).
## Module architecture (preserve the pure/shell split)
Pure (no REAPER types, unit-tested — the mirror of `reconcile`):
- **Prune-reconcile core** — given `{ files present in the bank folder }` and
`{ files referenced by the book }` (and, per fork R-D, `{ files the book owns }`),
compute the orphan set. REAPER-free, filesystem-free, unit-tested hard (the prune
null test lives here). The referenced-set is unioned across all banks by asking
the `bank_book`.
- **Prune-reconcile core** — given `{ files present in the bank folder }`,
`{ files referenced by the book }`, and `{ files the book owns }` (the owned-file
manifest, R-D), compute the orphan set `(owned ∩ present) referenced`.
REAPER-free, filesystem-free, unit-tested hard (the prune null test lives here).
The referenced-set is unioned across all banks by asking the `bank_book`.
REAPER-facing / filesystem-facing (thin):
- `persist` / session — supplies the referenced-set (union across the book) and,
per fork R-D, the owned-file manifest; resolves the current project bank folder
via the M4 project-relative machinery.
- `persist` / session — supplies the referenced-set (union across the book) and the
owned-file manifest (R-D, written from capture onward in Phase B); resolves the
current project bank folder via the M4 project-relative machinery.
- A **prune shell** — enumerates the bank folder (filesystem I/O), feeds the
pure core, presents the dry-run manifest, and on confirmation deletes the orphan
set (via trash if available — fork R-C — else unlink). Filesystem I/O only; the
decision stays in the pure core.
- `actions` (entry) — "Prune bank folder" (dry-run-first, confirm-to-delete);
registered with the `command_id`/`gaccel`/`hookcommand` contract.
set (via OS trash where portably available — fork R-C — else unlink). Filesystem
I/O only; the decision stays in the pure core.
- `actions` (entry) — "Prune bank folder" (dry-run-first, confirm-to-delete),
registered with the `command_id`/`gaccel`/`hookcommand` contract; **plus a
`bank_panel` button** (R-E) that fires the same action.
## REAPER / platform API surface (verify all signatures)
@@ -965,13 +987,15 @@ No new REAPER *audio* API. New surfaces to verify before use:
project bank folder. **Verify** the portable approach against SWELL / the existing
file-handling in `persist` / `capture` (which already resolve and write files);
prefer reusing whatever path/file machinery M4 established.
- **Move-to-trash (fork R-C)** — if adopted, verify a portable move-to-trash
exists (SWELL, or per-platform: Win `IFileOperation`/`SHFileOperation`, macOS
`NSFileManager trashItemAtURL:`, Linux XDG trash spec). This is a **to-verify**,
not an assumed capability.
- **Persistence (fork R-D, if owned-file manifest adopted)** — a new tracked set in
the `"reasampler"` ext-state (or folded into the `banks` blob); shared M4 blob
machinery, new data only.
- **Move-to-trash (fork R-C, settled trash-preferred)** — verify a portable
move-to-trash exists (SWELL, or per-platform: Win `IFileOperation`/
`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.
- **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
capture onward in Phase B** (the seam lands early), consumed by prune in Phase R.
- **Actions** — the `command_id`/`gaccel`/`hookcommand` contract from `main.cpp`
(unchanged), a new command-id string under the sampler family prefix.
@@ -990,18 +1014,24 @@ No new REAPER *audio* API. New surfaces to verify before use:
- **Additive only.** Prune reads the book and the folder; it does not modify
`BankIndex`, `bank_book`, the capture roadmap, or Design View semantics.
## Open questions to resolve during build
## Settled forks (Daniel, 2026-07-24)
- **Fork R-C — deletion mechanism: OS trash vs. unlink.** Open; needs Daniel + a
platform to-verify. Lean: trash if portably available, else unlink with strong
dry-run/confirm. Product notes §Fork R-C.
- **Fork R-D — orphan attribution: owned-file manifest vs. index-diff vs.
folder-sweep.** Open; needs Daniel. Lean: **owned-file manifest**, and — because
the manifest is cheap to maintain from capture onward but a backfill cliff to
reconstruct later — **start tracking owned files as part of capture / Phase B
even though prune ships in Phase R** (defer the feature, design the seam).
Folder-sweep is rejected as unsafe (deletes hand-dropped files). Product notes
§Fork R-D.
- **Fork R-E — trigger: manual-only vs. offer-on-orphaning vs. periodic.** Open.
Lean: manual-primary (dry-run-first), an optional "…and prune now" at the
delete-bank confirmation, no background sweep. Product notes §Fork R-E.
- **Fork R-C — deletion mechanism.** Settled: **trash-preferred, unlink fallback.**
Route to the OS trash where a portable move-to-trash is available (recoverable),
else unlink behind the dry-run/confirm guardrail. Per-platform trash surface is a
must-verify. Folded into Settled decisions + Guardrails + API surface above.
Product notes §Fork R-C.
- **Fork R-D — orphan attribution.** Settled: **owned-file manifest**, `(owned ∩
present) referenced`; folder-sweep rejected as unsafe. The **seam lands early** —
capture writes each created file to the manifest starting in Phase B, prune
consumes it in Phase R. Persistence shape (sibling key vs. `banks` blob) is a
build-time residual. Folded into Settled decisions + Module architecture + API
surface above, and added as an up-front Phase B / capture plan point. Product
notes §Fork R-D.
- **Fork R-E — trigger.** Settled: **manual action + `bank_panel` button**, dry-run-
first, confirm-to-delete; no background sweep. The delete-time "…and prune now"
convenience was not selected (out of scope). Folded into Guardrails + Module
architecture above and the R3 plan points. Product notes §Fork R-E.
**Build-time residual (not a fork):** the owned-file manifest's exact persistence
shape (sibling `"reasampler"` ext-state key vs. folded into the `banks` blob).