docs: record Phase Ε fork rulings in the plan
Ε-F1 proprietary RSBK, Ε-F2 new bank with auto-suffix, Ε-F3 refuse on a degraded ledger. No Ε track is gated now. Also carries Phase Ρ's plan sections, authored concurrently in this shared checkout.
This commit is contained in:
+447
-51
@@ -9,7 +9,8 @@ come from the seventeen: it came from a direct list of seven defects and refinem
|
|||||||
(Daniel, 2026-08-01) and is specified inline in its own section below — there is no
|
(Daniel, 2026-08-01) and is specified inline in its own section below — there is no
|
||||||
backing product doc for it, **and Phase Ε**, which likewise did not come from the
|
backing product doc for it, **and Phase Ε**, which likewise did not come from the
|
||||||
seventeen: it came from a direct request (Daniel, 2026-08-02) and is scoped in
|
seventeen: it came from a direct request (Daniel, 2026-08-02) and is scoped in
|
||||||
`docs/product/bank-package.md`.
|
`docs/product/bank-package.md`, **and Phase Ρ**, likewise a direct request (Daniel,
|
||||||
|
2026-08-02), scoped in `docs/product/render-in-place.md`.
|
||||||
|
|
||||||
## What this doc is, and how it relates to the others
|
## What this doc is, and how it relates to the others
|
||||||
|
|
||||||
@@ -30,9 +31,9 @@ seventeen: it came from a direct request (Daniel, 2026-08-02) and is scoped in
|
|||||||
cited section rather than reading a file whole.
|
cited section rather than reading a file whole.
|
||||||
|
|
||||||
**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`**. So Θ-W1-T1
|
transliterate: **Θ → `th`**, **Ξ → `xi`**, **Γ → `g`**, **Ψ → `psi`**, **Ε → `e`**, **Ρ → `r`**. So
|
||||||
dispatches into `pth-w1-t1-zone-retirement`, Γ-W1-T1 into `pg-w1-t1-knob-interaction-law`,
|
Θ-W1-T1 dispatches into `pth-w1-t1-zone-retirement`, Γ-W1-T1 into
|
||||||
and Ψ-W1-T1 into `ppsi-w1-t1-capture-range-exactness`.
|
`pg-w1-t1-knob-interaction-law`, and Ψ-W1-T1 into `ppsi-w1-t1-capture-range-exactness`.
|
||||||
|
|
||||||
## Decision state
|
## Decision state
|
||||||
|
|
||||||
@@ -62,13 +63,30 @@ the stage lengths to 10s"* — the ceiling moves in Γ-W1-T1. **Γ-F7** (the VST
|
|||||||
*order*) is **RULED: signal flow** — *"signal flow order."* **There is now NO unanswered
|
*order*) is **RULED: signal flow** — *"signal flow order."* **There is now NO unanswered
|
||||||
[Daniel]-class question anywhere in this plan.**
|
[Daniel]-class question anywhere in this plan.**
|
||||||
|
|
||||||
**That claim is scoped to Phases Θ / Ξ / Γ / Ψ, and Phase Ε reopens the class.** Phase Ε
|
**Phase Ε (added 2026-08-02) opened three more [Daniel]-class forks and ALL THREE ARE
|
||||||
(added 2026-08-02) carries **three** [Daniel]-class forks — **Ε-F1** (container format),
|
RULED**, same day (Daniel, 2026-08-02): **Ε-F1** container format — *"proprietary
|
||||||
**Ε-F2** (import target), **Ε-F3** (import under a degraded ledger) — each stated with a
|
container"*, the hand-rolled `RSBK`; **Ε-F2** import target — *"always lands as a new bank,
|
||||||
recommendation and its counter-argument in the phase header below and in
|
with an auto suffix if name collision"*; **Ε-F3** import under a degraded tracking ledger —
|
||||||
`docs/product/bank-package.md` §"Open forks". **Only Ε-F1 blocks a dispatch** (Ε-W1-T1);
|
*"refuse mismatched import."* The rulings are folded into the tracks below and indexed at
|
||||||
the other two are answerable at Ε-W2 and could be ruled at implementation review if Daniel
|
`docs/product/bank-package.md` §"Rulings". **Two of the three landed somewhere other than
|
||||||
prefers, but both are user-visible policy rather than implementation detail.
|
the framing recommendation:** Ε-F2 dropped the proposed rename prompt in favour of a
|
||||||
|
deterministic suffix, and Ε-F3 reversed allow-with-confirm to refuse — carried by the
|
||||||
|
framing's own counter-argument, that the accepted tracking residual contemplates *one*
|
||||||
|
untracked capture while a bulk import strands hundreds in a single gesture. **The plan-wide
|
||||||
|
claim above therefore holds unqualified — no unanswered [Daniel]-class question remains
|
||||||
|
anywhere in this plan, Phase Ε included — and no track in this plan is gated on a
|
||||||
|
decision.**
|
||||||
|
|
||||||
|
**Phase Ρ (added 2026-08-02) reopens the class with three forks, and NONE of them gates a
|
||||||
|
dispatch.** **Ρ-F1** (multi-track: inherit the existing refusal, or loop per selected
|
||||||
|
track), **Ρ-F2** (when Design is the active mode, does the new track go to Design as
|
||||||
|
Daniel's rule states, or always to Arrange), **Ρ-F3** (tail: follow the panel setting, or
|
||||||
|
force `None`). Each is stated with a recommendation and its counter-argument in the phase
|
||||||
|
header below and in `docs/product/render-in-place.md` §"Open forks". All three are
|
||||||
|
user-visible policy rather than implementation detail, and all three are answerable at
|
||||||
|
implementation review; the phase's single track is specced against the recommended answer
|
||||||
|
in each case, so a ruling that agrees changes nothing and a ruling that disagrees changes
|
||||||
|
one named paragraph.
|
||||||
|
|
||||||
**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
|
||||||
@@ -2267,19 +2285,19 @@ actionable message, and neither direction is ever a partial landing.
|
|||||||
`export|package|portable` returns only unrelated matches (prune's portable move-to-trash,
|
`export|package|portable` returns only unrelated matches (prune's portable move-to-trash,
|
||||||
MIDI-playback prose) — so this phase **supersedes nothing and absorbs nothing.**
|
MIDI-playback prose) — so this phase **supersedes nothing and absorbs nothing.**
|
||||||
|
|
||||||
**Three forks are OPEN and are Daniel's.** They are the first unanswered [Daniel]-class
|
**Three forks were opened at framing and all three are RULED** (Daniel, 2026-08-02). The
|
||||||
questions in this plan since Γ closed its seven; each is stated with a recommendation and
|
rationale for each — including why two went against the framing recommendation — is
|
||||||
its counter-argument in `docs/product/bank-package.md` §"Open forks".
|
recorded at `docs/product/bank-package.md` §"Rulings"; the binding specification for each
|
||||||
|
is in the section of that doc named below.
|
||||||
|
|
||||||
| Fork | Question | Recommendation | Blocks |
|
| Fork | Ruling | Specified in | Bound into |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **Ε-F1** | Container format: hand-rolled `RSBK` vs. ZIP via the vendored minizip (`vendor/WDL/WDL/zlib/`) | **hand-rolled `RSBK`** — the only option where the whole codec lands pure | **Ε-W1-T1 dispatch.** One-way door: packages are in users' hands the day it ships |
|
| **Ε-F1** | **Proprietary container** — the hand-rolled `RSBK`. ZIP via the vendored MiniZip64 (`vendor/WDL/WDL/zlib/`) and a hand-written stored-only ZIP shape are both rejected and are not to be revisited in this phase | §"The container" | Ε-W1-T1 |
|
||||||
| **Ε-F2** | Import target: always a new bank, or also offer merge-into-existing | **new bank by default**, merge as a *separate* bindable verb if wanted at all | Ε-W2-T2's action count and dialog; not W1 |
|
| **Ε-F2** | **Import always lands as a new bank**, with an automatic numeric suffix on a display-name collision — no prompt, no overwrite. Merge-into-existing is **out of scope for Phase Ε**, not deferred | §"Identity and collision on import", incl. the auto-suffix rule | Ε-W2-T2 |
|
||||||
| **Ε-F3** | Import while the tracking ledger is degraded: allow-with-confirm, or refuse | **allow with an up-front confirm naming the consequence** — matches the accepted residual rather than inventing a new block | Ε-W2-T2's guard; not W1 |
|
| **Ε-F3** | **Refuse** the import when the tracking ledger is degraded. No confirm-and-proceed path, no opt-out | §"Import under a degraded tracking ledger" | Ε-W2-T2 |
|
||||||
|
|
||||||
**Ε-F1 is the only one that blocks a dispatch.** Ε-F2 and Ε-F3 can be carried into Ε-W2-T2
|
**No Ε track is gated on a decision.** Every track in this phase is dispatchable as
|
||||||
as ruled-at-review if Daniel prefers, but both are user-visible policy rather than
|
written.
|
||||||
implementation detail, so the plan's default is to ask.
|
|
||||||
|
|
||||||
### Phase-Ε acceptance criteria
|
### Phase-Ε acceptance criteria
|
||||||
|
|
||||||
@@ -2334,7 +2352,9 @@ new directories** (`src/core/package/`, `src/shell/package/`) that no other phas
|
|||||||
not read here. The only pre-existing files any Ε track edits are named per track below —
|
not read here. The only pre-existing files any Ε track edits are named per track below —
|
||||||
`core/tracking/origin_ledger` (W1-T3, exclusively), the root `CMakeLists.txt`
|
`core/tracking/origin_ledger` (W1-T3, exclusively), the root `CMakeLists.txt`
|
||||||
`add_subdirectory` list (W1-T1 and W1-T2, one line each), `src/app/main.cpp` and the panel's
|
`add_subdirectory` list (W1-T1 and W1-T2, one line each), `src/app/main.cpp` and the panel's
|
||||||
bank menu (W2-T1 and W2-T2, one registration line and one menu row each). **No Ε track
|
bank menu (W2-T1 and W2-T2, one registration line and one menu row each), and
|
||||||
|
`core/model/bank_book.{h,cpp}` (W2-T2 only — **one additive public `const` member**, required
|
||||||
|
by the Ε-F2 auto-suffix rule so the name fold keeps its single home). **No Ε track
|
||||||
touches `core/instrument/`, `shell/instrument/`, or any capture backend.**
|
touches `core/instrument/`, `shell/instrument/`, or any capture backend.**
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -2356,10 +2376,10 @@ bytes and structs, T2 knows only paths and bytes, T3 knows only the ledger.
|
|||||||
`src/shell/package`. Two append-only lines in one list: **textual merge adjacency, not
|
`src/shell/package`. Two append-only lines in one list: **textual merge adjacency, not
|
||||||
semantic contention.** Whichever lands second rebases.
|
semantic contention.** Whichever lands second rebases.
|
||||||
|
|
||||||
**T1 is the wave's only gated dispatch** — it cannot start before Ε-F1 is ruled, because the
|
**All three tracks are dispatchable now.** T1 carries the Ε-F1 ruling — the container is the
|
||||||
fork *is* T1's deliverable. T2 and T3 are unaffected by Ε-F1 and can start immediately: T2's
|
hand-rolled `RSBK`, decided, not a candidate T1 chooses among. T2 and T3 never depended on
|
||||||
API is bytes-in/bytes-out regardless of what those bytes mean, and T3 touches no package
|
that ruling anyway: T2's API is bytes-in/bytes-out regardless of what those bytes mean, and
|
||||||
code at all.
|
T3 touches no package code at all.
|
||||||
|
|
||||||
#### Ε-W1-T1 — `package-format`
|
#### Ε-W1-T1 — `package-format`
|
||||||
|
|
||||||
@@ -2379,6 +2399,12 @@ root `CMakeLists.txt`. **Does not own:** `export_plan` / `import_plan` (Ε-W2),
|
|||||||
`shell/`, `core/model`, or `core/tracking`.
|
`shell/`, `core/model`, or `core/tracking`.
|
||||||
|
|
||||||
**Behavior.**
|
**Behavior.**
|
||||||
|
- **The container is the hand-rolled `RSBK` (Ε-F1, ruled).** Magic `RSBK`, a fixed
|
||||||
|
little-endian header carrying the two version fields, a length-prefixed JSON manifest, then
|
||||||
|
each entry's payload concatenated in manifest order. Framing is built on `core/wire/bytes.h`
|
||||||
|
(`putLE` / `ByteReader`) and the manifest on `core/json` — both already owned and tested
|
||||||
|
here. **No ZIP, no compressor, no new third-party source in the build**; a link edge to
|
||||||
|
`vendor/WDL/WDL/zlib/` means the ruling was misread.
|
||||||
- **Two version integers, not one.** `formatVersion` = what this writer emitted;
|
- **Two version integers, not one.** `formatVersion` = what this writer emitted;
|
||||||
`minReaderVersion` = the oldest reader that can read it safely. The reader's whole rule is
|
`minReaderVersion` = the oldest reader that can read it safely. The reader's whole rule is
|
||||||
`minReaderVersion <= kPackageFormatVersion`. An **additive** change (a new optional
|
`minReaderVersion <= kPackageFormatVersion`. An **additive** change (a new optional
|
||||||
@@ -2425,8 +2451,8 @@ root `CMakeLists.txt`. **Does not own:** `export_plan` / `import_plan` (Ε-W2),
|
|||||||
- `package_format_tests`, `package_manifest_tests`, `bank_package_tests` all run without
|
- `package_format_tests`, `package_manifest_tests`, `bank_package_tests` all run without
|
||||||
REAPER or a DAW.
|
REAPER or a DAW.
|
||||||
|
|
||||||
**Open questions.** **[Daniel] Ε-F1** — the container format itself; this track cannot be
|
**Open questions.** **No [Daniel] questions — Ε-F1 is RULED** (proprietary `RSBK`), so this
|
||||||
dispatched until it is ruled. **[propose at review]** whether `package_format` and
|
track is dispatchable as written. **[propose at review]** whether `package_format` and
|
||||||
`bank_package` are genuinely two modules or one — the split is proposed on responsibility
|
`bank_package` are genuinely two modules or one — the split is proposed on responsibility
|
||||||
grounds (constants and classification vs. offset arithmetic) and may collapse if the
|
grounds (constants and classification vs. offset arithmetic) and may collapse if the
|
||||||
arithmetic turns out to be twenty lines.
|
arithmetic turns out to be twenty lines.
|
||||||
@@ -2613,24 +2639,39 @@ from the bank's display name (recommended, sanitized through
|
|||||||
|
|
||||||
#### Ε-W2-T2 — `bank-import`
|
#### Ε-W2-T2 — `bank-import`
|
||||||
|
|
||||||
**Goal.** A package becomes a bank in this project — completely, or not at all — with every
|
**Goal.** A package becomes a **new** bank in this project — completely, or not at all —
|
||||||
one of the four collision classes answered explicitly rather than by whatever the model
|
with every one of the four collision classes answered explicitly rather than by whatever the
|
||||||
happens to do.
|
model happens to do.
|
||||||
|
|
||||||
**Spec:** `docs/product/bank-package.md` §"Identity and collision on import", §"Failure
|
**Spec:** `docs/product/bank-package.md` §"Identity and collision on import" (including the
|
||||||
modes" (import rows), §"Version tagging: both directions".
|
auto-suffix rule), §"Failure modes" (import rows), §"Import under a degraded tracking
|
||||||
|
ledger", §"Version tagging: both directions".
|
||||||
|
|
||||||
**Surface boundary — owns:** new `core/package/import_plan` (pure: the id remap table, the
|
**Surface boundary — owns:** new `core/package/import_plan` (pure: the id remap table, the
|
||||||
parent remap, the per-entry write / skip-already-present / rename disposition, the
|
parent remap, the per-entry write / skip-already-present / rename disposition, the
|
||||||
destination bank name after uniqueness folding), new `shell/package/import_bank` (the
|
destination bank name after uniqueness folding), new `shell/package/import_bank` (the
|
||||||
promptless verb), new `shell/actions/package_import_action`, the panel's `WM_DROPFILES` route
|
promptless verb), new `shell/actions/package_import_action`, the panel's `WM_DROPFILES` route
|
||||||
for a `.rsbank` (routing only — the existing ingest route for audio files is untouched), one
|
for a `.rsbank` (routing only — the existing ingest route for audio files is untouched), one
|
||||||
registration line in `src/app/main.cpp`, one panel menu row. **Does not own:** anything on the
|
registration line in `src/app/main.cpp`, one panel menu row, and — the **only** `core/model/`
|
||||||
export side, `bank_book`'s rules (consumed, never re-implemented), `origin_ledger`
|
edit in the phase — **one additive public `const` member on `BankBook`** (recommended
|
||||||
(Ε-W1-T3's).
|
`std::string uniqueDisplayName(const std::string& seed) const`), so the auto-suffix probe runs
|
||||||
|
behind the model's own name fold. **Does not own:** anything on the export side,
|
||||||
|
`bank_book`'s *rules* (consumed, never re-implemented — the new member exposes the existing
|
||||||
|
fold, it does not add a second one), `origin_ledger` (Ε-W1-T3's).
|
||||||
|
|
||||||
**Behavior.**
|
**Behavior.**
|
||||||
- **Version gate first, before any byte is written.** `minReaderVersion` above this build
|
- **The ledger guard runs FIRST — before the file picker opens (Ε-F3, ruled: refuse).** If
|
||||||
|
`tracking::ledgerDegraded(status)` holds for the project's loaded ledger status
|
||||||
|
(`Unreadable` or `FutureVersion`; `core/tracking/origin_ledger.h:94`, `:100-101`), the
|
||||||
|
import **refuses outright** — no picker, no bytes read, no confirm-and-proceed path, no
|
||||||
|
opt-out. `Fresh` and `Loaded` both proceed. **Do not key this on
|
||||||
|
`PruneReport::blockedByTracking`**: that flag also fires on undecodable `rsusage_*` keys,
|
||||||
|
which govern deletion-time protection and have nothing to do with writing birth records.
|
||||||
|
The refusal is a `ShowConsoleMsg` block mirroring `prune_action.cpp:30-69` in structure and
|
||||||
|
tone, with two variants (malformed / newer-build) and every recovery line naming **this
|
||||||
|
build's** namespace through `version::extStateNamespace()`. Exact wording in the spec doc.
|
||||||
|
**Export is deliberately not gated this way** — that is Ε-W2-T1's, and it stays ungated.
|
||||||
|
- **Version gate second, before any byte is written.** `minReaderVersion` above this build
|
||||||
refuses the whole package and reports through `ShowMessageBox`
|
refuses the whole package and reports through `ShowMessageBox`
|
||||||
(**verified**, `reaper_plugin_functions.h:6546`) naming three things: the package's
|
(**verified**, `reaper_plugin_functions.h:6546`) naming three things: the package's
|
||||||
requirement, this build's ceiling, and the writer's semver. Two of the three is not enough
|
requirement, this build's ceiling, and the writer's semver. Two of the three is not enough
|
||||||
@@ -2643,7 +2684,22 @@ export side, `bank_book`'s rules (consumed, never re-implemented), `origin_ledge
|
|||||||
`capture_paths::deriveBankPaths`, silently, counted in the summary. (3) **Content hash** —
|
`capture_paths::deriveBankPaths`, silently, counted in the summary. (3) **Content hash** —
|
||||||
consult `BankModel::findByHash` **before writing the payload**; on a hit, skip the write
|
consult `BankModel::findByHash` **before writing the payload**; on a hit, skip the write
|
||||||
entirely and let the entry collapse, so a dedup never manufactures an orphan. (4) **Bank
|
entirely and let the entry collapse, so a dedup never manufactures an orphan. (4) **Bank
|
||||||
display name** — Ε-F2.
|
display name** — **auto-suffix, no prompt** (Ε-F2, ruled). Seed = the package's recorded
|
||||||
|
source bank name **verbatim** (or the literal `Imported bank` if absent/blank); take the
|
||||||
|
first of `seed`, `seed + " 2"`, `seed + " 3"`, … whose fold is free in the destination
|
||||||
|
book, ascending from 2. Four points that decide the behaviour and must not be re-invented:
|
||||||
|
the seed is **never re-parsed** (`"Drums 2"` colliding lands as `"Drums 2 2"`, not
|
||||||
|
`"Drums 3"` — a bare trailing integer is indistinguishable from `"Kit 808"`); the probe
|
||||||
|
**fills gaps** (first-free, not highest-plus-one, so it is a pure function of the current
|
||||||
|
name set); the probe **terminates** by pigeonhole within `B + 1` candidates for `B` banks,
|
||||||
|
so **no arbitrary cap**; and the fold is `BankBook`'s own (`bank_book.h:252-258`), reached
|
||||||
|
through the new public member, never re-implemented in `import_plan`. Sample display names
|
||||||
|
are **not** suffixed, and `slot_map` positions are untouched.
|
||||||
|
- **Always a new bank; never a merge (Ε-F2, ruled).** The import creates a bank — it never
|
||||||
|
merges into an existing one, never lands into the pool, and offers no target picker. A
|
||||||
|
pool export therefore lands as a **named** bank `"Pool 2"`, which is correct, not a glitch.
|
||||||
|
This track ships **one** action, not two; merge-into-existing is out of scope for the
|
||||||
|
phase, and move/copy already cover the after-the-fact case.
|
||||||
- **Birth records at landing.** Every landed file goes through
|
- **Birth records at landing.** Every landed file goes through
|
||||||
`ReaSamplerSession::recordCreated(sample, OriginKind::PackageImport)` at the same point the
|
`ReaSamplerSession::recordCreated(sample, OriginKind::PackageImport)` at the same point the
|
||||||
`Sample` is added, in the same straight-line block, per `core/tracking/CLAUDE.md`'s
|
`Sample` is added, in the same straight-line block, per `core/tracking/CLAUDE.md`'s
|
||||||
@@ -2667,8 +2723,19 @@ export side, `bank_book`'s rules (consumed, never re-implemented), `origin_ledge
|
|||||||
- `planImport` is pure and total, and every one of the four collision classes has a test that
|
- `planImport` is pure and total, and every one of the four collision classes has a test that
|
||||||
exercises it without a filesystem: colliding ids, colliding file names, a hash already
|
exercises it without a filesystem: colliding ids, colliding file names, a hash already
|
||||||
present, and a colliding bank name.
|
present, and a colliding bank name.
|
||||||
- Importing a package built from bank B into a project that already contains B produces a
|
- Importing a package built from bank B back into the project that already contains B lands
|
||||||
correct result under the Ε-F2 ruling, with every id reminted and no entry lost.
|
a **new** bank named `"B 2"`, with every id reminted, no entry lost, and B itself
|
||||||
|
unmutated. Importing it a third time lands `"B 3"`.
|
||||||
|
- The suffix probe is pinned by pure tests over a name set, covering at minimum: a free seed
|
||||||
|
(no suffix applied), a case/whitespace-folded collision (`"drums"` blocks `"Drums"`), a gap
|
||||||
|
(`"Drums"` + `"Drums 3"` present ⇒ `"Drums 2"`), a seed that already ends in a number
|
||||||
|
(`"Drums 2"` colliding ⇒ `"Drums 2 2"`), an absent/blank recorded name (⇒ `Imported bank`),
|
||||||
|
and a package whose source bank was the pool (⇒ `"Pool 2"`, a named bank).
|
||||||
|
- A degraded ledger (`Unreadable` and `FutureVersion`, both asserted) refuses the import with
|
||||||
|
**no picker shown, zero files written, zero index mutation**, and the message names the
|
||||||
|
channel-correct ext-state namespace. An undecodable `rsusage_*` key with an otherwise
|
||||||
|
`Loaded` ledger **does not** block — asserted, because the tempting reuse of
|
||||||
|
`blockedByTracking` would silently make it.
|
||||||
- An entry whose payload fails its `hashBytes` check aborts the import with **zero** files
|
- An entry whose payload fails its `hashBytes` check aborts the import with **zero** files
|
||||||
landed and **zero** index mutation — asserted on both, since either alone would pass a
|
landed and **zero** index mutation — asserted on both, since either alone would pass a
|
||||||
weaker test.
|
weaker test.
|
||||||
@@ -2685,11 +2752,13 @@ export side, `bank_book`'s rules (consumed, never re-implemented), `origin_ledge
|
|||||||
metadata, confirm a live ReaSampler 9000 instance picks up the new bank content on the
|
metadata, confirm a live ReaSampler 9000 instance picks up the new bank content on the
|
||||||
generation bump, and confirm one Ctrl-Z removes the index entries.
|
generation bump, and confirm one Ctrl-Z removes the index entries.
|
||||||
|
|
||||||
**Open questions.** **[Daniel] Ε-F2** — new bank always, or merge offered; decides whether
|
**Open questions.** **No [Daniel] questions — Ε-F2 and Ε-F3 are both RULED** (new bank
|
||||||
this track ships one action or two. **[Daniel] Ε-F3** — behaviour when the ledger is degraded
|
always with an auto suffix; refuse on a degraded ledger), so this track is dispatchable as
|
||||||
at import time. **[propose at review]** whether the import summary is a console block, a
|
written and ships one action. **[propose at review]** whether the import summary is a console
|
||||||
message box, or both; the recommendation is a console block plus a one-line message box, so
|
block, a message box, or both; the recommendation is a console block plus a one-line message
|
||||||
the detail is copyable and the outcome is unmissable.
|
box, so the detail is copyable and the outcome is unmissable. Note the refusal path is
|
||||||
|
already fixed at a console block by the Ε-F3 spec, so this call is about the *success*
|
||||||
|
summary only.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -2751,6 +2820,294 @@ properties under test are structural and a large payload proves nothing extra.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Phase Ρ — Render in place: a track's output to a new sibling, source to the bench
|
||||||
|
|
||||||
|
**Ships:** one bindable action that renders the selected track's output over the current
|
||||||
|
range to a file outside the bank, drops that file as an item on a brand-new sibling track
|
||||||
|
at the exact position it was rendered from, clones the source's colour and its name with an
|
||||||
|
idempotent `Capture ` prefix, and moves the source track into Design mode — where Design
|
||||||
|
View's existing park hides it, takes it out of the mix, and puts its FX offline. The bank is
|
||||||
|
never read, never written, and never notified.
|
||||||
|
|
||||||
|
**Consolidates: none of the seventeen.** Phase Ρ came from a direct request (Daniel,
|
||||||
|
2026-08-02) and is scoped in `docs/product/render-in-place.md`. Daniel's framing, verbatim
|
||||||
|
in substance: *similar to REAPER's "Render selected track time selection to new track
|
||||||
|
(stereo) and mute original", except instead of muting the original, the source track
|
||||||
|
stays/goes to design mode, and the resulting new sibling track — which gets the rendered
|
||||||
|
audio item placed correctly in the timeline — stays in whatever mode was active when the
|
||||||
|
action was run; the new track clones the source track colour and name with a Capture
|
||||||
|
prefix; this must NOT put the rendered audio into the ReaSampler banks/pool.* It
|
||||||
|
**supersedes nothing** — a sweep of `docs/TODO.md` and `docs/TODO-1.0.md` for
|
||||||
|
`render.in.place|render to new track|preserve.source` returns nothing.
|
||||||
|
|
||||||
|
### The framing answer — why this does not breach the load-bearing principle
|
||||||
|
|
||||||
|
Root `CLAUDE.md`'s rule — *capture and placement are separate acts; any code path that
|
||||||
|
auto-inserts a capture into the timeline must be rejected in review* — is a rule **about
|
||||||
|
the bank**, and Phase Ρ does not put the bank on either side of its verb. The full argument
|
||||||
|
is `docs/product/render-in-place.md` §"The third verb"; the operative summary, which every
|
||||||
|
review of this phase must apply:
|
||||||
|
|
||||||
|
| Verb | Source | Sink | Touches the bank |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **Capture** (`RunCapture`, batch, realtime, bake, ingest) | arrange / instrument | bank | writes it |
|
||||||
|
| **Placement** (`RunInsertSelected`, `performArrangeDrop`) | bank | arrange | reads it |
|
||||||
|
| **Render in place** (this phase) | arrange | arrange | never |
|
||||||
|
|
||||||
|
What Ρ shares with capture is the **render** — `renderOffline`, `FxBypassGuard`, exact
|
||||||
|
custom time bounds, `RENDER_ADDTOPROJ = 0`, the multi-track refusal — not the capture. A
|
||||||
|
capture is a render *plus* a bank landing; Ρ takes the mechanism and declines the landing.
|
||||||
|
|
||||||
|
**Four boundary conditions, three of them structural, all review-rejectable:**
|
||||||
|
|
||||||
|
1. Ρ's shell never calls `session.bank()`, `session.book()`, `session.recordCreated()`, or
|
||||||
|
`session.bumpBankGeneration()`. The `Sample` the backend returns is discarded, and on the
|
||||||
|
`ProjectMedia` destination its `relativePath` is left **empty** — a Ρ `Sample` is inert
|
||||||
|
by construction.
|
||||||
|
2. **Ρ cannot express "write into the bank folder."** The destination reaches the backend as
|
||||||
|
a two-valued enum, never a caller-supplied path string. A `renderDir` string on
|
||||||
|
`CaptureRequest` instead of the enum **is** the drift, and is rejected on sight.
|
||||||
|
3. Ρ's file is never recorded as owned, so prune (`(owned ∩ present) − referenced`) cannot
|
||||||
|
reach it — and it lives outside the bank folder, so prune's enumeration never sees it
|
||||||
|
either. Two independent layers. The tool deletes only what it owns; a render-in-place
|
||||||
|
file belongs to the project.
|
||||||
|
4. **Traffic is one-way.** Ρ may borrow capture's render; **capture may never borrow Ρ's
|
||||||
|
placement.** No `place` flag on `CaptureActionDef`, no "capture and also place" action,
|
||||||
|
ever.
|
||||||
|
|
||||||
|
### Phase-Ρ acceptance criteria
|
||||||
|
|
||||||
|
These bind the track in this phase, in addition to the plan-wide set above.
|
||||||
|
|
||||||
|
- **Placement is sample-exact and unsnapped.** The item lands at the render window's
|
||||||
|
`startSeconds`, unrounded, with `SnapToGrid` deliberately **not** applied (unlike
|
||||||
|
`performArrangeDrop`). Ρ's placement is the null test performed automatically — a render
|
||||||
|
of a range re-inserted at its source position nulls to silence against the source — so a
|
||||||
|
snapped or rounded placement is a phase failure, not a rough edge.
|
||||||
|
- **No tempo conform, ever.** `computeInsertMode(InsertOptions{})` only; `insert_plan`
|
||||||
|
already guarantees the &4 stretch-to-time-selection bit is never set. No conform variant
|
||||||
|
of this action is offered.
|
||||||
|
- **The bank path is byte-identical to today.** The `CaptureDestination` enum defaults to
|
||||||
|
`Bank`; every existing capture entry point must produce exactly the file, path, hash, and
|
||||||
|
index entry it produces now. If any capture test changes expectation, the seam is wrong.
|
||||||
|
- **The new track is bare.** `InsertTrackInProject(proj, p, /*flags=*/0)` — flags&1 adds
|
||||||
|
default envelopes/FX (SDK header 3954) and a default chain would process the render a
|
||||||
|
second time.
|
||||||
|
- **Both `I_FOLDERDEPTH` writes, or none.** The sibling-placement arithmetic is pure and
|
||||||
|
unit-tested before any DAW work; a render that lands the new track at the wrong nesting
|
||||||
|
level is audibly wrong in both directions (double-processed through a folder it re-enters,
|
||||||
|
or bypassing the folder bus entirely).
|
||||||
|
- **The new track is tagged BEFORE the mode reapply.** An untagged track is an Arrange
|
||||||
|
member by default, so a reapply that runs first parks the brand-new capture track when
|
||||||
|
Design is active. The auto-tag poller's later observation must be idempotent, not
|
||||||
|
load-bearing.
|
||||||
|
- **One undo block, `UNDO_STATE_ALL`,** opened before the track is created and closed after
|
||||||
|
the mode reapply; the render sits outside it. `persistViewState` runs **after** the block
|
||||||
|
closes — it may raise a Save-As dialog, which must not sit inside an open undo block
|
||||||
|
(`design_view_actions::doMoveItems`' documented ordering).
|
||||||
|
- **Every pure module gets a `<module>_tests` target.** All three pure additions land in
|
||||||
|
existing `core/capture` modules that already have one.
|
||||||
|
|
||||||
|
**Performance posture.** Every surface is cold — one gesture, once. None of the named hot
|
||||||
|
paths (peaks envelope compute, audition, the realtime-capture tick's single-pointer-test
|
||||||
|
idle fast path, the instrument's `process()`) is touched.
|
||||||
|
|
||||||
|
**Concurrency.** Phase Ρ is extension-side. Γ lives in `core/instrument/` +
|
||||||
|
`shell/instrument/`; Ε lands in the new `core/package/` + `shell/package/`. The pre-existing
|
||||||
|
files Ρ edits are named in its track's surface boundary and intersect neither.
|
||||||
|
|
||||||
|
### Open forks — Daniel's. None gates a dispatch.
|
||||||
|
|
||||||
|
The track below is specced against the **recommended** answer in each case, so a ruling that
|
||||||
|
agrees changes nothing and a ruling that disagrees changes one named paragraph. Full
|
||||||
|
statements with counter-arguments: `docs/product/render-in-place.md` §"Open forks".
|
||||||
|
|
||||||
|
- **Ρ-F1 — multi-track.** *Recommendation: inherit the existing refusal* (one track per
|
||||||
|
fire). The stem-collapse hazard would not actually apply to a per-track loop, so the
|
||||||
|
counter is real; what it costs is bookkeeping (indices shift per insertion, partial
|
||||||
|
failure, undo label). If Daniel wants it, the honest shape is a **second wave**, not a
|
||||||
|
bigger first one.
|
||||||
|
- **Ρ-F2 — the new track's mode when Design is active.** *Recommendation: to Design, as
|
||||||
|
Daniel's rule states.* The alternative is an absolute rule (source always → Design,
|
||||||
|
capture always → Arrange). The relative rule wins on Daniel's explicit words and on the
|
||||||
|
A/B-on-the-bench use the absolute rule cannot express; it does mean firing Ρ from Design
|
||||||
|
puts nothing into the arrangement.
|
||||||
|
- **Ρ-F3 — tail.** *Recommendation: follow the panel setting.* Under Auto/Manual the placed
|
||||||
|
item is longer than the window it replaces (correct for a decaying chain) and the
|
||||||
|
exact-bounds gate is inactive (it runs only under `TailMode::None`) — inherited, not
|
||||||
|
introduced. A third option is named in the product doc: keep the panel setting but run the
|
||||||
|
gate's start-alignment check regardless of tail mode.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Ρ-W1 — The verb
|
||||||
|
|
||||||
|
**Depends on:** nothing.
|
||||||
|
|
||||||
|
**One track, deliberately.** The whole phase is roughly 350 lines: three small pure
|
||||||
|
additions to existing `core/capture` modules, one bounded edit to the offline backend, one
|
||||||
|
new shell TU, one `ActionTableRow`, two invariant amendments. Splitting it would create a
|
||||||
|
merge dance across `core/capture` for no gain, and the pure half cannot be reviewed
|
||||||
|
meaningfully apart from the caller that gives it meaning. **Named contingency:** if the
|
||||||
|
sibling-placement arithmetic balloons in implementation, that function is the natural split
|
||||||
|
point — it is the only piece with zero dependency on anything else in the track.
|
||||||
|
|
||||||
|
#### Ρ-W1-T1 — `render-in-place`
|
||||||
|
|
||||||
|
**Goal.** One bindable action: render the selected track's output over the current range to
|
||||||
|
the project's recording path, place it on a new sibling track at the exact render position,
|
||||||
|
clone colour and name, move the source to Design and the new track to the active mode.
|
||||||
|
|
||||||
|
**Spec:** `docs/product/render-in-place.md` — §"The render", §"Where the file goes",
|
||||||
|
§"Placement", §"The new track", §"Mode transitions", §"Undo", §"The action".
|
||||||
|
|
||||||
|
**Surface boundary — owns:**
|
||||||
|
- **New:** `src/shell/capture/render_in_place.{h,cpp}` (the action body) and its
|
||||||
|
`CMakeLists.txt` entry. It lives in `shell/capture/` rather than `shell/actions/` because
|
||||||
|
it composes `renderOffline` and `ResolveScopeSource` — that directory's `CLAUDE.md` places
|
||||||
|
action *bodies* here and reserves `shell/actions` for skins over mutation logic owned
|
||||||
|
elsewhere.
|
||||||
|
- **Extends (existing modules, existing test targets):** `core/capture/track_topology`
|
||||||
|
(`siblingPlacement`), `core/capture/capture_name` (`captureTrackName`),
|
||||||
|
`core/capture/capture_paths` (`RenderPaths` + `deriveRenderPaths`, with `deriveBankPaths`
|
||||||
|
re-expressed over it so the file-stem spelling keeps one owner —
|
||||||
|
`bankRelativeForName` already depends on that).
|
||||||
|
- **Edits, bounded:** `src/shell/capture/capture.{h,cpp}` — the `CaptureDestination` enum on
|
||||||
|
`CaptureRequest`, the ~6-line destination branch at path derivation, and
|
||||||
|
`CaptureResult::absolutePath`. `src/app/main.cpp` — one `ActionTableRow`.
|
||||||
|
- **Does not own:** anything under `core/instrument/`, `shell/instrument/`, `core/package/`,
|
||||||
|
`shell/package/`, `core/model/`, `core/tracking/`, `core/reclaim/`, or `shell/persist/`.
|
||||||
|
No new directory. No new persisted state, no new ext-state key, no new wire version rung.
|
||||||
|
|
||||||
|
**Behavior.**
|
||||||
|
- **Resolve** via `ResolveScopeSource(CaptureScope::Track, …)` — razor-else-time range,
|
||||||
|
selected tracks, canonical GUIDs, source track name. No range → refuse with the reason
|
||||||
|
`resolveRange` produced. **Item extent is not a fallback and must not become one.**
|
||||||
|
- **Refuse multi-track by inheritance.** `renderOffline` fires `isMultiTrackStemRender`
|
||||||
|
before touching anything, keyed on the render *source* (`SelectedTracks`), so Ρ adds no
|
||||||
|
check of its own and gets `multiTrackRefusalMessage(CaptureScope::Track)` for free.
|
||||||
|
- **Render** through `renderOffline(CaptureScope::Track, src.sourceTracks, req)` with
|
||||||
|
`req.destination = ProjectMedia`, tail from `bankPanelTailSetting()`, `channelCount = 2`,
|
||||||
|
`Float32`, `sampleRate = 0`, and the name from `captureNameFor(src.trackNames, 0,
|
||||||
|
"capture")`. Inherits the FX-bypass guard, the render-selection guard, the `RENDER_*`
|
||||||
|
snapshot/restore, `RENDER_ADDTOPROJ = 0`, the exact-bounds gate, the unsaved-project
|
||||||
|
Save-As gate, and the lossless mono collapse — **all unchanged**.
|
||||||
|
- **Destination resolves in the backend, after its own save gate**, so an unsaved project is
|
||||||
|
still prompted before any path arithmetic runs. `ProjectMedia` → `GetProjectPathEx(proj,
|
||||||
|
…)` (SDK header 2550; header 3102 names it as the way to get the *effective* recording
|
||||||
|
path when `RECORD_PATH` is blank or relative). The relative-paths-only invariant is
|
||||||
|
untouched — it binds the `BankIndex`, and Ρ writes to no index.
|
||||||
|
- **Sibling placement** (pure, in `track_topology`): prefix-sum `I_FOLDERDEPTH` to absolute
|
||||||
|
levels; `L = level[srcIdx]`; if `depth[srcIdx] >= 1` the insert position `p` is the first
|
||||||
|
`j > srcIdx` with `level[j] == L` (else `count`), otherwise `p = srcIdx + 1`; then exactly
|
||||||
|
two writes — `depth[p-1] = L - level[p-1]` and the new track's `depth = level[p] - L`
|
||||||
|
(with `level[count] = 0`). Total delta sum preserved, so nothing downstream shifts. The
|
||||||
|
five cases and their expected outcomes are tabulated in the product doc; a malformed
|
||||||
|
project whose deltas do not sum to zero clamps rather than asserts.
|
||||||
|
- **Create + dress:** `InsertTrackInProject(proj, p, 0)`, `GetTrack(proj, p)`;
|
||||||
|
`SetMediaTrackInfo_Value(new, "I_CUSTOMCOLOR", (double)GetTrackColor(source))` — one line
|
||||||
|
clones a colour and the absence of one, since `GetTrackColor` returns the value already
|
||||||
|
OR'd with `0x1000000` and `0` means unset; `GetSetMediaTrackInfo_String(new, "P_NAME",
|
||||||
|
buf, true)` with `captureTrackName(trackName(source))`.
|
||||||
|
- **Name rule (pure, tested):** `"Capture " + sourceName`, **idempotent** — if the source
|
||||||
|
name already begins with the prefix, the new name is the source name verbatim, so a second
|
||||||
|
run yields `Capture MONEY`, never `Capture Capture MONEY`. An unnamed source yields
|
||||||
|
`Capture Track N` (`trackName` uses `GetTrackName`, which already answers REAPER's own
|
||||||
|
convention — the Ψ-W2-T1 precedent). A counter suffix is rejected: REAPER does not
|
||||||
|
uniquify track names either, and it is a treadmill.
|
||||||
|
- **Place:** snapshot the edit cursor → `SetOnlyTrackSelected(new)` →
|
||||||
|
`SetEditCurPos(src.startSeconds, false, false)` → `InsertMedia(absolutePath,
|
||||||
|
computeInsertMode(InsertOptions{}))` → restore the cursor. **Leave the new track selected,
|
||||||
|
alone** — a deliberate divergence from the restore-the-selection convention every other
|
||||||
|
placing path follows, because in the headline case the source is being parked out of sight
|
||||||
|
in the same gesture and restoring the selection would leave the user selecting an
|
||||||
|
invisible track.
|
||||||
|
- **Modes:** `membership().tag(sourceGuid, kDesignModeId)` (covers stays *and* goes — `tag`
|
||||||
|
replaces prior single-mode membership); `membership().tag(newTrackGuid,
|
||||||
|
view.activeModeId())`; then `mintManagedLanes(view, nullptr)`; then `applyMode(view,
|
||||||
|
view.activeModeId(), nullptr)` — a reapply, never a switch, so it is not transport-gated
|
||||||
|
and does not touch solo. **Tag order is load-bearing** (see acceptance criteria).
|
||||||
|
- **Inherited and deliberately not fought:** show-both on the source is not cleared (it is
|
||||||
|
the user's pin); a folder-parent source is not hidden by tagging, because parents are
|
||||||
|
derived (`core/view/CLAUDE.md`) — the existing tag action behaves identically. **Do not
|
||||||
|
invent a cascade that tags the children.**
|
||||||
|
- **Feedback:** silent on success (the new track is the feedback), `ShowConsoleMsg` on every
|
||||||
|
refusal — matching `RunInsertSelected`.
|
||||||
|
- **Action:** suffix **`RENDER_TRACK_IN_PLACE`** — FOREVER-STABLE per channel, minted as a
|
||||||
|
new `RENDER_*` verb family rather than a `CAPTURE_*` member, deliberately: the id is
|
||||||
|
permanent and is the most durable statement the codebase makes about which pillar a
|
||||||
|
feature belongs to. Phrase: **`"render selected track to a new track (source moves to
|
||||||
|
Design)"`**. Main section only; one `ActionTableRow`; no `custom_action`/`hookcommand2`.
|
||||||
|
|
||||||
|
**Invariant amendments — deliverables of this track, not follow-ups** (the Phase Ψ
|
||||||
|
precedent, where three such amendments were acceptance criteria of the tracks that broke
|
||||||
|
them). A track that lands Ρ without these reads as an invariant breach in review.
|
||||||
|
|
||||||
|
1. `src/shell/capture/CLAUDE.md` §Invariants — *"`RunInsertSelected` is the one deliberate
|
||||||
|
exception to capture-never-places."* Amend to state that this directory now hosts two
|
||||||
|
placing paths and give the discriminator: `RunInsertSelected` places a *bank sample*;
|
||||||
|
`render_in_place` places a render that never entered the bank. Neither is a capture
|
||||||
|
placing itself.
|
||||||
|
2. `src/shell/actions/CLAUDE.md` §Invariants — *"`arrange_drop_win` is the only
|
||||||
|
timeline-placing shell in this directory."* Scope the sentence explicitly to that
|
||||||
|
directory and cross-reference the third verb.
|
||||||
|
3. Root `CLAUDE.md` §"The load-bearing principle" — **one sentence, not a rewrite**: a
|
||||||
|
render that never enters the bank and never leaves it is a third verb outside the rule,
|
||||||
|
with the two-way boundary named. The prohibition must not be softened; the exception must
|
||||||
|
be named precisely.
|
||||||
|
|
||||||
|
**Acceptance criteria.**
|
||||||
|
- `siblingPlacement` is unit-tested over all five cases in the product doc's table (normal
|
||||||
|
mid-folder, last-in-folder `-1`, last-in-two-folders `-2`, folder parent, last track in
|
||||||
|
the project) plus a malformed non-zero-sum delta list, with no DAW.
|
||||||
|
- `captureTrackName` is unit-tested for plain, already-prefixed (idempotence), empty, and
|
||||||
|
`Track N` sources.
|
||||||
|
- `deriveRenderPaths` round-trips the same stem spelling `deriveBankPaths` produces for the
|
||||||
|
same inputs, and the existing `capture_paths` tests pass unchanged.
|
||||||
|
- Every existing capture test passes with **no expectation change** — the proof that the
|
||||||
|
destination seam is inert on the `Bank` path.
|
||||||
|
- `render_in_place.cpp` contains no reference to `session.bank()`, `session.book()`,
|
||||||
|
`recordCreated`, or `bumpBankGeneration` — checkable by grep, and the review gate for
|
||||||
|
boundary condition 1.
|
||||||
|
- The action registers, dispatches, and mirror-unregisters through the single
|
||||||
|
`ActionTableRow` — no separate registration mechanism.
|
||||||
|
- All three invariant amendments are in the diff.
|
||||||
|
|
||||||
|
**DAW-verification obligation** (stated up front; nothing past the pure functions is
|
||||||
|
unit-testable):
|
||||||
|
- **The null test on Ρ's own output** — render a track over a range, polarity-invert the
|
||||||
|
source against the new track, confirm silence. The phase's trust anchor.
|
||||||
|
- **The three folder cases**, each confirming the new track's nesting level and that the
|
||||||
|
render feeds (or correctly bypasses) the folder bus. `[verify — DAW]` whether
|
||||||
|
`InsertTrackInProject` plus the two `I_FOLDERDEPTH` writes settle without an intermediate
|
||||||
|
`TrackList_AdjustWindows(false)` (SDK header 7735; header 2721 notes some attribute writes
|
||||||
|
need a manual panel update, and the `isMinor` semantics are undocumented).
|
||||||
|
- **The collapsed-mono placement** — render a dead-centre source, confirm a mono item, and
|
||||||
|
confirm it sums at the same level the stereo source did. This is root `CLAUDE.md`'s
|
||||||
|
existing `[verify — DAW]` on mono-item-on-stereo-track summing, **promoted to
|
||||||
|
load-bearing by this phase**: Ρ is the first path where a collapsed render is placed into
|
||||||
|
the mix by the tool itself.
|
||||||
|
- **Both mode transitions** (fired from Arrange; fired from Design), plus the ordering check
|
||||||
|
that the new track is never momentarily parked.
|
||||||
|
- **Undo** — one Ctrl-Z removes track and item and reverts the folder-depth write; the file
|
||||||
|
survives (REAPER's undo deletes no files, and prune cannot reach this one); the source
|
||||||
|
stays tagged Design, whose way out is the existing *tag selected tracks → Arrange* action.
|
||||||
|
All three residuals are inherited from the documented model-vs-undo split
|
||||||
|
(`src/shell/view/CLAUDE.md` §Gotchas), not introduced here.
|
||||||
|
- **Name and colour clone**, including a second run over an already-prefixed track (must not
|
||||||
|
stack) and an unnamed source (must read `Capture Track N`).
|
||||||
|
- **`GetProjectPathEx` against a project with a non-default recording path**, confirming the
|
||||||
|
render lands where the project's media lives.
|
||||||
|
|
||||||
|
**Open questions.** The three [Daniel] forks above (Ρ-F1/F2/F3), none gating. **[propose at
|
||||||
|
review]** whether `render_in_place` should refuse when the source track is the master —
|
||||||
|
`ResolveScopeSource` collects via `CountSelectedTracks`/`GetSelectedTrack`, which skip the
|
||||||
|
master (SDK header), so a master-only selection already resolves to zero tracks and refuses
|
||||||
|
with "nothing selected"; the recommendation is to leave that inherited behaviour alone
|
||||||
|
rather than add a message for a case the existing path already handles correctly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Traceability — all seventeen items
|
## Traceability — all seventeen items
|
||||||
|
|
||||||
The check that nothing was dropped. Every row points at a track that exists above.
|
The check that nothing was dropped. Every row points at a track that exists above.
|
||||||
@@ -2815,9 +3172,17 @@ proof it exists to give.
|
|||||||
(Daniel, 2026-08-02), not from `TODO-1.0.md`. Listed here as a block, like Γ and Ψ; the
|
(Daniel, 2026-08-02), not from `TODO-1.0.md`. Listed here as a block, like Γ and Ψ; the
|
||||||
product reasoning lives in `docs/product/bank-package.md`. It **supersedes nothing** —
|
product reasoning lives in `docs/product/bank-package.md`. It **supersedes nothing** —
|
||||||
neither `docs/TODO.md` nor `docs/TODO-1.0.md` records export, import, or a package format,
|
neither `docs/TODO.md` nor `docs/TODO-1.0.md` records export, import, or a package format,
|
||||||
so there is no deferred entry to absorb or contradict. It is also the only phase in this
|
so there is no deferred entry to absorb or contradict. Its three [Daniel]-class forks
|
||||||
plan that opens with unanswered [Daniel]-class forks rather than closing them; see
|
(Ε-F1/F2/F3) were opened and ruled the same day it was framed, so no track here is gated on
|
||||||
"Decision state" above.
|
a decision; see "Decision state" above.
|
||||||
|
- **All of Phase Ρ** (`pr-*`). **One track in one wave**, from a direct request (Daniel,
|
||||||
|
2026-08-02), not from `TODO-1.0.md`; the product reasoning lives in
|
||||||
|
`docs/product/render-in-place.md`. It **supersedes nothing** — a sweep of `docs/TODO.md`
|
||||||
|
and `docs/TODO-1.0.md` for `render.in.place|render to new track|preserve.source` returns
|
||||||
|
nothing. The smallest phase in this plan, deliberately: it is a thin third verb composed
|
||||||
|
out of machinery that already exists, and the burden was on any new machinery to prove it
|
||||||
|
unavoidable. Its three [Daniel]-class forks (Ρ-F1/F2/F3) are **open**, and none gates the
|
||||||
|
dispatch — the track is specced against the recommended answer in each case.
|
||||||
|
|
||||||
### Deliberate compressions
|
### Deliberate compressions
|
||||||
|
|
||||||
@@ -2934,14 +3299,15 @@ Phase Psi — The extension trust pass (none of the seventeen; a direct
|
|||||||
Phase Epsilon — The bank package (none of the seventeen; a direct request 2026-08-02)
|
Phase Epsilon — The bank package (none of the seventeen; a direct request 2026-08-02)
|
||||||
W1 The contract, the filesystem, and the ledger's new kind [3 tracks, disjoint by dir]
|
W1 The contract, the filesystem, and the ledger's new kind [3 tracks, disjoint by dir]
|
||||||
T1 package-format .............. core/package: framing + TWO version ints
|
T1 package-format .............. core/package: framing + TWO version ints
|
||||||
[BLOCKED until E-F1 is ruled — Daniel]
|
[E-F1 RULED: proprietary RSBK. No ZIP, no zlib]
|
||||||
T2 package-fs-shell ............ shell/package: atomic write, streaming, pickers
|
T2 package-fs-shell ............ shell/package: atomic write, streaming, pickers
|
||||||
[no REAPER save-picker exists; SWELL/Win32 split]
|
[no REAPER save-picker exists; SWELL/Win32 split]
|
||||||
T3 import-origin-kind .......... OriginKind::PackageImport = 5, append-only
|
T3 import-origin-kind .......... OriginKind::PackageImport = 5, append-only
|
||||||
W2 The two verbs [2 tracks; disjointness CONDITIONAL — see below]
|
W2 The two verbs [2 tracks; disjointness CONDITIONAL — see below]
|
||||||
T1 bank-export ................. export_plan + verb + action; project untouched
|
T1 bank-export ................. export_plan + verb + action; project untouched
|
||||||
T2 bank-import ................. import_plan + verb + action + .rsbank drop
|
T2 bank-import ................. import_plan + verb + action + .rsbank drop
|
||||||
[OPEN: E-F2 import target, E-F3 degraded ledger]
|
[E-F2 RULED: always a NEW bank, auto-suffix, no
|
||||||
|
merge. E-F3 RULED: REFUSE on degraded ledger]
|
||||||
W3 The compatibility fixtures [1 track]
|
W3 The compatibility fixtures [1 track]
|
||||||
T1 package-compat-fixtures ..... frozen bytes prove BOTH version directions
|
T1 package-compat-fixtures ..... frozen bytes prove BOTH version directions
|
||||||
|
|
||||||
@@ -2949,8 +3315,38 @@ Phase Epsilon — The bank package (none of the seventeen; a direct reque
|
|||||||
fields default, unknown keys skipped); newer package in older build REFUSES WHOLE with a
|
fields default, unknown keys skipped); newer package in older build REFUSES WHOLE with a
|
||||||
three-part message (package needs / this build reads / writer semver). The gate is
|
three-part message (package needs / this build reads / writer semver). The gate is
|
||||||
minReaderVersion <= kPackageFormatVersion — formatVersion is for the message, not the gate.
|
minReaderVersion <= kPackageFormatVersion — formatVersion is for the message, not the gate.
|
||||||
|
All three E-forks were ruled 2026-08-02, the day the phase was framed: NO track here is
|
||||||
|
gated on a decision. Import name collision = first free of seed, "seed 2", "seed 3", ...
|
||||||
|
seed never re-parsed, fold is BankBook's own.
|
||||||
Shared files, named: root CMakeLists.txt add_subdirectory list (W1-T1 | W1-T2, one
|
Shared files, named: root CMakeLists.txt add_subdirectory list (W1-T1 | W1-T2, one
|
||||||
append-only line each); main.cpp + the panel bank menu (W2-T1 | W2-T2, one registration
|
append-only line each); main.cpp + the panel bank menu (W2-T1 | W2-T2, one registration
|
||||||
line and one menu row each). W1's three tracks are unconditionally disjoint; W2's two are
|
line and one menu row each); bank_book.{h,cpp} is W2-T2's alone (one additive public const
|
||||||
|
member). W1's three tracks are unconditionally disjoint; W2's two are
|
||||||
textually adjacent only — serialize T2 behind T1 if zero contention is wanted.
|
textually adjacent only — serialize T2 behind T1 if zero contention is wanted.
|
||||||
|
|
||||||
|
Phase Rho — Render in place (none of the seventeen; a direct request 2026-08-02)
|
||||||
|
W1 The verb [ONE track, deliberately]
|
||||||
|
T1 render-in-place ............. render selected track -> new sibling track,
|
||||||
|
item placed at the exact render position,
|
||||||
|
colour + "Capture " name cloned, source -> Design.
|
||||||
|
NEVER touches the bank.
|
||||||
|
[OPEN, none gating: R-F1 multi-track, R-F2 the
|
||||||
|
new track's mode from Design, R-F3 tail]
|
||||||
|
|
||||||
|
The THIRD VERB. Capture = arrange -> bank (writes it). Placement = bank -> arrange
|
||||||
|
(reads it). Render in place = arrange -> arrange (bank on neither side). That is why the
|
||||||
|
load-bearing capture/placement rule survives it; the full argument is in the phase header
|
||||||
|
and in docs/product/render-in-place.md.
|
||||||
|
Structural boundary, not a convention: the render destination reaches the backend as a
|
||||||
|
TWO-VALUED ENUM (Bank | ProjectMedia), never a caller-supplied path — so no Rho caller can
|
||||||
|
name the bank folder. A renderDir string on CaptureRequest IS the drift.
|
||||||
|
Genuinely new: ONE pure sibling-placement function (folder-parent and last-in-folder are
|
||||||
|
both audibly wrong if inserted at srcIdx+1), an idempotent "Capture " prefix, a
|
||||||
|
deriveRenderPaths sibling, and CaptureResult::absolutePath. Everything else composes.
|
||||||
|
Three invariant amendments are track deliverables (the Psi precedent): shell/capture and
|
||||||
|
shell/actions CLAUDE.md placing-path claims, plus ONE sentence in root CLAUDE.md naming
|
||||||
|
the third verb without softening the prohibition.
|
||||||
|
Shared files, named: capture.{h,cpp} (destination enum + result field) and main.cpp (one
|
||||||
|
ActionTableRow) — the only pre-existing shell files touched. Disjoint from Gamma
|
||||||
|
(core+shell/instrument) and Epsilon (core+shell/package).
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user