# src/core/view — pure Design View mode model

## Scope

Pure, REAPER-free Design View model: mode/track membership, folder-derived
visibility, snapshot-based park/restore planning, the per-mode solo cache and its
replay plan, new-content (GUID) detection, and the managed/manual lane-identity
convention that underlies per-item mode separation (fixed lanes). Does **not**
include: the actual DAW-side flag application (hide, CPU-park, per-FX offline,
restore via `B_SHOWINTCP` / `B_SHOWINMIXER` / `B_MAINSEND` / `I_FXEN`, solo
cache/clear/replay via `I_SOLO`) or the never-touch-master/mute enforcement —
those live in `shell/view`.

## Invariants

Design View — additive phase spec (settled decisions; Two-canvas sub-phase,
settled 2026-07-23):

- **Membership.** Default = Arrange; every untagged leaf belongs to it. Leaves
  opt in to Design (or any mode). No track appears in two modes at once except
  via an explicit show-both toggle or parent/folder derivation.
- **Parents are derived, never tagged.** A parent/folder track is visible in
  mode M if either any descendant leaf is visible in M, or the parent belongs to
  M by its own membership; an untagged parent is an Arrange member by default. A
  parent is never parked in any mode it is visible in. Master track is always
  visible and never touched.
- **N-mode model, two-mode UI.** The data model carries arbitrarily many modes;
  the UI ships Arrange + Design. A mode is (stable id, display name, ordinal).
- **Persistence.** Membership index + last-active mode + per-track flag
  snapshots ride in the existing `"reasampler"` project ext-state namespace and
  travel with the `.rpp`.
- **Non-destructive restore (enforced in the pure layer).** For every flag the
  tool drives, snapshot the prior value before parking; on toggle-back, restore
  from the snapshot, never to a hardcoded default. Round-trip (snapshot → park →
  restore) returns every driven flag to its captured value — this is the
  phase's trust anchor, the analog of the capture null test.
- **Disjoint solo surfaces, cached not destroyed.** Solo is per mode: a real
  switch banks the outgoing mode's raw `I_SOLO` values, clears them, and replays
  the incoming mode's verbatim. Same snapshot sense of non-destructive as the
  bullet above — the tool never *loses* the user's solo, it parks it with the mode
  it belongs to. `B_MUTE` and the master track stay untouched absolutely. A
  reapply touches solo not at all.
- **GUID-keyed, reorder-safe.** Membership keys on track GUID (`GetTrackGUID`),
  never track index; tolerates unknown/stale GUIDs (pruned on reconcile via
  `ViewModeModel::reconcile(liveGuids)`). The same rule binds one level down: a
  parked track's per-FX offline state keys on the FX's own identity
  (`TrackFX_GetFXGUID`), never its slot — see `fx_offline`.
- **Relative/portable state only** in the persisted view section (GUID strings,
  mode ids — no absolute paths, no index positions).
- **Show-both semantics.** A per-track "pin visible across modes" flag that
  re-enables processing whenever shown. A show-both leaf appears in every
  mode's visible set and is never parked — its driven flags stay at
  snapshot/restored values. Stored on the membership record; persists;
  togglable per selection.
- **Non-goals / guardrails:** No literal second canvas — a second window or
  duplicated project stays rejected; subproject/second-project-file approaches
  and overloading item `D_POSITION` with mode semantics (timebase-offset
  regions) are rejected paths (the latter collides with the capture null test).
  Every leaf is managed: an untagged leaf is an Arrange member and, when the
  active mode is not Arrange, is fully parked and snapshot-restored exactly
  like a tagged leaf; show-both is the only way to opt a leaf out of parking.
  Restore from snapshot, never to a default: no hardcoded "on" restores.

**Two-canvas sub-phase (Phase D2/E) — settled and landed parts only:**

- **Mechanism: fixed item lanes.** Map mode → lane; toggle drives per-lane
  play/show so only the active mode's lane is present. Items keep their real
  position and real track — nothing is moved in time or deleted.
- **Membership: adoption rule for new items; active mode for new tracks absent an
  explicit tag.** New tracks are tagged to the active mode at creation **only when the GUID carries no
  membership record** — an explicit tag wins over the detector, because the detector
  classifies content the *user* made, not content the tool made and already
  classified. New items follow an
  adoption rule: if the item's track has pre-existing managed-eligible content
  spanning exactly one mode, the item adopts that mode; the active-mode
  fallback applies only when the track is empty or already spans multiple
  modes. Items in manual lanes are excluded from the prior-mode computation and
  are not auto-tagged at all. Membership is exclusive per item except via
  show-both.
