# Render in place — product notes

Framing, rationale, and design-direction calls behind **Phase Ρ — render a track's
output to a new sibling track, in the timeline, without touching the bank.** The
tickable spec lives in `docs/PLAN.md` (§Phase Ρ); the architecture detail belongs in
`src/shell/capture/CLAUDE.md` and `src/core/capture/CLAUDE.md` once the track lands.
This doc holds the *why* — the third-verb argument that reconciles this feature with
the capture/placement separation, the prior art it borrows from, the reuse inventory
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; **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.

---

## What it is (and what it is not)

**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, 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
respect is the whole feature: **instead of muting the original, it parks it.** The
source track goes to Design mode — hidden from the arrange, out of the mix, FX
offline, CPU reclaimed — and its rendered audio takes its place in the arrangement.
That is a strictly better disposition than mute, because mute leaves the design
scaffolding visible and its FX resident; Design mode removes both, reversibly, from
a snapshot.

**It is not a capture.** No `Sample` is minted into any `BankModel`, no index entry is
added, no file is recorded in the tracking ledger, the bank generation is not bumped,
and no live ReaSampler 9000 instance reloads. The bank does not change in any way an
observer could detect.

**It is not a freeze.** The source track's FX chain is untouched — not removed, not
bypassed permanently, not flattened. Design View's park is snapshot-based and fully
restored on toggle-back (`src/shell/view/CLAUDE.md` §Non-destructive restore), so
switching to Design brings the source back exactly as it was, FX and routing intact.
Ableton's *Freeze & Flatten* destroys the device chain; Phase Ρ never does.

**It is not a placement of a bank sample.** The insert action and the arrange drop
both take something already in the bank and put it on the timeline. Phase Ρ's file
was never in the bank and never will be. The two paths share `InsertMedia` and
nothing else.

---

## The third verb — and why the load-bearing principle survives it

Root `CLAUDE.md` carries the tool's sharpest rule:

> **Capture and placement are separate acts.** Capturing audio writes a file to the
> bank and adds an index entry. It **never** puts an item in the arrange view. […]
> Any code path that auto-inserts a capture into the timeline violates the purpose of
> the tool and **must be rejected in review**.

Phase Ρ renders audio, places an item in the arrange, and deliberately does not touch
the bank. The question is not rhetorical and the answer is not "it's fine because
Daniel asked for it."

**The answer is that the rule is about the bank, not about rendering.** Read the
sentence again: the object of "capturing" is *the bank* — a file in the bank folder
plus an index entry. The prohibition attaches to *that act* placing an item. What the
rule protects is a two-way boundary:

- the arrangement must never gain an item as a side effect of a bank gesture, and
- the bank must never gain a member as a side effect of an arrangement gesture.

Phase Ρ crosses neither direction, because **the bank is not a party to it.** The
system has two verbs today and gains a third:

| 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** (Phase Ρ) | arrange | arrange | never |

Three verbs, three distinct (source, sink) pairs. The bank appears in exactly two of
them and never on both sides of one. The load-bearing rule is the statement that no
single verb may have the bank on one side and the arrange on the other *in the wrong
direction* — and Ρ has the bank on neither side.

What Ρ shares with capture is the **render**, not the capture: the same
`renderOffline` seam, the same `FxBypassGuard`, the same exact-bounds custom time
window, the same multi-track refusal, the same `RENDER_ADDTOPROJ = 0`. A render is a
mechanism; a capture is a render *plus* a bank landing. Ρ takes the mechanism and
declines the landing. That is reuse, not a breach.

### The boundary that keeps them from bleeding

Four things must stay true. Each is a review-rejectable condition, and three of the
four are structural rather than remembered:

1. **Ρ's shell never names the bank.** `render_in_place.cpp` must not call
   `session.bank()`, `session.book()`, `session.recordCreated()`, or
   `session.bumpBankGeneration()`. The `Sample` that `OfflineRenderBackend::capture`
   returns is discarded, and on the project-media destination its `relativePath` is
   left **empty** — so a Ρ `Sample` is inert by construction and could not be usefully
   added to a bank even by accident.
