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
+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).