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
+102 -52
View File
@@ -194,6 +194,35 @@ active id. Legacy `bank_index` JSON parses into `{ pool }` with zero named banks
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 B1B5 (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 B1B4, 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
@@ -282,8 +311,9 @@ timeline item).
- [ ] 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).
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
@@ -305,12 +335,16 @@ ready to scope into implementation waves. One polish detail remains:
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`.
**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
B1B5 (retro-touches B1B4) — 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.
---
@@ -338,68 +372,84 @@ ready to scope into implementation waves. One polish detail remains:
> **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
**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 `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).
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) → 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.
- [ ] 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, 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).
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); resolve the bank
folder via the M4 project-relative machinery.
- [ ] 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 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.**
**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.
- [ ] 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.
- [ ] `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 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.
## 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).