docs: settle Design View two-canvas sub-phase (fixed-lane per-item modes)

Item-level mode separation via REAPER fixed lanes; new content auto-tags to
the active mode; inactive lanes hidden and silenced; a managed-vs-manual lane
index keeps toggles off user comp lanes; capture placement mode-aware.
Precision invariants unaffected; REAPER 7 floor.
This commit is contained in:
2026-07-23 16:32:58 -04:00
parent 3791e6c119
commit 1688de553b
2 changed files with 504 additions and 13 deletions
+340 -8
View File
@@ -6,8 +6,10 @@ detail in `CONTEXT.md` (§Design View). This doc holds the *why* — the workflo
narrative, the N-mode reasoning, the screenset differentiation, and the
design-direction recommendations — so those don't clutter the build docs.
Status: settled with Daniel (2026-07-22). Open items for Daniel are listed at the
bottom.
Status: settled with Daniel (2026-07-22). The **two-canvas direction** (item-level
mode separation via fixed lanes) is also settled with Daniel (2026-07-23) — see the
§Two-canvas direction section, which **supersedes** the "not a second canvas"
framing below. Open items for Daniel are listed above that section.
---
@@ -25,12 +27,19 @@ place it. The design scaffolding never touches the arrangement while you're
arranging, and the arrangement never buries the design bench while you're
designing.
**It is not a second canvas.** REAPER has exactly one arrange timeline. Design
View does not create a parallel surface, a separate window, or a duplicate
project. It is the *same timeline* with a curated, filtered track set and the
inactive tracks parked. Anyone expecting a second arrange view will be
disappointed — and that expectation must be headed off in the spec, not
discovered in review.
**It is not a *literal* second canvas.** REAPER has exactly one arrange timeline.
Design View does not create a parallel surface, a separate window, or a duplicate
project — that stays true and is enforced in review. It is the *same timeline* with
a curated, filtered track set and the inactive content parked.
> **Superseded in one respect (2026-07-23).** The original claim here was that
> Design View separates only *tracks*, not *items*. That is now extended: the
> settled §Two-canvas direction adds **item-level** mode separation on shared tracks
> via REAPER fixed lanes — so each stance can own its own items as well as its own
> tracks. It is still not a literal second surface/window/project; it is a mode
> *projection* over the one timeline that now reaches items. Read §Two-canvas
> direction for the settled shape; the sentence "It does not separate items" no
> longer holds.
This is the visibility/processing analog of the capture pillar's load-bearing
rule. The capture pillar says *capture and placement are separate acts*. Design
@@ -345,3 +354,326 @@ tooltip on the switch.
deleted or a folder is restructured while parked, the index holds a stale GUID.
The pure model should tolerate unknown GUIDs (ignore-and-prune on next reconcile)
— noted as a hardening item for the build, not a phase-defining decision.
---
# Two-canvas direction — settled (2026-07-23)
> **Status: settled with Daniel (2026-07-23).** This section reopens and supersedes
> the "not a second canvas" decision above. Daniel's stated intent (verbatim): *"I
> wasn't the one who specified single-canvas, that emerged from REAPER's design. I
> want to come as close as possible to supporting two separate design and arrange
> canvases, which are actually the same project but have different items and
> leaves."* The single-canvas framing above was a **derivation from a REAPER
> constraint**, not a product choice — so it was reopened, and Daniel has now signed
> off on the mechanism (fixed item lanes) and resolved all five forks. Everything
> under this heading **supersedes** the "not a second canvas" language in *What it is
> (and what it is not)*: Design View is now a mode projection that reaches **both
> tracks and items**. This is an **additive sub-phase** (Phase D2 / Phase E) on top
> of the shipped track-parking work — not a rewrite of it.
## What actually changed in the ask
The shipped model separates modes by **track membership** on one shared timeline:
tag a leaf into Design, and when you flip to Arrange that leaf is parked — hidden,
out of the mix, FX offline. Because items ride their tracks, a Design track's items
already vanish from the Arrange view. **The "different leaves" half of Daniel's ask
is already delivered.** Flipping modes already gives you a different *track set*.
The genuinely new half is **"different items."** Daniel wants two canvases that
differ not only in which tracks are present but in which *items* are present —
including items that live on a track shared by both stances. Concretely: a track
that exists in both Arrange and Design (a show-both reference track, or a folder
carrying its own media) should be able to hold a design take that only appears in
Design and an arrangement take that only appears in Arrange, on the *same track*,
without the two colliding on the timeline. Track-parking can't express that,
because parking is all-or-nothing per track — it can hide a whole track, never a
subset of its items.
So the reframe is precise: **the feature grows from track-level mode separation to
track-level *plus* item-level mode separation.** "Two canvases" = each stance owns
its own item set, layered over the shared project, with tracks it doesn't want
parked as today.
## The hard constraint (and why it's not fatal)
REAPER has exactly one arrange timeline and one project item graph. There is no
API to instantiate a second arrange surface, and the "no second window/no
duplicate project" observation in the shipped framing is *structurally true* — you
cannot conjure a literal second canvas. What is reversed is only the **conclusion
drawn from it**: that item separation is therefore off the table. It isn't. Items
carry per-item state (`I_FIXEDLANE`, `C_LANEPLAYS`, `D_POSITION`, track
assignment) that the tool can drive to make an item present-or-absent *per mode*
without destroying it. "Two canvases" becomes an **item-visibility projection over
one timeline**, exactly as "two track sets" is a track-visibility projection over
one timeline today. Same discipline, one level deeper.
## Mechanism (settled) and the alternatives it beat
**The settled mechanism is Option A — fixed lanes as per-mode item layers.** The two
alternatives (B timebase-offset, C subproject) are kept below because *why they were
rejected* is load-bearing: both collide with settled invariants, and recording that
keeps them from being re-proposed.
### Option A — Fixed lanes as per-mode item layers (SETTLED)
REAPER's **fixed-lane** feature lets a single track hold multiple horizontal lanes
of items sharing one timebase, with **per-lane play/visibility control**. Map each
mode to a lane: Arrange items live in the Arrange lane, Design items in the Design
lane. Toggling a mode sets `C_LANEPLAYS` so only the active mode's lane plays and
shows; the collapse/hidden flags hide the inactive lane. Items keep their real
positions and their real track — nothing is moved off the timeline, nothing is
deleted.
**SDK surface (verified present in `vendor/reaper-sdk`).** Track-side:
`I_FREEMODE = 2` (fixed-lane mode), `I_NUMFIXEDLANES`, per-lane `C_LANEPLAYS:N`.
Item-side: `I_FIXEDLANE`, `C_LANEPLAYS`, `B_FIXEDLANE_HIDDEN`. Two operational
notes for the implementation spec: **`I_FREEMODE` changes require
`UpdateTimeline()`** to take visible effect, and fixed lanes shipped in **REAPER 7**,
which sets the runtime floor (see fork 5).
- **Fidelity to the two-canvas feel:** high. Same track, same time position, but the
design take and the arrange take are genuinely distinct item sets that never
co-occupy the view. This is exactly "same project, different items."
- **Non-destructiveness:** strong. Lane assignment and `C_LANEPLAYS` are
reversible flags; the item is never relocated in time or deleted. Snapshot the
prior lane state, restore on toggle — a direct extension of the existing
snapshot/restore contract to a new flag family.
- **Fit with park/restore:** clean. It's the same architecture — the planner gains
an item-lane op family alongside the track-flag op family. Pure model stays pure:
lane math (which lane = which mode, which `C_LANEPLAYS` value per mode) is
REAPER-free and unit-testable, mirroring the existing flag planner.
- **Capture-pillar precision:** untouched. Capture writes files to the bank and
never inserts into the timeline; lanes are a placement/view concern downstream of
capture. The null test and bit-identical invariants are unaffected because no
capture path changes.
- **Portability/persistence:** native. Fixed lanes and lane-plays are stored in the
`.rpp` by REAPER itself; the tool only persists which lane maps to which mode
(a small addition to the `"reasampler"` view section).
- **Cost / caveats:** fixed lanes are a real REAPER mode on a track — enabling
`I_FREEMODE=2` changes how that track behaves for the user (lane buttons, comp
behavior), and users unfamiliar with fixed lanes see new UI on managed tracks.
Under the settled **auto-tag** membership rule (see *Membership — settled* below),
a track becomes lane-split when it first needs to hold content for a second mode,
not by a manual "make this a two-canvas track" opt-in. Requires **REAPER 7**, where
fixed lanes shipped — this sets the runtime floor.
### Option B — Timebase-region offset (each mode owns a stretch of the timeline)
Give each mode a disjoint region of the single timeline — Arrange from 0, Design
starting at some large offset (say bar 1000). Items are authored in their mode's
region; toggling a mode scrolls/zooms the arrange view to that region
(`GetSet_ArrangeView2`) and optionally sets the loop/time selection there.
Different items appear because you're *looking at a different part of the timeline*.
- **Fidelity:** medium. It reads as two workspaces if the scroll is snappy, but
they share tracks vertically — a Design track is still a lane on the same track
strip, just at a different horizontal offset. It doesn't give per-track item
layering; a shared track shows both stances' items if you scroll between them.
- **Non-destructiveness:** high (items just have positions), but **positions become
semantically loaded** — an item's `D_POSITION` now encodes its mode. That
collides with the capture pillar's null test, which re-inserts a capture *at its
source position* to verify it nulls. If source positions live in the Design
region, the null-test placement logic has to be region-aware. Friction with a
load-bearing invariant.
- **Fit with park/restore:** awkward. Parking is per-track; this is per-region.
Two orthogonal separation axes that don't compose cleanly.
- **Portability:** fine (just positions + a stored offset), but the offset is a
magic constant that leaks into every position calculation.
- **Verdict:** clever, but it overloads timeline position with mode semantics and
rubs against the capture invariants. Not recommended.
### Option C — Dual sub-projects / project tabs (true separation, heavy)
REAPER supports project tabs and subprojects (a project rendered as an item in a
parent). Model Design as a genuinely separate project surface, imported/rendered
back into Arrange. This is the *only* option that yields a literally separate
canvas.
- **Fidelity:** highest — it *is* two canvases.
- **Cost:** enormous, and it fights the whole premise. "Same project" is Daniel's
explicit constraint; a subproject is a *different* project file with its own
item graph, its own render boundary, and a copy/sync problem between the two. The
capture pillar's "one bank per project, GUID-keyed project identity, travels with
the .rpp" assumption breaks — which project owns the bank? Persistence,
reconcile, and the show-both/derivation model all have to be rethought across a
project boundary. This is a different product.
- **Verdict:** rejected. It satisfies "two canvases" literally by violating "same
project." Daniel asked for *close as possible* within one project, not a second
project.
## The settled shape
**Option A — fixed lanes as per-mode item layers — layered on top of the shipped
track-parking model.** The shipped model already delivers "different leaves"; this
adds "different items" using a native REAPER mechanism that (a) preserves every
precision invariant, (b) extends the existing snapshot/restore architecture rather
than replacing it, (c) keeps the pure/shell split intact (lane math is pure, lane
writes are shell), and (d) persists natively. It is the transplant-a-known-pattern
move: REAPER already solved "multiple item sets, one track, one timebase,
switchable visibility" for comping — we repurpose comping lanes as mode lanes.
The mental model: **modes are a projection, and now the projection reaches items as
well as tracks.** A track can be Arrange-only (parked in Design as today),
Design-only (parked in Arrange as today), or **shared-and-lane-split** (present in
both, but showing each mode its own lane of items). The third case is the new
capability and the concrete meaning of "two canvases."
Phase namespace: **Phase D2 / Phase E** (per the lettered-phase convention for
parallel features) — additive to the shipped D1 track-mode work, not a rewrite.
## Membership — settled (auto-tag by active mode at creation)
The earlier draft floated a manual per-track opt-in ("make this a two-canvas
track"). **That is superseded.** The settled rule, verbatim from Daniel:
> *"Items AND tracks added to the timeline in arrange mode or design mode should be
> tagged as for that mode only. Existing tracks and items default to arrange. If an
> item is added to arrange mode, it does not appear in design mode. If an item is
> added in design mode, it is NOT part of the arrange mode."*
Unpacked into the settled model:
- **Auto-tag at creation, not manual opt-in.** New content — **both new tracks and
new items** — is tagged to whatever mode is **active at the moment it is created**.
There is no separate "convert this track" gesture; membership follows the active
stance automatically.
- **Pre-existing content defaults to Arrange.** Every track and item that existed
before the mode system (or before this sub-phase) is an Arrange member, exactly as
the track-level default already established.
- **Membership is exclusive.** Design-created content never appears in Arrange, and
Arrange-created content never appears in Design — the one exception is the existing
**show-both** escape hatch, which continues to pin a leaf visible across modes.
- **Track level unchanged; item level is the new reach.** At the track level this is
the same tag-to-active-mode rule already settled. What is new: when a **shared**
track (present in both stances — a show-both track, or a folder carrying its own
media) gains an item in Design mode, that item lands in the track's **Design lane**
and is invisible/silent in Arrange, and vice versa. That per-item split is the
fixed-lane mechanism doing its job.
This resolves the old fork 2 (opt-in vs. automatic) in favor of **automatic**, and
it is the through-line that also settles fork 4 (capture placement) below.
## Inactive-mode items — settled (hidden AND silenced)
When you are in one mode, the other mode's items on a shared track are **both hidden
and silenced**: the off-mode lane is set `C_LANEPLAYS = 0` — neither shown nor
played. This matches the exclusive-membership rule (Design-created content is *not
part of* Arrange, so it must not sound there) and the track-level principle already
settled: **flipping modes is a real change, not a cosmetic one.** A user who wants
the design lane audible under the arrangement uses show-both, which is the deliberate
opt-out; the default is full separation.
## Managed vs. manual lanes — settled (2026-07-23)
Fixed lanes are not only the mechanism we borrow for mode-item layering — they are
also **REAPER's native comping surface.** A user may want *their own* multiple lanes
on a track (manual comp takes, alternate reads) that have nothing to do with the mode
system. So a fixed-lane track can host **both** kinds of lane at once, and the tool
must not confuse them. Daniel's constraint, verbatim intent: *"as long as I can still
opt in manually to put multiple lanes on a track. we must index which lanes are
managed and which lanes are manual."*
The settled distinction:
- **Managed lanes** — minted and owned by the mode system to represent a mode's item
set on that track. Mode toggles drive these: the active mode's managed lane shows
and plays (`C_LANEPLAYS`), inactive-mode managed lanes are hidden and silenced.
These are the lanes the two-canvas mechanism creates and controls.
- **Manual lanes** — lanes the user created outside the mode system (native REAPER
comping, alternate takes). The mode system did **not** mint these and **must never
touch them**: a mode toggle never shows, hides, silences, re-lanes, or re-plays a
manual lane. Its `C_LANEPLAYS` state is the user's, left exactly as they set it.
This is the fixed-lane analog of the two invariants already load-bearing in D1 —
*never touch `B_MUTE`/`I_SOLO`* and *never touch the master* — extended to a third
surface: **never drive a lane the tool did not mint.** It is the same non-destructive
promise (the tool owns only what it created) reaching one level deeper, into the lane
dimension.
### Lane-ownership index (the new data)
The tool maintains a **lane-ownership index**: per (track GUID, lane) it records
whether the lane is **managed** (and if so, which mode owns it) or **manual**. This
rides in the `"reasampler"` `view_state` alongside the membership index, GUID-keyed
and portable — same travel-with-the-`.rpp` guarantee as everything else in the view
section. Deciding *which lanes a toggle may touch* (managed only) is a **pure,
testable** query over this index; reading REAPER's live lane state is **shell**. The
seam is the same as everywhere else in the phase.
> **Lane-identity fragility — flag for the implementation spec, not solved here.**
> REAPER's `I_FIXEDLANE` is the lane's identity and is how the ownership index keys to
> a specific lane. Whether `I_FIXEDLANE` values survive lane reorder / renumber /
> deletion without the index going stale is a **design point the technical spec must
> resolve** (the same class of concern as GUID-keyed reorder-safety for tracks). The
> product decision is only that managed and manual lanes are indexed and distinct; how
> the index stays correct under lane reordering is an implementation call, not a
> product one.
### What a mode toggle does to manual lanes (intended behavior)
A mode toggle **leaves every manual lane exactly as the user set it** — its
`C_LANEPLAYS` (play/show) state is untouched across the toggle. Only managed lanes are
partitioned into active (shown/played) and inactive (hidden/silenced). Concretely: if
a shared track has an Arrange managed lane, a Design managed lane, and two manual comp
lanes the user is auditioning, flipping Arrange↔Design swaps which managed lane plays
and leaves both manual lanes precisely as they were.
**Interaction with auto-tag.** Auto-tag applies to content the user adds as **normal
timeline content** — items dropped, recorded, or placed onto the arrange surface in
the active mode. It does **not** reach into a lane the user is hand-managing: items a
user adds to a **manual** lane are **not** auto-tagged to the active mode, because the
user has taken explicit lane control there and the tool respects that boundary — the
manual/managed distinction is exactly the signal that this content is outside the mode
system.
> **Auto-tag/manual-lane boundary heuristic — flag for the implementation spec.**
> Distinguishing "item added as normal timeline content (auto-tag it)" from "item added
> to a manual lane the user hand-manages (leave it alone)" may need a heuristic at the
> detection layer — e.g., an item landing in a lane the ownership index marks manual is
> exempt from auto-tag; an item landing outside any managed lane on a mode-managed
> track follows the active-mode rule. The precise rule is an **open implementation
> design point**, not invented here. The product decision is the boundary itself:
> manual-lane content is off-limits to auto-tag.
## Capture placement — settled (mode-aware)
Daniel's rule is general over *"items added to the timeline."* So an **explicit**
placement while in Design mode — including capture-and-place — lands the item in the
**Design lane**; the same rule governs manual insertion. This is the same auto-tag
rule reaching the placement pillar, not a new special case.
**The capture load-bearing principle is untouched.** Capture still writes a file to
the bank and an index entry and **never** auto-inserts into the timeline. This rule
governs only *where an explicit placement lands* once the user chooses to place —
it does not create any auto-insertion path. The precision invariants (null test,
bit-identical repeats, non-destructive, exact bounds, relative paths) are entirely
unaffected: no capture path changes; lanes are a placement/view concern downstream
of the file.
## REAPER version floor — settled (v7)
Fixed lanes shipped in **REAPER 7**, so this sub-phase sets the runtime floor at
**v7**. The full API surface (track-side `I_FREEMODE=2` / `I_NUMFIXEDLANES` /
`C_LANEPLAYS:N`, item-side `I_FIXEDLANE` / `C_LANEPLAYS` / `B_FIXEDLANE_HIDDEN`) is
**verified present in the vendored SDK header**. No version gate branch is needed —
below v7 the sub-phase is simply unavailable.
## Technical consideration — detecting new content (for the implementation spec)
**REAPER exposes no clean "item added" / "track added" event callback.** The auto-tag
rule therefore requires the shell to detect new track/item GUIDs by **diffing project
state on the panel's existing timer**. The `bank_panel` already polls and fingerprints
the bank; this extends that same machinery to the timeline's tracks and items.
- **Cadence / correctness.** Each poll, the shell compares the live track/item GUID
set against the previous poll's set; **any GUID new since the last poll is tagged to
the then-active mode.** The diffing cadence and the "new-since-last-poll ⇒ tag"
correctness rule are a design point the implementation must handle (e.g., first poll
after project open must not mass-tag pre-existing content — pre-existing defaults to
Arrange, per the membership rule).
- **Pure/shell split.** The **tagging decision** — given a set of new GUIDs plus the
active mode, produce the membership writes — is **pure and unit-testable**, mirroring
the existing planner. The **detection** — diffing REAPER's live item/track set each
timer tick — is **shell**. Keep the seam here: the shell hands the pure model "these
GUIDs are new, the active mode is M," and the model returns the membership mutations.