2. **Ρ cannot express "write into the bank folder."** The destination reaches the
   backend as a **two-valued enum** (`Bank` / `ProjectMedia`), never as a caller-supplied
   path. There is no string a Ρ caller could pass that lands a file in
   `reasampler_bank/`. This is the single most important structural choice in the
   phase: it makes the boundary a type, not a convention.
3. **Ρ's file is never recorded as owned.** Prune deletes `(owned ∩ present) −
   referenced` (`src/core/reclaim/CLAUDE.md`), where `owned` comes from the tracking
   ledger. Ρ records nothing, so its file is not prune-eligible — and it lives outside
   the bank folder, so prune's enumeration never sees it either. Two independent
   layers. The symmetry is worth stating plainly: **the tool deletes only what it
   owns, and a render-in-place file belongs to the project, not to the tool.**
4. **The traffic is one-way.** Ρ may borrow capture's render. **Capture may never
   borrow Ρ's placement.** No capture action grows a "…and place it" option, ever. If
   a future request wants capture-and-place, the answer is "fire the capture action,
   then fire the insert action" — two acts, which is the whole point.

**What would count as drift**, stated so a reviewer can name it: a `renderDir` string
on `CaptureRequest` instead of the enum; a Ρ path that calls `session.bank().add()`;
a Ρ file recorded via `recordCreated`; a `place` flag added to `CaptureActionDef`; or
a "Ρ but also add it to the bank" convenience action. Any of those collapses the
three verbs back into two and the rule stops meaning anything.

---

## Prior art, and what each one contributes

The shape is not novel; the *disposition of the source* is. Named precedents, because
they anchor the argument better than reasoning does:

- **Logic Pro — Bounce in Place.** The idiom Ρ's name borrows. Renders a track's
  output to audio at the same timeline position, on a new track, with the source
  preserved. Confirms that "in place" in DAW usage means *at the same timeline
  position*, not *onto the same track* — which is why the name is right despite Ρ
  creating a new track.
- **Pro Tools — Commit.** The closest prior art, and the one that validates the mode
  transition. Commit offers four dispositions for the source track: *Hide and Make
  Inactive* (the default), *Make Inactive*, *Delete*, and *Do Nothing*. The default
  is hide-and-deactivate — visually gone and processing gone. That is precisely what
  Design View's park already does (`B_SHOWINTCP=0`, `B_SHOWINMIXER=0`,
  `B_MAINSEND=0`, `I_FXEN=0`, per-FX offline), except that Ρ gets it *reversibly and
  as a membership fact* rather than as a per-track inactive flag. Ρ is Commit with
  a fifth disposition the DAWs do not have — *move to the design bench* — supplied by
  the tool's own mode system.
  ([Sound on Sound](https://www.soundonsound.com/techniques/making-commitments),
  [Production Expert](https://www.production-expert.com/production-expert-1/pro-tools-track-commit-vs-track-freeze))
- **REAPER — Render selected track time selection to new track and mute original.**
  The action Daniel named. Contributes the range semantics (time selection) and the
  new-track placement; Ρ replaces its source disposition and adds colour/name
  cloning.
- **Ableton Live — Freeze & Flatten.** Contributes a negative: flatten destroys the
  device chain. Ρ explicitly does not, and the Design-mode park is what makes
  preserving it cost nothing at playback.

---

## What already exists — the reuse inventory

Daniel's framing was that the machinery is in place. It substantially is. This table
is the proof, and it is also the spec's shape: each row names the module that answers
the need, so the implementation is composition rather than construction.

| What Ρ needs | Already answered by |
|---|---|
| Resolve the source track + the range (razor-else-time) | `shell/capture/scope_resolve` — `ResolveScopeSource(CaptureScope::Track, …)` |
| Refuse a multi-track render | `core/capture/render_settings` — `isMultiTrackStemRender` / `multiTrackRefusalMessage`, fired inside `renderOffline` |
| Render exactly the requested window, wet, at track scope | `shell/capture/capture_orchestrator` — `renderOffline` + `FxBypassGuard` + `RenderTrackSelection` |
| Never add the render to the project as an item | `shell/capture/capture.cpp` — `RENDER_ADDTOPROJ = 0`, unconditional |
| Refuse a widened render | `core/capture/render_window::frameCountFor` + the bounds gate in `OfflineRenderBackend::capture` |
| Snapshot and restore every `RENDER_*` project setting | `ScopedRenderSettings` (RAII) in `capture.cpp` |
| Force the project to be saved first | the `EnumProjects` / `Main_SaveProject` gate in `OfflineRenderBackend::capture` |
| Name the render after its source track + a discriminator | `core/capture/capture_name` — `composeCaptureName`, `shell/capture/capture.cpp` — `captureNameFor` |
| Read the source track's display name (with the `Track N` fallback) | `shell/capture/scope_resolve::trackName` |
| Collapse a bit-identical stereo render to mono | `core/capture/wav_codec::collapseToMono`, driven by `collapseCapturedFileToMono` |
| Compute the `InsertMedia` bitmask with the stretch bit provably clear | `core/capture/insert_plan::computeInsertMode` |
| Place a file at a known track + time, undo-wrapped, selection restored | the recipe in `shell/capture/insert.cpp` / `shell/actions/arrange_drop_win.cpp` |
| Move a track into Design and reapply the active mode | `core/view` `MembershipIndex::tag` + `shell/view/view.h` `applyMode` / `mintManagedLanes` |
| Persist the view model | `ReaSamplerSession::saveToActiveProject()` (the `persistViewState` pattern in `design_view_actions.cpp`) |
| Register one more bindable action | `shell/actions/action_registry` — one `ActionTableRow` in `main.cpp`'s table |
| Pure folder arithmetic over the flat `I_FOLDERDEPTH` delta list | `core/capture/track_topology` (extended — see §"The new track") |

**What genuinely does not exist**, and why nothing already there stretches to cover
it — three small pure additions and one bounded seam:

1. **A render destination that is not the bank.** `OfflineRenderBackend::capture`
   derives its output path from `deriveBankPaths(projectDir, …)` unconditionally
   (`capture.cpp`'s `OfflineRenderBackend::capture`) and points `RENDER_FILE` at the bank folder. Nothing about
   that is parameterized. The alternative — render into the bank and then move the
   file out — was rejected: it puts a transient, unindexed, unowned file inside the
   folder prune enumerates, which is exactly the file class the ownership rule exists
   to reason about, and it would make the bank folder momentarily lie about its
   contents. **Seam:** a `CaptureDestination { Bank, ProjectMedia }` field on
   `CaptureRequest` (defaulting to `Bank`), resolved by the backend *after* its own
   save gate, plus a `RenderPaths deriveRenderPaths(absoluteDir, baseName, uniqueTag)`
   sibling in `capture_paths` that `deriveBankPaths` is then expressed in terms of, so
   the file-stem spelling keeps one owner.
2. **The absolute path of the rendered file, returned.** `CaptureResult` carries only
   `sample.relativePath`, which Ρ deliberately leaves empty. One new field,
   `CaptureResult::absolutePath`, set on the Ok path.
3. **Where a sibling track goes, in folder terms.** Genuinely new, genuinely
   necessary, and genuinely small — see §"The new track".
4. **The idempotent `Capture ` prefix.** Six lines in `core/capture/capture_name`.

Everything else is composition. No new directory, no new backend, no new interface,
no new persisted state.

---

## The render — scope, range, refusal

**Scope is Track**, always. `CaptureScope::Track` means the render hears the item/take
FX plus the selected track's own track FX, with every ancestor and the master
neutralized to unity — no FX, no fader, no pan/width/law colouring
(`fxBypassPlanFor`, `FxBypassGuard`). That is exactly right for a drop-in
replacement: what the render contains is *the track's own contribution to its
parent*, which is what the new sibling track must reproduce when it feeds the same
parent.

**The range is razor-else-time selection**, resolved by `ResolveScopeSource` — the
same rule every other capture action already obeys. Razor wins when present; the
razor union's bounds are the window. If neither a razor area nor a time selection is
present, the action refuses with the reason `resolveRange` already produces. **Item
extent is not a fallback**, and should not become one: item extent is item scope's
concern, and a track render bounded by whichever items happen to be selected is a
different and much less predictable verb.

**Multi-track selections are refused**, inherited rather than re-implemented.
`renderOffline` fires `isMultiTrackStemRender` before touching anything, keyed on the
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.

**Ρ-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
(`insert.cpp` passes only a path). Daniel's Ψ.6 ask named "mono arrange items"
explicitly, so this is the intended outcome, not a side effect. **But note what Ρ
changes about the risk:** root `CLAUDE.md` already flags, as `[verify — DAW]`,
whether REAPER sums a 1-channel item on a stereo track at the same unity gain as a
dual-mono 2-channel item. Until Ρ, that property was unverified but not load-bearing
— nothing in the tool placed a collapsed capture automatically. **Ρ is the first path
where a collapsed render is placed into the mix by the tool itself,** which promotes
that question from a footnote to a verification obligation on this phase.

---

## Where the file goes

**The project's recording path** — `GetProjectPathEx(proj, buf, sz)` (SDK header
2550), which the header's own `RECORD_PATH` entry names as the way to get the
*effective* path when `RECORD_PATH` is blank or relative (header 3102).

Why there rather than a dedicated `reasampler_renders/` folder: because the file is
**the project's media, not the tool's.** REAPER's own render-to-new-track, apply-FX,
and freeze glue actions all write into the recording path; *Clean current project
directory* and *Save project as… with copy of media* both understand it. A file in
the recording path is managed by REAPER's project-media machinery, which is exactly
the machinery that should own it. A `reasampler_renders/` folder would be marginally
more findable and would make ReaSampler the apparent owner of files it explicitly
does not own — the wrong trade.

The relative-paths-only invariant is untouched: it binds the persisted `BankIndex`,
and Ρ writes nothing to any index. REAPER stores the item's source path in the `.rpp`
by its own rules.

---

## Placement — exactly

The item lands at **`src.startSeconds`**, the render window's start, unrounded and
**unsnapped**.

Unsnapped is the load-bearing word. `performArrangeDrop` runs its drop time through
`SnapToGrid` because a hand drop wants snapping; Ρ must not, because a snapped
placement would move the audio off the sample-accurate position it was rendered from
and break the property the whole tool is built on. **Ρ's placement is the null test
performed automatically:** an offline render of a range, re-inserted at its source
position, nulls to silence against the source — root `CLAUDE.md` calls that the
tool's trust anchor. Ρ *is* that gesture, made a workflow. If Ρ's placement is not
sample-exact, Ρ is broken, and the way you find out is by soloing the two tracks with
one polarity-inverted.

The recipe is `insert.cpp`'s, verbatim, with the bank lookup removed:
snapshot the cursor → `SetOnlyTrackSelected(newTrack)` → `SetEditCurPos(startSeconds,
false, false)` → `InsertMedia(absolutePath, computeInsertMode(InsertOptions{}))` →
restore the cursor. `InsertOptions{}` defaults give native length, no tempo conform,
and `insert_plan` guarantees the &4 stretch-to-time-selection bit is never set — so
"do not silently time-stretch on insert" holds by construction. Ρ must never offer a
conform variant: a conform would defeat the exact placement it exists to produce.

**Selection afterwards is a deliberate divergence.** Every other placing path
restores the caller's track selection. Ρ leaves **the new track selected, alone.**
The reason is specific: in the headline case the source track is being parked out of
sight in the same gesture, so restoring the selection would leave the user selecting
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

### Index and folder — the one piece of genuinely new arithmetic

"Sibling" is easy to say and has three cases. Getting it wrong is audible, not
cosmetic, which is why this is the one place Ρ adds a real (small) pure function
rather than composing.

The naive answer — insert at `sourceIndex + 1` — is wrong twice:

- **Source is a folder parent** (`I_FOLDERDEPTH >= 1`). Inserting immediately after
  it makes the new track the folder's **first child**, so the rendered audio is
  summed back into the folder and runs through the parent's FX and fader a second
  time. Track scope already put the parent's own FX and fader *into* the render, so
  this double-processes audibly.
- **Source is the last track in its folder** (`I_FOLDERDEPTH <= -1`). The source
  carries the folder's closing delta, so inserting after it lands the new track
  **outside** the folder — the audio then bypasses the folder bus entirely and the
  drop-in replacement is silently wrong in the other direction.

The correct rule is one computation in absolute nesting levels, over the same flat
`I_FOLDERDEPTH` delta list `track_topology::directChildIndices` already prefix-sums.
Given `depth[i]` for every track and `level[0] = 0`, `level[i+1] = level[i] +
depth[i]` (and `level[count] = 0` for a well-formed project):

1. `L = level[srcIdx]` — the source's own nesting level.
2. **Insert position** `p`: if `depth[srcIdx] >= 1` (folder parent), `p` = the first
   `j > srcIdx` with `level[j] == L` — i.e. immediately after the whole folder, at
   the source's own level; `count` if none. Otherwise `p = srcIdx + 1`.
3. **Two folder-depth writes**, and only two. With `b = p - 1` (the track that will
   precede the new one) and `Lp = level[p]` (the level the track currently at `p`
   sits at, `0` at end-of-project): set `depth[b] = L - level[b]`, and set the new
   track's `depth = Lp - L`.

The total of all deltas is preserved, so nothing downstream of the insertion shifts.
Checked against every case:

| Case | `depth[b]` after | new track `depth` | Result |
|---|---|---|---|
| Normal track, mid-folder or top level | unchanged (`0`) | `0` | inserted directly below, same level |
| Last track in a folder (`-1`) | `0` | `-1` | new track becomes the folder's last member |
| Last in two folders (`-2`) | `0` | `-2` | closing delta moves to the new track intact |
| Folder parent | unchanged | `0` | new track lands after the whole folder, at the parent's level |
| Last track in the project | unchanged | `0` or the source's close | consistent, sums to zero |

That is roughly thirty lines, fully unit-testable with no DAW, and it belongs beside
`directChildIndices` in `core/capture/track_topology` — same input, same arithmetic,
same file. A malformed project whose deltas do not sum to zero should clamp rather
than assert; the failure mode is a track at the wrong nesting level, never a crash.

Creation is `InsertTrackInProject(proj, p, /*flags=*/0)` (SDK header 3954), then
`GetTrack(proj, p)` (3501) to obtain the handle. **`flags = 0`, not `1`:** the header
states `flags&1` adds default envelopes/FX, and a Ρ track must be bare — the FX are
already baked into the audio, and a default chain would process the render a second
time.

### Colour

`SetMediaTrackInfo_Value(newTrack, "I_CUSTOMCOLOR", (double)GetTrackColor(source))`.
`GetTrackColor` (3517) returns the custom colour already OR'd with `0x1000000`, or
`0` when the track has no colour set; `I_CUSTOMCOLOR` (2942) treats a value without
that bit as "not used." So the same single line clones a colour *and* clones the
absence of one, with no branch.

### Name

`"Capture " + sourceName`, where `sourceName` is `scope_resolve::trackName(source)` —
`GetTrackName` (3629), which already answers REAPER's `Track N` convention for an
unnamed track. An unnamed track 7 therefore yields `Capture Track 7`, which is a real,
deterministic, identifiable name; this is Ψ-W2-T1's precedent applied unchanged.
Written with `GetSetMediaTrackInfo_String(newTrack, "P_NAME", buf, true)` (2997).

**The prefix is idempotent — it never stacks.** If the source name already begins with
`"Capture "`, the new name is the source name **verbatim**. So rendering `MONEY`
gives `Capture MONEY`, and rendering `Capture MONEY` gives `Capture MONEY` again, not
`Capture Capture MONEY`.

The alternative — a counter suffix, `Capture MONEY 2` — is rejected. REAPER does not
uniquify track names either, duplicate track names are ordinary and harmless, and a
counter is a treadmill that has to be maintained forever. What actually distinguishes
two renders of the same source is their position in the track list and the item on
each; the name's job is to say *what this is*, and it says that correctly the first
time. Making the operation a fixed point is worth more than distinguishability here.

This is one pure function in `core/capture/capture_name` — `captureTrackName(sourceName)`
— tested for the plain case, the already-prefixed case, the empty-source case, and
the `Track N` case. The prefix string is a display convention, not a persisted key:
unlike `kManagedLanePrefix` or an action-id suffix, changing it later strands nothing.

---

## Mode transitions — the source parks, the result goes to Arrange

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 —
`tag` replaces any prior single-mode membership, so a source already in Design
*stays* (no observable change) and a source in Arrange or untagged *goes*. This is
exactly what the shipped `VIEW_TAG_DESIGN` action does to a selection; Ρ performs it
on one track as part of a larger gesture.

Two inherited behaviours to state rather than fight:

- **Show-both on the source is not cleared.** Show-both is the user's explicit "pin
  this visible across modes" flag. Ρ tagging a source into Design must not silently
  unpin it; a show-both source stays visible in both stances, which is what the user
  asked for.
- **A folder-parent source is not hidden by tagging it.** Parents are derived, never
  tagged: a parent is visible in every mode any descendant leaf is visible in
  (`core/view/CLAUDE.md`). Tagging a folder parent Design sets its *own* membership
  but leaves it visible in Arrange as long as any child is an Arrange member. This is
  a limitation of the shipped model that the existing tag action shares exactly; Ρ
  inherits it. Do **not** invent a cascade that tags the children — that changes the
  membership model to make one feature convenient.

**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.

**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 | 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 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-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
for two modes splits into managed lanes exactly as it would from any other membership
change. Ρ adds no lane rule of its own.

---

## Undo

**One undo block** (`Undo_BeginBlock2` / `Undo_EndBlock2` with `UNDO_STATE_ALL`, i.e.
`-1`), opened before the track is created and closed after the mode reapply — the
same shape `insert.cpp` and `performArrangeDrop` already use. The render itself sits
*outside* the block: `renderOffline` mutates only `RENDER_*` project settings, which
it snapshots and restores by RAII, and writes a file. Nothing there is undoable and
nothing there should be in the undo history.

What one Ctrl-Z therefore restores: the new track is gone, its item with it, the
source track's folder-depth write is reverted, and the track selection is back.

Three residuals, all inherited and all honest:

1. **The rendered file survives.** REAPER's undo does not delete files, prune is the
   exclusive deletion authority in this system, and Ρ's file is not even prune-
   eligible. An undone render leaves an orphan `.wav` in the project's recording
   path — precisely what REAPER's own render and record actions do. Not a defect.
2. **The source stays tagged Design.** REAPER's undo restores live track state but
   does not roll back the view model's membership index or active mode — documented
   in `src/shell/view/CLAUDE.md` §Gotchas, where `snapshots_` already carries the same
   split. The way out is the existing *tag selected tracks → Arrange* action. Do not
   build a compensating mechanism for one feature; the model-vs-undo split is a
   phase-D-scale question, not Ρ's.
3. **A membership entry for the deleted track's GUID lingers**, harmlessly:
   `ViewModeModel::reconcile(liveGuids)` prunes unknown GUIDs on its next pass.

**Persist runs after the block closes, not inside it** — the ordering
`design_view_actions::doMoveItems` already documents, because `persistViewState` may
raise a Save-As dialog and a modal dialog must not sit inside an open undo block.

---

## The action

**Command-id suffix: `RENDER_TRACK_IN_PLACE`.** FOREVER-STABLE per channel
(`channelCommandId` composes `CEREBELLUM_REASAMPLER_` / `CEREBELLUM_REASAMPLER_BETA_`
in front of it), so this string can never change once shipped — user keybindings key
off the composed id.

Chosen deliberately as a **new verb family**, not a member of `CAPTURE_*`. The id is
permanent and it is the most durable statement the codebase makes about which pillar
a feature belongs to; filing this under `CAPTURE_` would encode the exact confusion
the third-verb argument exists to prevent. `RENDER_*` also leaves room for a future
`RENDER_ITEMS_IN_PLACE` without renaming anything.

**Actions-list phrase: `"render selected track to a new track (source moves to
Design)"`**, which REAPER shows as *ReaSampler: render selected track to a new track
(source moves to Design)*. Long, but the parenthetical is not decoration — a user
binding this to a key must know the source is about to disappear from the arrangement
before they press it, not after. The existing family already carries parentheticals of
this weight (*insert selected sample at edit cursor (conform to tempo)*).

