docs: retire Phase Rho from the plan, record it as landed
This commit is contained in:
@@ -887,3 +887,58 @@ wrong that refusal costs a working capture. Each track's DAW-verification obliga
|
|||||||
is recorded in `docs/PLAN.md`'s Phase Ψ section; `docs/verify-track-scope-multitrack.md`
|
is recorded in `docs/PLAN.md`'s Phase Ψ section; `docs/verify-track-scope-multitrack.md`
|
||||||
is a new standalone verification script on this branch, for Ψ-W3-T1's multi-track
|
is a new standalone verification script on this branch, for Ψ-W3-T1's multi-track
|
||||||
refusal specifically. No human has observed any of these seven behaviors in a DAW.
|
refusal specifically. No human has observed any of these seven behaviors in a DAW.
|
||||||
|
|
||||||
|
### Phase Ρ — Render in place: a track's output to a new sibling, source to the bench
|
||||||
|
|
||||||
|
One wave, one track (Ρ-W1-T1 `render-in-place`), code-complete, reviewed, remediated, and
|
||||||
|
merged to `dev` as `b400384`: 91/91 tests passing, a clean build. Phase Ρ came from a
|
||||||
|
direct request (Daniel, 2026-08-02) rather than a backing product doc list — see
|
||||||
|
`docs/product/render-in-place.md` for the framing and its three [Daniel]-class forks
|
||||||
|
(Ρ-F1/F2/F3), all ruled the day the phase was framed.
|
||||||
|
|
||||||
|
**Ρ-W1-T1 — `render-in-place`.** One bindable action, `RENDER_TRACK_IN_PLACE`, renders
|
||||||
|
the selected track's output over the current range to the project's recording path —
|
||||||
|
never the bank — places it as an item on a brand-new sibling track at the exact unsnapped
|
||||||
|
render position, clones the source's colour and its name with an idempotent `Capture `
|
||||||
|
prefix, moves the source track to Design mode, and puts the result track into Arrange
|
||||||
|
unconditionally (the Ρ-F2 ruling). New `src/shell/capture/render_in_place.{h,cpp}`.
|
||||||
|
Extended `core/capture/track_topology` (`siblingPlacement`), `core/capture/capture_name`
|
||||||
|
(`captureTrackName`), `core/capture/capture_paths` (`RenderPaths`/`deriveRenderPaths`,
|
||||||
|
with `deriveBankPaths` re-expressed over it). A `CaptureDestination` enum was added to
|
||||||
|
`CaptureRequest`; `render_bounds_gate` became destination-aware. A filter added to
|
||||||
|
`panel_input::detectNewContent`, one `ActionTableRow` in `src/app/main.cpp`. All four
|
||||||
|
invariant amendments the plan required (`src/shell/capture/CLAUDE.md`,
|
||||||
|
`src/shell/actions/CLAUDE.md`, root `CLAUDE.md` §"The load-bearing principle",
|
||||||
|
`src/core/view/CLAUDE.md`) landed inline with the track.
|
||||||
|
|
||||||
|
**Four deviations worth recording:**
|
||||||
|
|
||||||
|
1. **The `activeModeId` acceptance criterion was met in spirit, not to the letter.** The
|
||||||
|
criterion said `activeModeId()` must appear only in the `applyMode` reapply. The
|
||||||
|
implementer added the spec-recommended one-line Design-fired `ShowConsoleMsg`, which
|
||||||
|
requires reading the active mode, and hoisted that read into a single named local
|
||||||
|
shared by the message condition and the reapply. All three `tag()` calls still take
|
||||||
|
literal mode ids, so the ruling the criterion protects (Ρ-F2) holds. Review accepted
|
||||||
|
this explicitly.
|
||||||
|
2. **The `panel_input` edit was larger than the spec's estimate** — the spec budgeted
|
||||||
|
"two lines only"; the landed change is six lines plus an `<algorithm>` include and
|
||||||
|
dropping a `const`, still confined to `detectNewContent`.
|
||||||
|
3. **`TrackList_AdjustWindows(false)` was included preemptively** where the spec had
|
||||||
|
asked to `[verify — DAW]` whether it is needed. Consequence worth recording: the DAW
|
||||||
|
check can no longer distinguish, so answering that question now requires commenting
|
||||||
|
the call out locally.
|
||||||
|
4. **A behavioural change beyond Ρ's stated scope**, surfaced in review and judged an
|
||||||
|
improvement: a track restored by undo now keeps its original mode instead of being
|
||||||
|
re-tagged to the active mode. Its reach is narrower than it sounds —
|
||||||
|
`ViewModeModel::reconcile` prunes records for GUIDs that have gone away, so a track
|
||||||
|
absent across a reconcile pass still falls back to the old behaviour.
|
||||||
|
|
||||||
|
Both **[propose at review]** items resolved to the plan's own recommendations: the
|
||||||
|
Design-fired console message was added (yes), and no master-track refusal was added
|
||||||
|
(no — `ResolveScopeSource` already refuses a master-only selection).
|
||||||
|
|
||||||
|
**The entire DAW-verification obligation remains outstanding.** The null test on Ρ's
|
||||||
|
own output, the three folder cases, collapsed-mono placement and summing, both mode
|
||||||
|
transitions waited out past a panel timer tick, undo, name/colour clone, and
|
||||||
|
`GetProjectPathEx` against a non-default recording path — none of it is unit-testable
|
||||||
|
and none has been run.
|
||||||
|
|||||||
+6
-369
@@ -91,8 +91,9 @@ decision.**
|
|||||||
RULED**, same day (Daniel, 2026-08-02): **Ρ-F1** multi-track — *"refuse"*, one track per
|
RULED**, same day (Daniel, 2026-08-02): **Ρ-F1** multi-track — *"refuse"*, one track per
|
||||||
fire, a settled non-goal rather than a deferral; **Ρ-F2** the result track's mode —
|
fire, a settled non-goal rather than a deferral; **Ρ-F2** the result track's mode —
|
||||||
*"for this action which is not a capture, the result track should always go to
|
*"for this action which is not a capture, the result track should always go to
|
||||||
arrange"*; **Ρ-F3** tail — *"follow panel tail settings."* The rulings are folded into
|
arrange"*; **Ρ-F3** tail — *"follow panel tail settings."* Ρ-W1-T1 has landed; see
|
||||||
the track below and indexed at `docs/product/render-in-place.md` §"Rulings". **Ρ-F2
|
`docs/COMPLETED.md` for the full narrative, and the rulings remain indexed at
|
||||||
|
`docs/product/render-in-place.md` §"Rulings". **Ρ-F2
|
||||||
overrode the request's own original wording** ("stays in whatever mode was active") and
|
overrode the request's own original wording** ("stays in whatever mode was active") and
|
||||||
is the only one of the three that changed the spec: the result track is now an Arrange
|
is the only one of the three that changed the spec: the result track is now an Arrange
|
||||||
member unconditionally, the A/B-on-the-bench behaviour mode-following would have enabled
|
member unconditionally, the A/B-on-the-bench behaviour mode-following would have enabled
|
||||||
@@ -2891,372 +2892,6 @@ 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, 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 — and puts
|
|
||||||
the result track into Arrange, unconditionally. 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.* **The
|
|
||||||
mode-following clause in that framing was superseded by Daniel's own Ρ-F2 ruling the same
|
|
||||||
day** — *"for this action which is not a capture, the result track should always go to
|
|
||||||
arrange"* — and the quote is kept verbatim only as the record of the request. 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 Ρ-F2 ruling survives the auto-tag detector.** The result track and its placed
|
|
||||||
item are tagged `kArrangeModeId` **explicitly** (a membership record, not an `untag()`
|
|
||||||
to the Arrange default), and `panel_input::detectNewContent` drops added GUIDs that
|
|
||||||
already carry a record. Without that filter the detector tags every new track to the
|
|
||||||
active mode on its next tick and a Design-fired render silently becomes a Design
|
|
||||||
member — the ruling reversed inside a second. This criterion **replaces** the
|
|
||||||
tag-before-reapply ordering criterion the phase carried under mode-following: with the
|
|
||||||
result track an Arrange member unconditionally, a Design reapply parking it is the
|
|
||||||
correct outcome and the ordering is state hygiene, not behaviour.
|
|
||||||
- **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 — including
|
|
||||||
`shell/panel/panel_input.cpp`, which the Ρ-F2 ruling adds: no in-flight track in this plan
|
|
||||||
touches that file (Phase Ψ's two named regions in it, the footer block and the drag-arm
|
|
||||||
block, are both landed, and both are functions other than `detectNewContent`).
|
|
||||||
|
|
||||||
### Rulings — Daniel's, 2026-08-02. Nothing open.
|
|
||||||
|
|
||||||
All three forks this phase opened were ruled the day it was framed. Full statements, the
|
|
||||||
counter-arguments that made each a fork, and the one unexercised alternative:
|
|
||||||
`docs/product/render-in-place.md` §"Rulings".
|
|
||||||
|
|
||||||
- **Ρ-F1 — multi-track. RULED: refuse.** One selected track per fire, inherited from
|
|
||||||
`isMultiTrackStemRender`. A **settled non-goal**, in the same register as the other
|
|
||||||
entries under §"What Phase Ρ explicitly is NOT" — not deferred-with-a-plan. There is no
|
|
||||||
per-track loop planned, no second wave holding one, and no seam to leave half-open for
|
|
||||||
it. Matches the framing recommendation; nothing in the track changed.
|
|
||||||
- **Ρ-F2 — the result track's mode. RULED: always Arrange.** *"For this action which is
|
|
||||||
not a capture, the result track should always go to arrange."* **This overrode the
|
|
||||||
framing and the request's own original wording** — the result track no longer follows
|
|
||||||
the active mode. The source still goes to Design. Three consequences, all specced
|
|
||||||
below: the result track and its item are tagged `kArrangeModeId` explicitly; firing
|
|
||||||
from Design produces **no visible change** (the A/B-on-the-bench behaviour
|
|
||||||
mode-following would have enabled **does not exist** and must not be cited as a
|
|
||||||
benefit anywhere); and the panel's auto-tag detector needs a two-line
|
|
||||||
explicit-tag-wins filter, or it re-tags the result track to Design on its next timer
|
|
||||||
tick and silently reverses the ruling.
|
|
||||||
- **Ρ-F3 — tail. RULED: follow the panel tail settings.** Matches the framing
|
|
||||||
recommendation; nothing in the track changed. **Two consequences, accepted rather than
|
|
||||||
caveated:** under Auto/Manual the placed item is **longer than the window it replaces**
|
|
||||||
(correct for a decaying chain, wrong for a butt-joined section — the user's lever is
|
|
||||||
the panel's own tail setting), and the exact-bounds gate is **inactive** in those two
|
|
||||||
modes because it runs only under `TailMode::None`. Both are inherited from every other
|
|
||||||
capture path, not introduced here. The item's **start** is exact in all three modes, so
|
|
||||||
the null test holds in all three. The third option floated at framing (run the gate's
|
|
||||||
start-alignment check regardless of tail mode) is **not ruled in** and is recorded in
|
|
||||||
the product doc as an unexercised alternative.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Ρ-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, a two-line filter in `panel_input.cpp`, one `ActionTableRow`, four 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 result track to Arrange —
|
|
||||||
**always Arrange, whatever mode was active** (Ρ-F2).
|
|
||||||
|
|
||||||
**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/shell/panel/panel_input.cpp` — two lines inside
|
|
||||||
`detectNewContent` only, dropping added GUIDs that already carry a membership record
|
|
||||||
(`MembershipIndex::query(guid) != nullptr`) before the `autoTagNewContent` call. That
|
|
||||||
edit exists **only because of the Ρ-F2 ruling**. `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 (Ρ-F2, RULED — absolute, not mode-following):**
|
|
||||||
`membership().tag(sourceGuid, kDesignModeId)` (covers stays *and* goes — `tag` replaces
|
|
||||||
prior single-mode membership); `membership().tag(newTrackGuid, kArrangeModeId)` —
|
|
||||||
**`kArrangeModeId` unconditionally, never `view.activeModeId()`**; `membership().tag(…,
|
|
||||||
kArrangeModeId)` for **each item on the new track** (enumerate after `InsertMedia`; the
|
|
||||||
track is brand new so those are exactly the items just placed — `item_read::itemGuid` is
|
|
||||||
the existing GUID seam); 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.
|
|
||||||
- **Why explicit tags and not `untag()`:** an untagged GUID is an Arrange member by
|
|
||||||
behaviour but carries no membership record, and the record is what the auto-tag
|
|
||||||
detector's new filter keys on. Tag, don't untag. **This is the first explicit
|
|
||||||
`kArrangeModeId` record in the tree** — the shipped *tag selected tracks → Arrange*
|
|
||||||
action dispatches to `doUntag()`, i.e. Arrange-by-absence. The record is well-formed
|
|
||||||
and behaviourally identical (`isMember` answers the same for both states, `untag()`
|
|
||||||
still clears it); it costs one persisted entry. Unit-test the JSON round-trip;
|
|
||||||
`[verify — DAW]` that such a project shows no view-behaviour difference.
|
|
||||||
- **Fired from Design, the result track is parked and nothing is visible.** That is the
|
|
||||||
ruled behaviour, not a bug: the result track is an Arrange member, so a Design reapply
|
|
||||||
parks it, and it appears in the source's place on the next switch to Arrange. The
|
|
||||||
tag-before-reapply ordering that mode-following made load-bearing is now state hygiene
|
|
||||||
only.
|
|
||||||
- **Selection stays absolute too.** The result track is left selected, alone, in both
|
|
||||||
cases — even fired from Design, where that selects a parked track. Making selection
|
|
||||||
conditional on the active mode would reintroduce exactly the mode-relative behaviour
|
|
||||||
Ρ-F2 removed.
|
|
||||||
- **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`. **[propose at review]** whether the Design-fired
|
|
||||||
path breaks that silence with a one-line `ShowConsoleMsg` naming the track it created:
|
|
||||||
under Ρ-F2 that path produces no visible change, so a silent success is
|
|
||||||
indistinguishable from a no-op. Recommendation: yes, one line and one string.
|
|
||||||
- **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.
|
|
||||||
4. `src/core/view/CLAUDE.md` §Invariants — *"New tracks are tagged to the active mode at
|
|
||||||
creation."* **Added by the Ρ-F2 ruling.** That rule now applies only to a GUID carrying
|
|
||||||
no membership record: an explicit tag wins over the detector. Amend the sentence and
|
|
||||||
state the reason in one clause — the detector classifies content the *user* made, not
|
|
||||||
content the tool made and already classified. `src/shell/panel/CLAUDE.md` describes
|
|
||||||
`panel_input` only as "the new-content auto-tag timer" and does not restate the rule, so
|
|
||||||
it needs no amendment.
|
|
||||||
|
|
||||||
**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.
|
|
||||||
- `render_in_place.cpp` contains no reference to `activeModeId` in the tagging path —
|
|
||||||
checkable by grep, and the review gate for the Ρ-F2 ruling. The result track and its
|
|
||||||
item are tagged `kArrangeModeId`; `activeModeId()` appears only in the `applyMode`
|
|
||||||
reapply.
|
|
||||||
- `detectNewContent` no longer auto-tags a GUID that already carries a membership record,
|
|
||||||
and the existing `view_mode_model` / `guid_diff` tests pass unchanged (the filter is
|
|
||||||
shell-side; `autoTagNewContent`'s pure contract does not move).
|
|
||||||
- A membership index carrying an explicit `kArrangeModeId` record JSON-round-trips
|
|
||||||
unchanged, and `isMember` answers identically for that record and for an absent one —
|
|
||||||
one added `view_mode_model` test, no DAW.
|
|
||||||
- All four 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 under the Ρ-F2 ruling** — fired from Arrange (source parks;
|
|
||||||
result track visible and in the mix) and fired from Design (source stays on the bench;
|
|
||||||
result track parked, then present in the source's place after a switch to Arrange).
|
|
||||||
**In each case wait out at least one panel timer tick and re-check the membership** —
|
|
||||||
that is the auto-tag-detector regression, and it is what catches a missing
|
|
||||||
explicit-tag-wins filter or an untagged item, either of which silently reverses the
|
|
||||||
ruling. The old ordering check (that the new track is never momentarily parked) no
|
|
||||||
longer applies.
|
|
||||||
- **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.** **No [Daniel] questions — Ρ-F1, Ρ-F2 and Ρ-F3 are all RULED**
|
|
||||||
(2026-08-02), so nothing here is gated. Two **[propose at review]** items, both
|
|
||||||
recommendation-carrying. First, whether the Design-fired path emits a one-line
|
|
||||||
`ShowConsoleMsg` (see the Feedback bullet) — recommendation: yes. Second, **[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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase Λ — ReaSampler on Linux: both artifacts, shipped
|
## Phase Λ — ReaSampler on Linux: both artifacts, shipped
|
||||||
|
|
||||||
**Ships:** `reaper_reasampler.so` and `reasampler_9000.vst3` built, installed and documented
|
**Ships:** `reaper_reasampler.so` and `reasampler_9000.vst3` built, installed and documented
|
||||||
@@ -4219,7 +3854,8 @@ proof it exists to give.
|
|||||||
day it was framed, so the track here is not gated on a decision; see "Decision state"
|
day it was framed, so the track here is not gated on a decision; see "Decision state"
|
||||||
above. **Ρ-F2 was the one that moved the spec** — the result track goes to Arrange
|
above. **Ρ-F2 was the one that moved the spec** — the result track goes to Arrange
|
||||||
unconditionally rather than following the active mode, which is also the only reason the
|
unconditionally rather than following the active mode, which is also the only reason the
|
||||||
phase touches `panel_input.cpp` at all.
|
phase touches `panel_input.cpp` at all. **Ρ-W1-T1 has landed**; see `docs/COMPLETED.md`
|
||||||
|
for the full narrative.
|
||||||
- **All of Phase Λ** (`pl-*`). **Thirteen pending tracks across seven waves** (Λ-W2…Λ-W8),
|
- **All of Phase Λ** (`pl-*`). **Thirteen pending tracks across seven waves** (Λ-W2…Λ-W8),
|
||||||
plus Λ-W1's two audit tracks, which are landed. From a direct request (Daniel, 2026-08-02),
|
plus Λ-W1's two audit tracks, which are landed. From a direct request (Daniel, 2026-08-02),
|
||||||
not from `TODO-1.0.md`; the product reasoning lives in `docs/product/linux-readiness.md`
|
not from `TODO-1.0.md`; the product reasoning lives in `docs/product/linux-readiness.md`
|
||||||
@@ -4373,6 +4009,7 @@ Phase Epsilon — The bank package (none of the seventeen; a direct reque
|
|||||||
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)
|
Phase Rho — Render in place (none of the seventeen; a direct request 2026-08-02)
|
||||||
|
[LANDED — Ρ-W1-T1; see docs/COMPLETED.md; full detail section removed from this file]
|
||||||
W1 The verb [ONE track, deliberately]
|
W1 The verb [ONE track, deliberately]
|
||||||
T1 render-in-place ............. render selected track -> new sibling track,
|
T1 render-in-place ............. render selected track -> new sibling track,
|
||||||
item placed at the exact render position,
|
item placed at the exact render position,
|
||||||
|
|||||||
Reference in New Issue
Block a user