From 21c21dd8c6985f263fbc9be407669042ad83c25b Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Fri, 25 Sep 2026 14:38:51 -0400 Subject: [PATCH] Docs: add Phase Upsilon (temp bank for never-saved projects) to PLAN.md --- docs/PLAN.md | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 2 deletions(-) diff --git a/docs/PLAN.md b/docs/PLAN.md index caad96f..54e19e5 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -18,7 +18,9 @@ seventeen: it came from a direct list of seven defects and refinements (Daniel, 2026-08-03), and like Ψ it has **no backing product doc of its own** — the seven are carried verbatim in its phase header below as Ω.1…Ω.7. It does amend two clauses of `docs/product/instrument-control-surface.md`, which are edited there rather than merely -contradicted here. +contradicted here. **Phase Υ**, a temporary bank for never-saved projects, is also a direct +request (Daniel, 2026-09-25). Like Ω it has no backing product doc; its settled design is +recorded in its phase section. ## What this doc is, and how it relates to the others @@ -40,7 +42,7 @@ contradicted here. **Worktree slug convention:** `p-w-t-`. Greek phase letters transliterate: **Θ → `th`**, **Ξ → `xi`**, **Γ → `g`**, **Ψ → `psi`**, **Ε → `e`**, **Ρ → `r`**, -**Λ → `l`**, **Ω → `om`**. So Θ-W1-T1 dispatches into `pth-w1-t1-zone-retirement`, Γ-W1-T1 into +**Λ → `l`**, **Ω → `om`**, **Υ → `u`**. So Θ-W1-T1 dispatches into `pth-w1-t1-zone-retirement`, Γ-W1-T1 into `pg-w1-t1-knob-interaction-law`, Ψ-W1-T1 into `ppsi-w1-t1-capture-range-exactness`, Λ-W2-T1 into `pl-w2-t1-linux-compile-blockers` (Phase Λ's two audit tracks already ran under `pl-w1-t1-build-toolchain-audit` and `pl-w1-t2-source-runtime-audit`), and Ω-W1-T1 @@ -160,6 +162,11 @@ phase in this plan with unanswered [Daniel]-class questions; the enumeration in predates Ω, so read *"scoped to Θ, Ξ, Γ, Ψ, Ε and Ρ"* as **Θ, Ξ, Γ, Ψ, Ε, Ρ and Ω** — seven phases, all of them fully ruled, and Λ alone excepted. +**Phase Υ (added 2026-09-25) has eight rulings, Υ-D1…Υ-D8, and all eight are settled** +(Daniel, 2026-09-25, every recommendation accepted). It has zero open forks. Its W0 +experiments are `[verify — DAW]` gates, not [Daniel] questions, so they don't change the Λ +carve-out above. + **Ruling 3 (Daniel, 2026-08-01) — real units at the host boundary.** *"The parameter values exposed to the VST host should be in real units, such that the host automation lanes report usable values."* Satisfied through VST3's **plain-value layer**, not its wire format (which is @@ -329,6 +336,127 @@ touches them. The instrument adds a sixth surface that binds every Θ track: --- +## Phase Υ — A temporary bank for never-saved projects + +**Ships:** Capture, ingest, import, export and the resample bake all work in a project that +has never been saved. Until first save the bank lives in a per-project temp root under +REAPER's default recording path. On first save it is copied into +`<.rpp dir>/reasampler_bank/`, and the index doesn't change, because index paths were +already relative to the root. + +**Consolidates: none of the seventeen.** Daniel's direct request, 2026-09-25, verbatim: +*"capture in a never saved project fails now. instead, can we put a temp bank in the default +reaper media folder, which upon initial save (with the copy option checked) will move into +the saved directory bank location?"* There is no backing product doc. The design came from a +staff-engineer design pass (2026-09-25), and its settled form is recorded here in full. +**Zero open forks.** The W0 experiments are `[verify — DAW]` gates, not decisions. + +**The design, in five statements:** + +- **Bank root.** For a saved project, the root is the `.rpp`'s directory. For an unsaved one + it is `/ReaSampler unsaved banks//`. Index paths stay + `reasampler_bank/` relative to the root, so the index, the tracking ledger, instrument + sample refs and usage records **never change at first save**. The relative-paths-only + invariant holds for the index unchanged. +- **Lazy temp root.** The root is created by the first act that needs to write (capture, + ingest, import, bake landing). Its absolute path goes in ONE new ext-state key, + `unsaved_bank_root`, and never in the index, the ledger or any ref. The key is read only + when the `.rpp` path is empty, or as a migration breadcrumb. It is cleared, without + dirtying the project, after migration. +- **Ext-state writes on unsaved projects are allowed.** Today they are refused by the + project's own rule (`ext_state_io.cpp`'s `activeSavedProject` / `ensureProjectGuid`), not + by an SDK fact. **This is gated on experiment E1.** +- **First-save migration**, `migrateUnsavedBank(root, projectDir)`, is idempotent and has + two triggers. (1) The existing timer poll sees the first-save transition: path `""` → X on + the same project object and GUID. The GUID is minted without dirtying the project. + (2) At project load, a saved `.rpp` still carries `unsaved_bank_root`. That covers + save-at-close/quit, a crash, and a later session. The migration **copies**, following the + existing relocation precedent. It skips an existing file of the same size, refuses to + overwrite one that differs and reports it, and counts items that still point into temp. +- **The instrument resolves its own project**, not the active tab. It tries candidate roots in + order: the project dir, then the breadcrumb root. + +### Rulings — Daniel's, 2026-09-25. Eight, settled (every recommendation accepted). + +**Do not re-litigate these.** There is no product doc, so this table is their only home. + +| Ruling | What it settled | Bound into | +|---|---|---| +| **Υ-D1** | **Always move the bank on first save, whatever the copy-media checkbox says.** An extension can't observe that checkbox | Υ-W2-T2 | +| **Υ-D2** | **Copy, and keep the temp originals.** No second deletion authority beside prune | Υ-W2-T2 | +| **Υ-D3** | **Arrange items still pointing at temp files after first save get a console notice only.** No automatic re-point | Υ-W2-T2 | +| **Υ-D4** | **The temp root goes under the default recording path** (`GetProjectPathEx` on the unsaved project) | Υ-W2-T1 | +| **Υ-D5** | **The breadcrumb stores the absolute temp path** | Υ-W2-T1 | +| **Υ-D6** | **Leftover temp roots from abandoned or crashed projects are left alone in v1.** A confirmed, dry-run-first "reclaim unsaved-bank leftovers" action in the prune family comes later | Υ-W3-T2 | +| **Υ-D7** | **Drop the Save-As prompts** from offline capture, realtime capture and Design View | Υ-W3-T1 | +| **Υ-D8** | **Allow package import and export on unsaved projects** | Υ-W3-T1 | + +### Phase-Υ acceptance criteria + +These bind every track in this phase, on top of the plan-wide set above. + +- **A saved project's behaviour doesn't change.** For a saved project, the root resolves to + exactly the directory it resolves to today. Υ-W1 is a pure no-behaviour-change refactor and + says so as a criterion. +- **The temp path never enters the index, the ledger, instrument refs or usage records.** + `unsaved_bank_root` is the only place it is stored. +- **Prune is still the only thing that deletes files** (Υ-D2). Migration copies and nothing + else. The temp originals survive until Υ-W3-T2's reclaim action, which is itself part of + the prune family. +- **Migration is idempotent and never overwrites silently.** A second run is a no-op. A file + of the same size is skipped. A file that differs is refused and reported, never + overwritten. Note that this is stricter than today's `relocateBankFolder` (see A1 below). +- **Capture and placement stay separate acts.** Nothing here places an item or re-points + one (Υ-D3). +- **`bankRootFor` is pure and has its own test target**, the same as every pure module. + +--- + +### Υ-W0 — Daniel's REAPER experiments + +The script is `docs/verify-unsaved-bank.md`. **E1 gates Υ-W2**, and E2 shapes how it is +built. + +| Exp | Question | +|---|---| +| **E1** | Does ext state survive this sequence: unsaved → switch tabs → first save? | +| **E2** | Does `SetProjExtState` on its own dirty the project? | +| **E3** | Is an unsaved project's `GetProjectPathEx` the same across tabs? | +| **E4** | Where do items pointing at temp files end up after save, with copy ticked and with it unticked? | +| **E5** | Does save-at-close give the extension no tick? (If so, the load-time trigger is the only one that catches that case) | + +### Υ-W1 — The bank-root seam *(IN PROGRESS — dispatched 2026-09-25)* + +| Track | Owns | +|---|---| +| **T1** `bank-root-seam` | Pure `bankRootFor` plus ONE shell resolver, `bankRootOf(ReaProject*)`. It replaces the extension's ~12 copies of "EnumProjects → `.rpp` dir". **No behaviour change** | +| **T2** `instrument-own-project` | The instrument resolves ITS OWN project instead of the active tab (**a real bug fix**: today it reads the active tab), with an ordered list of candidate roots | + +### Υ-W2 — Unsaved ext state, and the migration *(gated on E1; E2 shapes it)* + +| Track | Owns | +|---|---| +| **T1** `unsaved-ext-state` | Lift the ext-state refusal for unsaved projects; create the temp root lazily; add the `unsaved_bank_root` key; update the ext-state contract text (Υ-D4, Υ-D5) | +| **T2** `first-save-migration` | `migrateUnsavedBank` and both of its triggers (timer-poll transition, load-time breadcrumb); the console notice for items still pointing into temp (Υ-D1, Υ-D2, Υ-D3) | + +### Υ-W3 — Lift the refusals, then the leftovers + +| Track | Owns | +|---|---| +| **T1** `lift-unsaved-refusals` | Every refusal on an unsaved-project surface (Υ-D7, Υ-D8). Closes the `docs/TODO.md` entries "The capture path ignores `saveToActiveProject`'s return at four sites" and "`RunCaptureItemAssign`'s undo point does not follow the pattern its comment claims" | +| **T2** `reclaim-unsaved-leftovers` *(later)* | Υ-D6's confirmed, dry-run-first reclaim action in the prune family | + +### Pre-existing issues the design pass found — for awareness, NOT in scope + +| # | Issue | +|---|---| +| **A1** | `relocateBankFolder` uses `overwrite_existing`, so a Save-As into a folder that holds another project's bank can overwrite files with the same name | +| **A3** | Prune doesn't protect bank files that arrange items reference. The usage scan only looks at instrument FX, so a sample that was placed and then removed can be pruned, which takes the item offline | +| **A4** | Save-As, including first save, marks the project dirty immediately (GUID re-mint plus `MarkProjectDirty` in `session.cpp`) | +| **A5** | REAPER's "Clean current project directory" may offer to delete `reasampler_bank` files `[unverified]` | + +--- + ## Phase Ω — Responsiveness, and the control surface's second pass **Ships:** A mode switch that draws its result at once instead of two seconds later; a master @@ -1815,4 +1943,23 @@ Phase Lambda — ReaSampler on Linux (none of the seventeen; a direct reque gating a dispatch, W2-T3), L-F3 (copy-only invariant wording — gates nothing), L-F4 (declared support floor — gates W5, not a dispatch). Lambda registers NO VST3 parameter, ever — that table is Gamma-W4-T1's and is frozen. + +Phase Upsilon — Temporary bank for never-saved projects (none of the seventeen; 2026-09-25) + W0 Daniel's REAPER experiments (docs/verify-unsaved-bank.md) [E1 GATES W2] + W1 The bank-root seam [IN PROGRESS, dispatched 2026-09-25] + T1 bank-root-seam .............. pure bankRootFor + ONE shell bankRootOf(ReaProject*) + replaces ~12 copies; no behaviour change + T2 instrument-own-project ...... own project, not the active tab (a bug fix); + ordered candidate roots + W2 Unsaved ext state, and the migration [GATED: E1; E2 shapes it] + T1 unsaved-ext-state ........... lazy temp root + unsaved_bank_root breadcrumb + T2 first-save-migration ........ idempotent COPY; timer-poll + load-time triggers + W3 Lift the refusals, then the leftovers + T1 lift-unsaved-refusals ....... D7 + D8; closes two TODO.md capture-family entries + T2 reclaim-unsaved-leftovers ... D6, later; prune family, dry-run first + + Index paths stay relative to the bank root, so nothing in the index/ledger/refs changes + at first save. D1-D8 SETTLED 2026-09-25; ZERO open forks. Awareness only, not in scope: + A1 relocate overwrites, A3 prune ignores arrange refs, A4 Save-As dirties at once, + A5 Clean-project-dir may offer the bank. ```