Docs: add Phase Upsilon (temp bank for never-saved projects) to PLAN.md

This commit is contained in:
2026-09-25 14:38:51 -04:00
parent 8c5a9473f3
commit 21c21dd8c6
+149 -2
View File
@@ -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 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 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 `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 ## What this doc is, and how it relates to the others
@@ -40,7 +42,7 @@ contradicted here.
**Worktree slug convention:** `p<phase>-w<wave>-t<track>-<slug>`. Greek phase letters **Worktree slug convention:** `p<phase>-w<wave>-t<track>-<slug>`. Greek phase letters
transliterate: **Θ → `th`**, **Ξ → `xi`**, **Γ → `g`**, **Ψ → `psi`**, **Ε → `e`**, **Ρ → `r`**, 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`, `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 Λ-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 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 predates Ω, so read *"scoped to Θ, Ξ, Γ, Ψ, Ε and Ρ"* as **Θ, Ξ, Γ, Ψ, Ε, Ρ and Ω** — seven
phases, all of them fully ruled, and Λ alone excepted. 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 **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 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 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 `<GetProjectPathEx(proj)>/ReaSampler unsaved banks/<genGuid>/`. Index paths stay
`reasampler_bank/<file>` 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 ## 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 **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), gating a dispatch, W2-T3), L-F3 (copy-only invariant wording — gates nothing),
L-F4 (declared support floor — gates W5, not a dispatch). 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. 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.
``` ```