docs: rule Phase Rho's three forks
Refuse multi-track; result track always Arrange; follow panel tail. Rho-F2 overrides the mode-following framing: explicit Arrange tags on track and item, plus an explicit-tag-wins filter in detectNewContent.
This commit is contained in:
+154
-62
@@ -77,16 +77,19 @@ claim above therefore holds unqualified — no unanswered [Daniel]-class questio
|
||||
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.
|
||||
**Phase Ρ (added 2026-08-02) opened three more [Daniel]-class forks and ALL THREE ARE
|
||||
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
|
||||
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
|
||||
is gone, and the ruling pulls in a two-line fix to the panel's auto-tag detector that
|
||||
would otherwise reverse it on the next timer tick. **The plan-wide claim above therefore
|
||||
still 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.**
|
||||
|
||||
**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
|
||||
@@ -2825,9 +2828,10 @@ properties under test are structural and a large payload proves nothing extra.
|
||||
**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.
|
||||
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
|
||||
@@ -2836,7 +2840,10 @@ in substance: *similar to REAPER's "Render selected track time selection to new
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -2897,10 +2904,15 @@ These bind the track in this phase, in addition to the plan-wide set above.
|
||||
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.
|
||||
- **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
|
||||
@@ -2914,29 +2926,42 @@ 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.
|
||||
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`).
|
||||
|
||||
### Open forks — Daniel's. None gates a dispatch.
|
||||
### Rulings — Daniel's, 2026-08-02. Nothing open.
|
||||
|
||||
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".
|
||||
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.** *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.
|
||||
- **Ρ-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.
|
||||
|
||||
---
|
||||
|
||||
@@ -2946,7 +2971,8 @@ statements with counter-arguments: `docs/product/render-in-place.md` §"Open for
|
||||
|
||||
**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
|
||||
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
|
||||
@@ -2956,7 +2982,8 @@ point — it is the only piece with zero dependency on anything else in the trac
|
||||
|
||||
**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.
|
||||
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".
|
||||
@@ -2974,7 +3001,11 @@ clone colour and name, move the source to Design and the new track to the active
|
||||
`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`.
|
||||
`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.
|
||||
@@ -3022,17 +3053,41 @@ clone colour and name, move the source to Design and the new track to the active
|
||||
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,
|
||||
- **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. **Tag order is load-bearing** (see acceptance criteria).
|
||||
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`.
|
||||
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
|
||||
@@ -3055,6 +3110,13 @@ them). A track that lands Ρ without these reads as an invariant breach in revie
|
||||
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
|
||||
@@ -3071,7 +3133,17 @@ them). A track that lands Ρ without these reads as an invariant breach in revie
|
||||
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.
|
||||
- `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):
|
||||
@@ -3087,8 +3159,14 @@ unit-testable):
|
||||
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.
|
||||
- **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.
|
||||
@@ -3099,7 +3177,10 @@ unit-testable):
|
||||
- **`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
|
||||
**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
|
||||
@@ -3181,8 +3262,11 @@ proof it exists to give.
|
||||
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.
|
||||
unavoidable. Its three [Daniel]-class forks (Ρ-F1/F2/F3) were opened and ruled the same
|
||||
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.
|
||||
|
||||
### Deliberate compressions
|
||||
|
||||
@@ -3328,10 +3412,11 @@ Phase Rho — Render in place (none of the seventeen; a direct reque
|
||||
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]
|
||||
colour + "Capture " name cloned, source -> Design,
|
||||
result track -> ARRANGE always. Never the bank.
|
||||
[R-F1 RULED: refuse multi-track, settled non-goal.
|
||||
R-F2 RULED: result track ALWAYS Arrange, never
|
||||
mode-following. R-F3 RULED: follow panel 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
|
||||
@@ -3343,10 +3428,17 @@ Phase Rho — Render in place (none of the seventeen; a direct reque
|
||||
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).
|
||||
R-F2 fallout, the ruling's only cost: the result track AND its item are tagged
|
||||
kArrangeModeId explicitly, and detectNewContent must drop added GUIDs that already carry
|
||||
a membership record — the auto-tag detector otherwise re-tags a Design-fired result to
|
||||
Design on its next tick and reverses the ruling. Firing from Design is therefore
|
||||
deliberately invisible: the render waits in Arrange. No A/B-on-the-bench behaviour exists.
|
||||
Four invariant amendments are track deliverables (the Psi precedent): shell/capture and
|
||||
shell/actions CLAUDE.md placing-path claims, ONE sentence in root CLAUDE.md naming
|
||||
the third verb without softening the prohibition, and core/view CLAUDE.md's "new tracks
|
||||
are tagged to the active mode" made conditional on carrying no membership record.
|
||||
Shared files, named: capture.{h,cpp} (destination enum + result field), main.cpp (one
|
||||
ActionTableRow), and panel_input.cpp (detectNewContent only; Psi's two named regions in
|
||||
that file are landed and are other functions) — the only pre-existing shell files
|
||||
touched. Disjoint from Gamma (core+shell/instrument) and Epsilon (core+shell/package).
|
||||
```
|
||||
|
||||
+223
-98
@@ -9,12 +9,20 @@ the capture/placement separation, the prior art it borrows from, the reuse inven
|
||||
that makes it small, and the handful of decisions the shape actually turns on.
|
||||
|
||||
Status: framed by product-designer (2026-08-02) from Daniel's direct request the same
|
||||
day. **Three [Daniel]-class forks are open** — Ρ-F1 (multi-track), Ρ-F2 (the new
|
||||
track's mode when Design is active), Ρ-F3 (tail) — each stated with a recommendation
|
||||
in §"Open forks". None of them blocks a dispatch; all three are answerable at
|
||||
implementation review if Daniel prefers, but all three are user-visible policy rather
|
||||
than implementation detail. Everything else below is a product-designer call with its
|
||||
reasoning stated; contradict it in review with an argument, not a preference.
|
||||
day; **all three [Daniel]-class forks RULED the same day** — Ρ-F1 **refuse a
|
||||
multi-track selection**, Ρ-F2 **the result track always goes to Arrange**, Ρ-F3
|
||||
**follow the panel tail setting**. See §"Rulings" for the index and the recorded
|
||||
rationale; each is specified in place in the section that owns it. Nothing in this
|
||||
doc is open.
|
||||
|
||||
**Ρ-F2 overrode the framing, including Daniel's own original phrasing.** The request
|
||||
as first stated had the new track take whatever mode was active when the action fired;
|
||||
the ruling replaced that with an absolute rule. §"Mode transitions" is written against
|
||||
the ruling, and the A/B-on-the-bench behaviour that mode-following would have produced
|
||||
**no longer exists anywhere in this design** — do not reintroduce it as a benefit.
|
||||
|
||||
Everything else below is a product-designer call with its reasoning stated; contradict
|
||||
it in review with an argument, not a preference.
|
||||
|
||||
---
|
||||
|
||||
@@ -23,8 +31,9 @@ reasoning stated; contradict it in review with an argument, not a preference.
|
||||
**Render in place takes one selected track, renders its output over the current
|
||||
range to a file, and drops that file as an item on a brand-new sibling track at the
|
||||
exact position it was rendered from — then moves the source track into Design mode.**
|
||||
The new track inherits the source's colour and its name with a `Capture ` prefix. The
|
||||
bank is never opened, never read, never written.
|
||||
The new track inherits the source's colour and its name with a `Capture ` prefix, and
|
||||
belongs to Arrange unconditionally (Ρ-F2). The bank is never opened, never read, never
|
||||
written.
|
||||
|
||||
The model Daniel named is REAPER's own *Render selected track time selection to new
|
||||
track (stereo) and mute original*. Phase Ρ differs in exactly one respect, and that
|
||||
@@ -235,19 +244,46 @@ different and much less predictable verb.
|
||||
render *source* (`SelectedTracks`, which track scope always uses), so any selection of
|
||||
more than one track refuses with `multiTrackRefusalMessage(CaptureScope::Track)`
|
||||
before a single project setting is written. Ρ inherits this for free and adds no
|
||||
check of its own. See fork Ρ-F1 for the alternative.
|
||||
check of its own.
|
||||
|
||||
**Tail follows the panel setting** — None / Auto / Manual, read from
|
||||
`bankPanelTailSetting()` like every other capture path. Under Auto or Manual the
|
||||
rendered file is longer than the requested window by design (the chain's decay rings
|
||||
past the range end), so the placed item is correspondingly longer than the source
|
||||
window. That is the musically correct answer for a design chain with reverb on it,
|
||||
and forcing None would be Ρ inventing a policy the rest of the tool does not have.
|
||||
Two consequences to state rather than discover: the exact-bounds gate in
|
||||
`OfflineRenderBackend::capture` runs **only** under `TailMode::None`, so Auto/Manual
|
||||
renders are unguarded against widening (inherited, not introduced); and the placed
|
||||
item's *start* is exact in every mode, because a tail is added at the end only. See
|
||||
fork Ρ-F3.
|
||||
**Ρ-F1, RULED by Daniel, 2026-08-02: *refuse*.** One selected track per fire is a
|
||||
**settled non-goal**, in the same sense as the other entries in §"What Phase Ρ
|
||||
explicitly is NOT" — not a deferral. There is no per-track loop planned, no second
|
||||
wave holding one, and no seam to be left half-open on the way past. If multi-track is
|
||||
ever wanted it is a new phase with its own framing, and the counter-argument that made
|
||||
this a fork (the stem-collapse hazard does not apply to a per-track loop, so the
|
||||
refusal is inherited rather than required) is recorded in §"Rulings" so it does not
|
||||
have to be rediscovered.
|
||||
|
||||
**Tail follows the panel setting — Ρ-F3, RULED by Daniel, 2026-08-02: *"follow panel
|
||||
tail settings."*** None / Auto / Manual, read from `bankPanelTailSetting()` like every
|
||||
other capture path. Forcing `None` would be Ρ inventing a policy the rest of the tool
|
||||
does not have, and a decaying design chain wants its tail when its source is about to
|
||||
be silenced.
|
||||
|
||||
**Two consequences of that ruling, both accepted, both stated here because an
|
||||
implementer meets them directly:**
|
||||
|
||||
1. **Under Auto or Manual the placed item is longer than the window it replaces.** The
|
||||
chain's decay rings past the range end and the render carries it, so the item on
|
||||
the result track extends past the source range. This is correct for reverb and
|
||||
wrong for a section meant to butt against the next one — and the ruling accepts
|
||||
that trade rather than splitting the behaviour. The user's lever is the panel's own
|
||||
tail setting: set it to None before firing if a hard edge is wanted.
|
||||
2. **The exact-bounds gate is inactive under Auto and Manual.** The gate in
|
||||
`OfflineRenderBackend::capture` runs **only** under `TailMode::None`, so Ρ renders
|
||||
in the other two modes are unguarded against a widened render. This is inherited
|
||||
from every other capture path, not introduced by Ρ, and it is not a caveat to bury:
|
||||
the phase's exactness guarantee under Auto/Manual is the *start* only.
|
||||
|
||||
The placed item's **start is exact in every tail mode**, because a tail is only ever
|
||||
added at the end. The null test therefore holds in all three modes — the shared region
|
||||
nulls; the tail simply has nothing to null against.
|
||||
|
||||
A third option was floated at framing and is **not ruled in**: keep the panel setting
|
||||
but run the bounds gate's start-alignment check regardless of tail mode, since a tail
|
||||
only ever extends the end. It is recorded as an unexercised alternative in §"Rulings"
|
||||
and is not to be built into this phase.
|
||||
|
||||
**Mono collapse applies**, unchanged. A render whose channels are bit-identical
|
||||
collapses losslessly to one channel and REAPER derives a mono item from the file
|
||||
@@ -313,6 +349,14 @@ sight in the same gesture, so restoring the selection would leave the user selec
|
||||
an invisible track. The new track is the workflow's next subject; select it. The edit
|
||||
cursor *is* restored, since nothing about Ρ argues for moving it.
|
||||
|
||||
**The Ρ-F2 ruling inverts that reasoning in the Design-fired case, and the rule stays
|
||||
absolute anyway.** Fired from Design, the result track is the parked one and the source
|
||||
is the visible one, so leaving the result selected selects an invisible track — the
|
||||
exact thing the rule exists to avoid in the other direction. Making the selection
|
||||
conditional on the active mode would reintroduce mode-relative behaviour, which is
|
||||
precisely what the ruling removed from this action. One rule, both cases: the result
|
||||
track ends up selected, alone.
|
||||
|
||||
---
|
||||
|
||||
## The new track — index, folder, colour, name
|
||||
@@ -406,11 +450,14 @@ unlike `kManagedLanePrefix` or an action-id suffix, changing it later strands no
|
||||
|
||||
---
|
||||
|
||||
## Mode transitions — resolving "stays/goes"
|
||||
## Mode transitions — the source parks, the result goes to Arrange
|
||||
|
||||
Daniel's phrasing was *"the source track stays/goes to design mode, and the resulting
|
||||
new sibling track […] stays in whatever mode was active when the action was run."*
|
||||
Both halves resolve into the shipped membership model without inventing anything.
|
||||
Daniel's original phrasing was *"the source track stays/goes to design mode, and the
|
||||
resulting new sibling track […] stays in whatever mode was active when the action was
|
||||
run."* **The second half was overridden by his own later ruling — Ρ-F2, 2026-08-02:
|
||||
*"for this action which is not a capture, the result track should always go to
|
||||
arrange."*** The source half stands exactly as first stated; the result half is now
|
||||
absolute and mode-independent.
|
||||
|
||||
**Source track: unconditionally a Design member afterwards.**
|
||||
`membership().tag(sourceGuid, kDesignModeId)` covers both readings in one call —
|
||||
@@ -433,29 +480,91 @@ Two inherited behaviours to state rather than fight:
|
||||
inherits it. Do **not** invent a cascade that tags the children — that changes the
|
||||
membership model to make one feature convenient.
|
||||
|
||||
**New track: tagged to the mode that was active when the action fired**,
|
||||
synchronously and explicitly — `membership().tag(newTrackGuid, view.activeModeId())`.
|
||||
**Result track: unconditionally an Arrange member**, whatever mode was active —
|
||||
`membership().tag(newTrackGuid, kArrangeModeId)`, synchronously and explicitly, **not**
|
||||
an `untag()` to the Arrange default. The distinction is load-bearing: an explicit
|
||||
membership record is what protects the ruling from the auto-tag detector (below). An
|
||||
untagged track is an Arrange member by observable behaviour but carries no record, and
|
||||
a record is what the detector must be made to respect.
|
||||
|
||||
**This must happen before `applyMode` reapplies, and that ordering is load-bearing.**
|
||||
The auto-tag poller (`panel_input::detectNewContent`, over `guid_diff::GuidBaseline`)
|
||||
would tag the new track on its next tick, and would reach the same answer — but the
|
||||
reapply inside Ρ's own gesture runs first. An untagged track is an Arrange member by
|
||||
default, so if the active mode is Design and Ρ reapplies before tagging, the reapply
|
||||
**parks the brand-new capture track** — hidden, out of the mix — and it stays parked
|
||||
until the next mode switch. Tagging explicitly, first, closes that window; the
|
||||
poller's later observation is then idempotent (it re-derives the same mode).
|
||||
**Note what is novel here, because it is worth a reviewer's attention.** Ρ is the
|
||||
**first** path in the tree to write an explicit `kArrangeModeId` record. The shipped
|
||||
*tag selected tracks → Arrange* action does not — it dispatches to `doUntag()`, i.e.
|
||||
Arrange-by-absence, which is why the constant appears in `view_mode_model` and
|
||||
`mode_enable` but never in a `tag()` call. The record is nonetheless well-formed and
|
||||
behaviourally identical to the untagged state everywhere that matters:
|
||||
`ViewModeModel::isMember` answers `true` for `arrange` and `false` for `design` in
|
||||
both cases, the derived-parent and lane rules read the same, and `untag()` still
|
||||
returns it to absence. The only differences are one more entry in the persisted
|
||||
membership index and — the point — its visibility to the detector's filter. **[verify
|
||||
— DAW]** that a project saved with an explicit Arrange record round-trips and shows no
|
||||
behavioural difference from an untagged track; the JSON round-trip is unit-testable and
|
||||
should be tested, but the live view behaviour is not.
|
||||
|
||||
**The placed item is tagged Arrange too**, explicitly, for the same reason. Item
|
||||
membership is a separate index from track membership and it is what drives lane
|
||||
minting; leaving the item untagged hands it to the detector, which tags a new item to
|
||||
the active mode whenever its track has no pre-existing single-mode content — and a
|
||||
brand-new track never does. After `InsertMedia`, enumerate the new track's items (it
|
||||
is brand new, so they are exactly the ones just placed) and tag each;
|
||||
`shell/capture/item_read::itemGuid` is the existing GUID seam.
|
||||
|
||||
### The one hazard the ruling creates — the auto-tag detector
|
||||
|
||||
The ruling **dissolves** the ordering hazard the mode-following rule carried and
|
||||
**creates a different one** in its place. Both are stated, because the first was
|
||||
written into the plan's acceptance criteria and is now wrong.
|
||||
|
||||
**Dissolved.** Under mode-following, the tag had to precede the `applyMode` reapply:
|
||||
an untagged track is an Arrange member by default, so a Design reapply running first
|
||||
would have parked a track destined for Design, and it would have stayed parked until
|
||||
the next switch. Under the ruling the result track *is* an Arrange member, so a Design
|
||||
reapply parking it is the correct outcome and the ordering no longer changes what the
|
||||
user sees. Tag first anyway, for state hygiene — it is simply no longer load-bearing.
|
||||
|
||||
**Created, and it is a defect rather than a nuance.** The panel's new-content detector
|
||||
(`panel_input::detectNewContent` over `guid_diff::GuidBaseline`, feeding
|
||||
`view_mode_model::autoTagNewContent`) tags **every new track to the active mode**,
|
||||
unconditionally: it diffs live GUIDs against the previous tick and never consults the
|
||||
membership index. Fire Ρ while Design is active and, on the next timer tick, the
|
||||
detector re-tags the brand-new result track — and its item — from Arrange to Design,
|
||||
silently reversing the ruling inside a second. Nothing in Ρ's own gesture can outrun
|
||||
it, because it runs after.
|
||||
|
||||
**The fix is one rule, and it is worth stating generally: an explicit tag wins over
|
||||
the detector.** `detectNewContent` drops any added GUID that already carries a
|
||||
membership record (`MembershipIndex::query(guid) != nullptr`) before building its
|
||||
auto-tag input. Two lines, at the point in that function where the model is already in
|
||||
hand. The rule is right beyond Ρ — the detector exists to classify content the *user*
|
||||
made, not content the tool made and has already classified — and it is a strict
|
||||
improvement on an existing rough edge: a track brought back by undo keeps its original
|
||||
mode instead of being re-tagged to whatever mode happens to be active at the time.
|
||||
|
||||
Two alternatives were considered and rejected. Re-arming the baseline after Ρ's
|
||||
gesture (the `bankPanelNotifyProjectLoaded` mechanism) works, but it absorbs an entire
|
||||
tick of genuinely new content silently — a blunt instrument aimed at two GUIDs.
|
||||
Changing `autoTagNewContent`'s own track rule changes Design View's behaviour for
|
||||
every caller in order to serve one.
|
||||
|
||||
The resulting behaviour, stated completely:
|
||||
|
||||
| Active mode when fired | Source afterwards | New track afterwards | What the user sees |
|
||||
| Active mode when fired | Source afterwards | Result track afterwards | What the user sees |
|
||||
|---|---|---|---|
|
||||
| **Arrange** | Design — parked, hidden, FX offline | Arrange — visible, in the mix | The headline case. The design chain vanishes from the arrangement and its audio takes its place, at the same position, same colour, named after it. |
|
||||
| **Design** | Design — visible | Design — visible | Both on the bench, side by side, for A/B. Neither reaches Arrange. |
|
||||
| **Design** | Design — visible on the bench | Arrange — parked while Design is active | The bench keeps only the source. The render is not on screen; it is waiting in the arrangement, and switching to Arrange shows it in the source's place. |
|
||||
|
||||
The Design-active case is coherent — you are iterating on the bench and want the
|
||||
render beside its source — but it means firing Ρ from Design never puts anything into
|
||||
the arrangement. That follows directly from Daniel's stated rule and is a genuinely
|
||||
useful second behaviour, not a defect. It is also the subject of fork Ρ-F2.
|
||||
**The Design-fired case produces no visible change, and that is the ruling's accepted
|
||||
cost.** Fire Ρ from the bench and the immediate feedback is that nothing appears. The
|
||||
price buys the action meaning exactly one thing wherever it is fired — *commit this
|
||||
design work into the arrangement* — which is the reasoning behind the ruling: Ρ is not
|
||||
a capture, and a capture's mode-relative habits do not apply to it.
|
||||
|
||||
**Feedback deserves reconsideration for this case, and only this case.** Ρ is
|
||||
otherwise silent on success because the new track is the feedback; fired from Design
|
||||
there is no visible new track, so a silent success is indistinguishable from a no-op.
|
||||
**[propose at review]** whether the Design-fired path should emit a one-line
|
||||
`ShowConsoleMsg` naming the track it created. Recommendation: yes — it costs one line
|
||||
and one string, and it is the only place in the phase where success is invisible.
|
||||
|
||||
**Lane minting runs**, via `mintManagedLanes(view, nullptr)` before the reapply, on
|
||||
the same path `doMoveItems` already uses — so a track that ends up carrying content
|
||||
@@ -535,8 +644,12 @@ are named:
|
||||
|
||||
- **Not a bank capture, in any form.** No index entry, no ledger record, no
|
||||
generation bump, no instance reload.
|
||||
- **Not multi-track** (this phase — see Ρ-F1). One selected track per fire; more than
|
||||
one refuses.
|
||||
- **Not multi-track.** Ρ-F1, RULED: one selected track per fire; more than one refuses
|
||||
with the message that already exists. Settled, not deferred — no per-track loop is
|
||||
planned, half-built toward, or left a seam for.
|
||||
- **Not mode-following.** Ρ-F2, RULED: the result track is an Arrange member
|
||||
unconditionally. There is no variant that follows the active mode, no per-fire
|
||||
choice, and no bench-visible result — firing from Design puts nothing on the bench.
|
||||
- **Not item-scoped.** No `RENDER_ITEMS_IN_PLACE`, no item-extent range fallback. The
|
||||
seam is left open by the id family; the feature is not built.
|
||||
- **Not a tempo-conforming insert.** No conform variant, ever — a conform would
|
||||
@@ -549,13 +662,14 @@ are named:
|
||||
- **Not a new persisted state.** Membership writes go into the existing `"reasampler"`
|
||||
view section. Ρ adds no key, no version rung, no wire format.
|
||||
- **Not a new directory.** Three small pure additions to existing `core/capture`
|
||||
modules, one new shell TU in `shell/capture`, one row in `main.cpp`.
|
||||
modules, one new shell TU in `shell/capture`, two lines in `panel_input.cpp`, one
|
||||
row in `main.cpp`.
|
||||
|
||||
---
|
||||
|
||||
## Invariant amendments this phase owns
|
||||
|
||||
Two statements in the tree become false the moment Ρ lands, and amending them is a
|
||||
Three statements in the tree become false the moment Ρ lands, and amending them is a
|
||||
**deliverable of the track**, not a follow-up — the precedent is Phase Ψ, where three
|
||||
such amendments were carried as acceptance criteria of the tracks that broke them. A
|
||||
track that lands Ρ without these reads as an invariant breach in review.
|
||||
@@ -572,6 +686,15 @@ track that lands Ρ without these reads as an invariant breach in review.
|
||||
lives in `shell/capture/`, but the sentence reads as a claim about the system.
|
||||
Amend it to be explicit that it scopes to *this directory*, and cross-reference the
|
||||
third verb.
|
||||
3. **`src/core/view/CLAUDE.md` §Invariants** — *"New tracks are tagged to the active
|
||||
mode at creation."* Ρ-F2 makes this conditional for the first time: the detector's
|
||||
active-mode rule now applies only to a GUID that carries **no** membership record,
|
||||
because an explicit tag wins over it (§"Mode transitions"). Amend the sentence to
|
||||
say so, 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/`'s
|
||||
own CLAUDE.md describes `panel_input` as "the new-content auto-tag timer" without
|
||||
restating the rule, so it needs no amendment; if that changes, the rule has one
|
||||
home and this is it.
|
||||
|
||||
Root `CLAUDE.md` §"The load-bearing principle" should gain **one sentence**, not a
|
||||
rewrite: that a render which never enters the bank and never leaves it is a third
|
||||
@@ -605,6 +728,11 @@ directory:
|
||||
derivation) and `CaptureResult::absolutePath`. **No behavioural change on the bank
|
||||
path**: the enum defaults to `Bank`, and the bank branch must be byte-identical to
|
||||
today.
|
||||
- `shell/panel/panel_input.cpp` — two lines inside `detectNewContent`: drop added
|
||||
GUIDs that already carry a membership record, so an explicit tag wins over the
|
||||
auto-tag detector (§"Mode transitions"). This edit exists **only because of the Ρ-F2
|
||||
ruling**; without it the ruling reverses itself on the next timer tick. No other
|
||||
function in the file is touched.
|
||||
- `src/app/main.cpp` — one `ActionTableRow`.
|
||||
|
||||
**Performance posture:** every surface is cold — one gesture, once. None of the named
|
||||
@@ -633,9 +761,15 @@ than a discovery. Nothing in Ρ is unit-testable past the pure functions.
|
||||
mono, and confirm it sums at the same level as 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 Ρ.
|
||||
- **Both mode transitions** — fired from Arrange (source parks, new track visible) and
|
||||
fired from Design (both visible, neither in Arrange) — plus the ordering check that
|
||||
the new track is never momentarily parked.
|
||||
- **Both mode transitions** — 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 switching 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 the check that catches a missing
|
||||
explicit-tag-wins filter or an untagged item — either of which silently reverses the
|
||||
Ρ-F2 ruling. The old ordering check (that the new track is never momentarily parked)
|
||||
no longer applies: under the ruling the result track is an Arrange member and a
|
||||
Design reapply parking it is correct.
|
||||
- **Undo** — one Ctrl-Z removes the track and item and restores the folder depth; the
|
||||
file survives; the source stays tagged Design.
|
||||
- **The name and colour clone**, including a second run over an already-prefixed track
|
||||
@@ -645,60 +779,51 @@ than a discovery. Nothing in Ρ is unit-testable past the pure functions.
|
||||
|
||||
---
|
||||
|
||||
## Open forks — Daniel's
|
||||
## Rulings — Daniel's, 2026-08-02
|
||||
|
||||
### Ρ-F1 — Multi-track: inherit the refusal, or render each selected track?
|
||||
All three [Daniel]-class forks this doc opened were ruled the same day it was framed.
|
||||
Nothing here is open. This section is an index; each ruling is **specified** in the
|
||||
section that owns it, and that section is the implementation-binding text.
|
||||
|
||||
**Recommendation: inherit the refusal.** One selected track per fire; more than one
|
||||
refuses with the message that already exists. Daniel's phrasing was singular ("the
|
||||
source track"), and it costs zero code.
|
||||
| Fork | Ruling | Specified in |
|
||||
|---|---|---|
|
||||
| **Ρ-F1** | **Refuse** a multi-track selection. One track per fire, inherited from `isMultiTrackStemRender`. A per-track loop is a settled non-goal for this phase, not a deferral | §"The render" — the multi-track paragraph; §"What Phase Ρ explicitly is NOT" |
|
||||
| **Ρ-F2** | **The result track always goes to Arrange**, whatever mode was active. *"For this action which is not a capture, the result track should always go to arrange."* | §"Mode transitions" — the absolute rule, the item tag, and the auto-tag-detector fix it requires |
|
||||
| **Ρ-F3** | **Follow the panel tail setting** (None / Auto / Manual), with both consequences accepted | §"The render" — the tail paragraph and its two numbered consequences |
|
||||
|
||||
**The counter-argument is real**, which is why this is a fork rather than a call.
|
||||
REAPER's model action operates on the selection, and the multi-track *stem-collapse*
|
||||
hazard does not actually apply here: Ρ could loop, running `renderOffline` once per
|
||||
selected track with a one-track source each time, and every individual render would
|
||||
be single-track and safe. What it would cost is not the render but the bookkeeping —
|
||||
each insertion shifts the indices of every later track, so the folder arithmetic must
|
||||
be re-derived per iteration; and the undo label, the partial-failure story ("three of
|
||||
five rendered"), and the selection-afterwards rule all have to be answered.
|
||||
**Ρ-F2 went against the framing, and against the request's own original wording.**
|
||||
The framing recommended mode-following on the strength of Daniel's first phrasing
|
||||
("stays in whatever mode was active") and of one use it enabled — an A/B of a chain
|
||||
against its own render, both on the bench, neither touching the arrangement. The
|
||||
ruling took the alternative the framing had itself named: an absolute rule, because Ρ
|
||||
is *not a capture* and the mode-relative habit belongs to the capture pillar, not
|
||||
here. **That A/B use no longer exists, and no text in this doc or in `PLAN.md` may
|
||||
still claim it as a benefit.** The cost the ruling accepts is that firing Ρ from
|
||||
Design produces no visible change; the compensating console message is a
|
||||
[propose at review] item in §"Mode transitions".
|
||||
|
||||
If Daniel wants multi-track, the honest shape is a **second wave**, not a bigger
|
||||
first one — the single-track verb is a strict prerequisite either way, and the design
|
||||
already leaves room (the placement function takes an index and returns a plan, so a
|
||||
loop just re-reads the depth list each pass).
|
||||
**The ruling also has a cost the framing did not anticipate**, recorded here so it is
|
||||
not read as scope creep at review: the panel's auto-tag detector tags every new track
|
||||
to the active mode, so without an explicit-tag-wins filter the Design-fired case
|
||||
reverses the ruling on the next timer tick. The two-line fix in
|
||||
`panel_input::detectNewContent` is a **deliverable of this phase**, caused by this
|
||||
ruling. Under mode-following it would not have been needed — the detector would have
|
||||
agreed with Ρ.
|
||||
|
||||
### Ρ-F2 — When Design is the active mode, where does the new track go?
|
||||
**Ρ-F1 and Ρ-F3 both matched the recommendation**, so nothing in the spec moved. The
|
||||
counter-arguments are kept because they are the reason each was a fork rather than a
|
||||
call, and they will resurface: for Ρ-F1, that the stem-collapse hazard does **not**
|
||||
apply to a per-track loop (each iteration renders one track), so the refusal is
|
||||
inherited rather than forced — what a loop would actually cost is bookkeeping (indices
|
||||
shift per insertion, so the folder arithmetic re-derives each pass; plus an undo label,
|
||||
a partial-failure story, and a selection-afterwards rule). For Ρ-F3, that a drop-in
|
||||
replacement arguably wants exact length, and that forcing `None` would have kept the
|
||||
exact-bounds gate live on every Ρ render.
|
||||
|
||||
**Recommendation: to Design, as Daniel stated** — the new track takes whatever mode
|
||||
was active. Implemented as written.
|
||||
|
||||
**The alternative is defensible**: make Ρ *absolute* rather than mode-relative — the
|
||||
source always goes to Design, the capture always goes to Arrange, regardless of which
|
||||
stance you fired from. That reading makes Ρ mean "promote this design work into the
|
||||
arrangement" in every context, which is arguably the verb's actual purpose, and it
|
||||
means the action does the same thing wherever you are.
|
||||
|
||||
The relative rule wins on Daniel's explicit words and on one real use: firing Ρ from
|
||||
Design to get an A/B of a chain against its own render, on the bench, without either
|
||||
touching the arrangement. That is a genuinely useful thing the absolute rule cannot
|
||||
express. But it does mean a user in Design mode who expects to have "committed
|
||||
something to the arrangement" has not. **One confirm.**
|
||||
|
||||
### Ρ-F3 — Tail: follow the panel setting, or force None?
|
||||
|
||||
**Recommendation: follow the panel setting** (None / Auto / Manual), for consistency
|
||||
with every other render path and because a decaying design chain wants its tail when
|
||||
its source is about to be silenced.
|
||||
|
||||
**The counter:** Ρ's pitch is a drop-in replacement for the source, and under Auto or
|
||||
Manual the placed item is longer than the window it replaces — which is correct for
|
||||
reverb and wrong for a section you intend to butt against the next one. Forcing None
|
||||
would also keep the exact-bounds gate active on every Ρ render, since that gate runs
|
||||
only under `TailMode::None`.
|
||||
|
||||
A third option exists and is worth naming: follow the panel setting but **run the
|
||||
bounds gate's start-alignment check regardless of tail mode**, since a tail only ever
|
||||
extends the end. That gets both properties at the cost of a small change to the gate's
|
||||
condition, and it is the option I would take if Daniel wants the guard without losing
|
||||
the tail. Not recommended by default only because it edits a shared gate for one
|
||||
caller's benefit.
|
||||
**One unexercised alternative, recorded and not built.** For Ρ-F3, a third option was
|
||||
floated at framing: follow the panel setting *and* run the bounds gate's
|
||||
start-alignment check regardless of tail mode, since a tail only ever extends the end.
|
||||
It would recover the widening guard on the start edge without losing the tail, at the
|
||||
cost of editing a gate shared by every capture path for one caller's benefit. It is
|
||||
**not ruled in** and is not part of this phase. If the unguarded Auto/Manual render
|
||||
ever produces a real bug, this is the shape of the fix.
|
||||
|
||||
Reference in New Issue
Block a user