From 51b13304ee349de62fcd195b40793d61494e75d2 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Sun, 2 Aug 2026 15:04:19 -0400 Subject: [PATCH] docs: retire Phase Rho from the plan, record it as landed --- docs/COMPLETED.md | 55 +++++++ docs/PLAN.md | 375 +--------------------------------------------- 2 files changed, 61 insertions(+), 369 deletions(-) diff --git a/docs/COMPLETED.md b/docs/COMPLETED.md index e50d580..f7f86bc 100644 --- a/docs/COMPLETED.md +++ b/docs/COMPLETED.md @@ -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 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. + +### 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 `` 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. diff --git a/docs/PLAN.md b/docs/PLAN.md index 3a2db48..0833a9a 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -91,8 +91,9 @@ decision.** 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 — *"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 -the track below and indexed at `docs/product/render-in-place.md` §"Rulings". **Ρ-F2 +arrange"*; **Ρ-F3** tail — *"follow panel tail settings."* Ρ-W1-T1 has landed; see +`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 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 @@ -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 `_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 **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" 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 - 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), 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` @@ -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. 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] T1 render-in-place ............. render selected track -> new sibling track, item placed at the exact render position,