# `StatifierBlocks.Edit.History`
[🔗](https://github.com/riddler/statifier_blocks/blob/v0.44.1/lib/statifier_blocks/edit/history.ex#L1)

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.

# `t`

```elixir
@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.

# `can_redo?`

```elixir
@spec can_redo?(t()) :: boolean()
```

Whether `redo/3` would have anything to do.

# `can_undo?`

```elixir
@spec can_undo?(t()) :: boolean()
```

Whether `undo/3` would have anything to do.

# `commit`

```elixir
@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.

# `new`

```elixir
@spec new(keyword()) :: t()
```

Builds an empty history. Options: `:limit`, a `pos_integer()` bounding
the undo stack, or `:infinity` (the default - never drops an entry).

# `redo`

```elixir
@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.

# `undo`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
