One undo-block opener for the whole tree: fourteen sites route through it, and the reclaim span can no longer swallow a user's act

The drain refuses a tick inside a foreign block; applyMode wakes the reclaimer
before it opens the span.
This commit is contained in:
2026-08-07 16:38:11 -04:00
parent b2f6b3f1d0
commit b7b93a7ac3
25 changed files with 561 additions and 313 deletions
+12 -2
View File
@@ -11,6 +11,7 @@
#include <vector>
#include "shell/persist/session.h" // flushViewState — the release's durable half
#include "shell/undo/undo_block.h" // undoBlockOpen — the reverse of the nesting rule
#include "shell/view/view.h" // modeApplyInProgress, transportBlocksModeSwitch
#include "shell/view/view_park.h" // fxGuidString — the one FX-identity read
#include "shell/view/view_tracks.h"
@@ -233,12 +234,21 @@ void invalidateFxReclaim() {
bool fxReclaimDrainInProgress() { return g_draining; }
void fxReclaimTick(ReaSamplerSession& session, ReaProject* proj) {
// Neither guard may close the span: both name a caller that is still on the stack and
// still owns it — the outer drain, or the applyMode that opened it. Both re-tick.
// NO guard here may close the span: each names a caller that is still on the stack and
// still owns it — the outer drain, or the applyMode that opened it. All re-tick.
if (g_draining) return;
// The model is mid-rewrite and its records are being consumed out from under any
// plan derived from it. The apply invalidates on its way out, so nothing is lost.
if (modeApplyInProgress()) return;
// The MIRROR of the rule that no block may open over the span: an extension undo
// block is open — a capture, a bank verb, an ingest — and its own message pump is how
// this tick arrived. openSpan from in there would begin at depth 2, and that block's
// close would then mint under the SPAN's label and destroy the user's own point.
// Refused whole rather than written unspanned, which would instead absorb the FX
// toggles into the user's act. Starves nothing: the wakeup, the derived queue and the
// project pin are all untouched (this returns before beginTick), so the first tick
// after the block closes drains exactly as if this one had never been due.
if (undoBlockOpen()) return;
DrainLatch latch;
// beginTick owns what a pin/project mismatch means. What is local to here: the model