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:
@@ -0,0 +1,53 @@
|
||||
# src/shell/undo — the one REAPER undo-block opener
|
||||
|
||||
## Scope
|
||||
|
||||
One type, `UndoBlock`, and one query, `undoBlockOpen()`. Every extension-owned undo
|
||||
block in the tree is opened through this type; nothing here decides what a point is
|
||||
called or whether one is worth minting — each call site still owns its own label,
|
||||
mask, and discard-vs-mint condition. The FX reclaim span is NOT this directory's:
|
||||
`shell/view/fx_reclaim` owns it, and this directory only guarantees no block ever
|
||||
nests over it.
|
||||
|
||||
## Invariants
|
||||
|
||||
- **`Undo_BeginBlock2` is not callable outside this directory, and that is enforced
|
||||
by the compiler, not by review.** Only `undo_block.cpp` and `shell/view`'s
|
||||
`fx_reclaim.cpp` (which owns the span's own begin/end) define
|
||||
`REAPERAPI_WANT_Undo_BeginBlock2` / `..._Undo_EndBlock2`. Every other TU compiles
|
||||
under `REAPERAPI_MINIMAL` with no header in its include graph pulling the SDK in
|
||||
unguarded, so a raw `Undo_BeginBlock2` there is an undeclared identifier. Adding
|
||||
the WANT define back to a third TU is the one way to defeat this — do not.
|
||||
- **Ending the reclaim span is part of opening, and ONLY the outermost block does
|
||||
it.** At depth 0 the span is the only block that can be open, so ending it mints
|
||||
the pending mode switch's point whole before this block exists. At depth > 0 an
|
||||
enclosing `UndoBlock` already ended it, and a close from the inner one would land
|
||||
at depth 2->1 — minting nothing and hijacking the enclosing block's label. That
|
||||
is the same criterion `applyMode`'s reapply is gated on; it lives in the
|
||||
constructor so no call site has to make the judgment. Full reasoning for the rule
|
||||
itself: `src/shell/view/CLAUDE.md`.
|
||||
- **Closing is a destructor.** No throw between the open and the label decision can
|
||||
leave a block open in the user's project, which is why several call sites can
|
||||
allocate freely between the two.
|
||||
- **No label recorded means no point.** `UndoBlock` closes on `("", 0)` unless
|
||||
`recordPoint` was called — the discard form, real from a hand-fired action
|
||||
(DAW-measured; `src/shell/view/CLAUDE.md`). A verb that mutated nothing therefore
|
||||
costs no undo entry simply by saying nothing.
|
||||
|
||||
## Modules
|
||||
|
||||
- `undo_block` — `UndoBlock` (RAII open/close plus `recordPoint`) and
|
||||
`undoBlockOpen()`. The latter is read by `fxReclaimTick`, which refuses a tick
|
||||
outright while any block is open: the drain's lazy `openSpan` would otherwise
|
||||
begin at depth 2, and the enclosing block's close would then mint under the span's
|
||||
label and destroy the user's own point. The refusal consumes nothing — no wakeup,
|
||||
no queue, no project pin — so the first tick after the block closes drains as if
|
||||
this one had never been due.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- The depth counter deliberately does NOT count the reclaim span. The span is the
|
||||
thing this stands off, not a peer.
|
||||
- The counter drops AFTER `Undo_EndBlock2`, not before: the close that reaches depth
|
||||
0 mints a point, and anything re-entered through that must still read a block as
|
||||
open rather than let the drain open a span underneath it.
|
||||
Reference in New Issue
Block a user