Undo and redo over StatifierBlocks.Edit.t() commands (ADR-0005
decision 3), and the one funnel every editor command actually goes
through: commit/4 runs Edit.check_config/3 before Edit.apply/2, so
"invalid config never reaches the document" is a property of this
module, not a rule an editor shell has to remember to enforce itself.
The gate runs on undo and redo too
Edit.apply/2 is purely structural; it never asks a block type whether
a config is valid. commit/4, undo/3 and redo/3 all route through
the same private funnel, and that funnel always calls
Edit.check_config/3 first - undo and redo included, not just the
initial commit. This is deliberate, not an oversight: one code path
means one thing to test, and it is the strict reading of decision 9
("invalid config never reaches the document" is a property of every
path that can produce a document, not only the first one).
It is also sound, not merely convenient. The config an inverse restores was already in the document at some earlier point, which means it already validated - under whatever palette was in effect when it got there. ADR-0005 decision 15 makes the editor single-session: one palette, built once, for the whole session. A host that swapped the palette out from under a live session could in principle see an undo refused by a rule that did not exist when the config was written, but that scenario is exactly what decision 15 puts out of scope. Within a single session the palette never changes underneath the history, so the config an undo or redo restores always validates the same way it did the first time.
Summary
Functions
Whether redo/3 would have anything to do.
Whether undo/3 would have anything to do.
Applies command to document through the gate, pushes its inverse
onto the undo stack, and clears the redo stack - a fresh commit
invalidates whatever redo/3 would have replayed.
Builds an empty history. Options: :limit, a pos_integer() bounding
the undo stack, or :infinity (the default - never drops an entry).
The mirror of undo/3: re-applies the top of the redo stack through the
same gated path, and moves its inverse back onto the undo stack.
Types
@type t() :: %StatifierBlocks.Edit.History{ limit: pos_integer() | :infinity, redo: [StatifierBlocks.Edit.t()], undo: [StatifierBlocks.Edit.t()] }
undo and redo hold commands in the order they would be applied
next: List.first(undo) is what a call to undo/3 runs, and
List.first(redo) is what a call to redo/3 runs. limit bounds how
many entries undo may carry; :infinity (the default) never drops
one.
Functions
Whether redo/3 would have anything to do.
Whether undo/3 would have anything to do.
@spec commit( t(), StatifierBlocks.Palette.t(), StatifierBlocks.Document.t(), StatifierBlocks.Edit.t() ) :: {:ok, t(), StatifierBlocks.Document.t()} | {:error, {:invalid_config, StatifierBlocks.Block.id(), [StatifierBlocks.BlockType.finding()]}} | {:error, term()}
Applies command to document through the gate, pushes its inverse
onto the undo stack, and clears the redo stack - a fresh commit
invalidates whatever redo/3 would have replayed.
Funnel order (ADR-0005 decision 9): Edit.check_config/3, then
Edit.apply/2, then push and clear. The trailing {:error, term()} arm
is Edit.apply/2's own error union, propagated unchanged.
Builds an empty history. Options: :limit, a pos_integer() bounding
the undo stack, or :infinity (the default - never drops an entry).
@spec redo(t(), StatifierBlocks.Palette.t(), StatifierBlocks.Document.t()) :: {:ok, t(), StatifierBlocks.Document.t()} | {:error, :nothing_to_redo} | {:error, term()}
The mirror of undo/3: re-applies the top of the redo stack through the
same gated path, and moves its inverse back onto the undo stack.
@spec undo(t(), StatifierBlocks.Palette.t(), StatifierBlocks.Document.t()) :: {:ok, t(), StatifierBlocks.Document.t()} | {:error, :nothing_to_undo} | {:error, term()}
Applies the top of the undo stack's own inverse - the command already
captured at commit time, so it is already the correct target - through
the same gated path commit/4 uses. Moves the new inverse apply/2
hands back onto the redo stack, so redo/3 can replay the original
forward command.