diff --git a/docs/COMPLETED.md b/docs/COMPLETED.md index 4456da6..6c97251 100644 --- a/docs/COMPLETED.md +++ b/docs/COMPLETED.md @@ -1660,3 +1660,49 @@ v2 blob keeps the behaviour it had rather than losing every FX state. **Left open, filed to `docs/TODO.md`:** FX-GUID stability itself — whether `TrackFX_GetFXGUID` survives a chain reorder while parked — is unverified in the DAW (SWS issue #802 names a specific way it might not hold). + +### Design View mode-switch redesign — FX off the switch path, one undo point + +Four waves, merged to `dev` as `da84012`, that took the per-FX offline/online work +off the Design View mode switch's synchronous path and collapsed the switch's undo +footprint to a single point. + +The first wave split the pure layer: park plans became flag-only, the FX work list +is now derived separately (new `src/core/view/fx_state.{h,cpp}` — `desiredFxState`, +`fxRestoreTargets`, `resolveFxWork`), the park record's two halves now release +independently (the flag half at restore, the FX half only once the reclaimer has +actually driven the chain back online), and a new `applied_state.{h,cpp}` +fingerprints what the last apply actually drove, for a reconcile pass to diff +against a reloaded model. + +The second wave restructured the shell to match: FX writes were stripped out of the +mode switch itself, `src/shell/view/view.cpp` split into `view_park`, `view_lanes`, +`view_reconcile`, and `view_tracks`, and `view_fx_park.{h,cpp}` was deleted +outright. A track that reads park-shaped (flags only) but carries no park record is +now handed back visible with a named console report ("EXPOSE") rather than +silently adopted as a park or refused. + +The third wave added the reclaimer: new `src/shell/view/fx_reclaim.{h,cpp}`, a +debounced (`kParkHoldMs`, 3s) and time-boxed (`kOnlineBudgetMs`/`kOfflineBudgetMs` +per tick) background convergence loop that offlines parked chains and restores +returning ones off the switch path. Each track moves atomically per step, the +derived work queue is owned by a generation-counted `FxReclaimQueue` so a mid-drain +invalidation is never applied over stale state, the offline direction (not online) +is gated off the transport, and a completed record's release is held in memory and +only made durable on the project's next save — never on the drain's own +initiative. + +The fourth wave collapsed the undo footprint: a new `src/shell/undo/` module holds +the one REAPER undo-block opener, `UndoBlock`, which every extension-owned block in +the tree — thirteen call sites across `shell/actions`, `shell/capture`, +`shell/bank_ops`, and `shell/view` — now routes through, plus `undo_intercept`'s +interception of REAPER's own Undo/Redo dispatch (ids `40029`/`40030`), which ends +the FX reclaim span before REAPER runs the command so the switch's own undo point +mints first rather than being caught mid-drain. + +**Left open:** `fxReclaimActivity()` — added in the fourth wave so a UI indicator +could paint the drain's direction — shipped with no caller; a separate, +not-yet-merged branch supplies one. `src/shell/view/CLAUDE.md` still carries a +`[verify — DAW]` note on whether a redo can re-strand a track (rebuilding the +pre-switch `view_state`, which carries no snapshot for a track that reads parked) +— unresolved.