Registration is **one `ActionTableRow`** in `main.cpp`'s `buildMainActionTable()` — the
Q-W6 data-driven table drives registration, `hookcommand` dispatch, and the unload
mirror-unregister from that one row. Main section only; no `custom_action` /
`hookcommand2` second registration is needed.

**Feedback:** silent on success (the new track is the feedback), `ShowConsoleMsg` on
every refusal, carrying the reason `resolveRange` / `renderOffline` already produced.
This matches `RunInsertSelected` exactly.

---

## What Phase Ρ explicitly is NOT

Stated as sharply as the goals, because a small phase stays small only if its edges
are named:

- **Not a bank capture, in any form.** No index entry, no ledger record, no
  generation bump, no instance reload.
- **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
  destroy the exact placement the feature exists to produce.
- **Not a mute, not a delete, not a freeze.** The source keeps its items, its FX, its
  routing and its automation. It moves stance; it loses nothing.
- **Not a source-track cascade.** Rendering a folder parent does not tag its children,
  does not restructure the folder, and does not touch anything but the two
  folder-depth values the insertion arithmetic requires.
- **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`, two lines in `panel_input.cpp`, one
  row in `main.cpp`.

---

## Invariant amendments this phase owns

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.

1. **`src/shell/capture/CLAUDE.md` §Invariants** — *"`RunInsertSelected` is the one
   deliberate exception to capture-never-places … every other capture entry point
   writes only a file + index entry."* Ρ adds a second placing path in this
   directory. The amended form must say that this directory now hosts two placing
   paths and state 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."* Strictly this stays true if Ρ's shell
   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
verb outside the rule, with the two-way boundary spelled out. The rule's force must
not be diluted — it is what keeps the tool honest — so the amendment names the
exception precisely rather than softening the prohibition.

---

## Where it lives

**Pure** — three additions, all to existing modules with existing test targets, no new
directory:

- `core/capture/track_topology` — the sibling-placement arithmetic
  (`siblingPlacement(depths, srcIdx) -> { insertIndex, precedingDepth, newDepth }`).
  Same input list, same prefix-sum, same file as `directChildIndices`.
- `core/capture/capture_name` — `captureTrackName(sourceName)`, the idempotent prefix.
- `core/capture/capture_paths` — `RenderPaths` + `deriveRenderPaths(absoluteDir,
  baseName, uniqueTag)`, with `deriveBankPaths` re-expressed over it so the stem
  spelling keeps one owner (`bankRelativeForName` already depends on that being true).

**Shell** — one new TU plus one bounded edit:

- `shell/capture/render_in_place.{h,cpp}` — the action body. It lives in
  `shell/capture/` rather than `shell/actions/` because it composes `renderOffline`
  and `ResolveScopeSource` and is genuinely a render path, not a skin over one; the
  directory's own CLAUDE.md says action *bodies* belong here and that `shell/actions`
  only skins mutation logic owned elsewhere.
- `shell/capture/capture.cpp` — the destination branch (~6 lines at the path
  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
hot paths (peaks envelope compute, audition, the realtime-capture tick's
single-pointer-test idle fast path, the instrument's `process()`) is touched, and no
guardrail applies beyond the general one.

---

## DAW-verification obligations

Following the plan's convention, stated up front so they are an obligation rather
than a discovery. Nothing in Ρ is unit-testable past the pure functions.

- **The null test on Ρ's own output** — render a track over a range, then
  polarity-invert the source against the new track and confirm silence. This is the
  phase's trust anchor and the single most important check.
- **The three folder cases** — a normal mid-folder track, a last-in-folder track, and
  a folder parent — each rendered, each confirming the new track's nesting level and
  that the render feeds (or bypasses) the folder bus correctly. `[verify — DAW]`
  whether `InsertTrackInProject` at index `p` combined with the two `I_FOLDERDEPTH`
  writes settles without an intermediate `TrackList_AdjustWindows(false)` (header
  7735; the note at 2721 says some attribute writes need a manual panel update, and
  the `isMinor` semantics are undocumented).
- **The collapsed-mono placement** — render a dead-centre source, confirm the item is
  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; 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
  (must not stack) and an unnamed source (must read `Capture Track N`).
- **`GetProjectPathEx` on a project saved in a folder with a non-default recording
  path**, confirming the render lands where the project's media lives.

---

## Rulings — Daniel's, 2026-08-02

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.

| 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 |

**Ρ-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".

**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 Ρ.

**Ρ-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.

**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.