- **Managed vs. manual lanes — indexed and distinct.** The tool maintains a
  lane-ownership index — per (track GUID, lane): managed (which mode owns it)
  vs. manual (user-minted, outside the mode system). Mode operations touch only
  managed lanes; manual lanes are never shown, hidden, silenced, or re-laned by
  a toggle, and their `C_LANEPLAYS` stays exactly as the user set it. The
  ownership index rides in `"reasampler"` `view_state` alongside the membership
  index, GUID-keyed and portable. **New invariant — mode operations touch only
  managed lanes:** "the 'which lanes may this toggle touch' decision is a pure
  query over the ownership index; only reading REAPER's live lane state is
  shell."
- **REAPER floor: v7** for this sub-phase (fixed lanes shipped in v7); no
  version-gate branch — below v7 the sub-phase is simply unavailable.
- Precision invariants (null test, bit-identical repeats, non-destructive,
  exact bounds, relative-paths-only) are entirely unaffected by this
  sub-phase — no capture path changes; lane assignment and `C_LANEPLAYS` are
  reversible flags, never a destructive operation.

## Modules

- `view_mode_model` — Design View mode system: mode registry, GUID-keyed membership, folder-tree-aware visibility derivation, snapshot-based park/restore planner, the per-mode `SoloCache` it owns, JSON round-trip.
- `fx_offline` — the per-FX offline snapshot's KEY and its restore resolution: `FxKeying` (Identity / Slot), `FxOfflineState`, the planned `FxOfflineOp`, and `resolveFxRestore`, which matches each captured state to the FX it came from against the chain as it stands at restore time. An identity that is no longer live is DROPPED and counted (`FxRestoreDrops`, reported through `describeFxRestoreDrops`), never re-pointed at a slot — see the `FxKeying` and `resolveFxRestore` comments in `fx_offline.h` for why. Slot keying survives only for snapshots lifted from a pre-identity `view_state` and for park plans, where every live slot is the target by construction.
- `solo_cache` — the per-mode solo surface: `SoloCache` (mode id → GUID → raw `I_SOLO`), the soloed-subset filter, and `planSoloRestore`, whose two drop rules (dead GUID, not visible in the incoming mode) and their reasoning live in its header.
- `view_tree` — pure `I_FOLDERDEPTH`→FolderTree helper for the Design View shell.
- `mode_switch` — REAPER-free segment layout + hit-test for the bank_panel's Design View mode switch.
- `guid_diff` — the pure, REAPER-free core of the D2 Wave-2 new-content detection: `newGuids(previous, current)` computes the GUIDs present in `current` but absent from `previous` (empty GUIDs ignored); `GuidBaseline` tracks the live GUID set across polls for one project, implementing the first-poll-after-open guard (the first `observe()` after construction/`reset()` records a baseline and reports nothing new, so pre-existing content is never mass-tagged) and re-arms via `reset()` on a detected project switch so detection never diffs across two unrelated projects.
- `lane_keys` — the pure, REAPER-free convention mapping a fixed lane's durable REAPER name (`P_LANENAME:n`) to the opaque lane-key `view_mode_model` keys by, plus the managed/manual heuristic both the toggle-apply path and the new-content/auto-tag exemption path share: `kManagedLanePrefix` ("reasampler:") stamps every lane the tool mints; `isManagedLaneName`/`managedLaneKey`/`laneNameForMode`/`modeIdFromLaneName` round-trip a lane name ↔ its owning mode id; `isOnManualLane` is the single predicate governing which lanes a toggle may drive and which items are exempt from auto-tag.

## Gotchas

- REAPER exposes no durable per-lane GUID — the only lane identity is the
  ordinal `I_FIXEDLANE`, which REAPER renumbers on reorder/delete. Lane
  identity must ride on the durable `P_LANENAME` (`lane_keys`), never the raw
  ordinal, or a reorder will silently corrupt managed/manual ownership.
- `kManagedLanePrefix` ("reasampler:") is stable-forever like an action-id
  string — changing it strands the ownership of every already-minted lane in
  every already-saved project.
- The `view_state` blob's version ladder lives beside `kViewStateVersion` in
  `view_mode_model.cpp`. v2 writes the v1 slot array BESIDE the identity array so
  a downgrade keeps the behavior it had; the version field is written but
  deliberately not validated on read, because an unreadable `view_state` falls
  back to a default model and loses every membership tag.
- `guid_diff::GuidBaseline` must have `reset()` called on every detected
  project switch, or the next `observe()` will diff across two unrelated
  projects and mass-tag (or miss) content.
