StatifierBlocks.Recipe behaviour (StatifierBlocks v0.24.0)

Copy Markdown View Source

A palette entry that puts down an arrangement rather than a block (ADR-0005 clauses 1C and 2C).

A block type answers "what is this block". A recipe answers "how do these blocks go together": it is handed the position the author armed and the document, and it returns the commands that build the arrangement. The caller wraps them in one {:compound, commands} and commits, so the arrangement is one gesture in and one gesture out.

A recipe is not a block type. It has no type_name, it appears in no document, and nothing resolves a block against it - StatifierBlocks.Palette keeps recipes in a second map for exactly that reason, and the two maps are two namespaces rather than one. A recipe named "deadline" and a block type named "deadline" do not collide.

The two callbacks

palette_entry/0 is StatifierBlocks.BlockType's callback, in every particular: the same optional keys, the same total normalizers, the same fallback to the entry's name when a key is absent. A recipe draws in the palette browser the way a type draws, which is deliberate - an author picking a deadline is not doing a different kind of thing from an author picking a send.

insert/2 is where a recipe differs. It is pure: it reads the document rather than writing it, mints no ids beyond the ones ADR-0005 decision 2 already requires an :insert to carry, and may refuse. A refusal is the ordinary case where the arrangement does not fit the position the author armed, and it is an error term, never an exception.

What a recipe may reach

Clause 3C bounds the commands insert/2 returns to the armed position itself and any slot of the block that encloses it - the block the armed target names as its parent - and nothing above that. A recipe that could write two levels up would move blocks into a region the author is not looking at.

The bound is the caller's to enforce: insert/2 is a pure function answering with commands, and it is StatifierBlocks.Recipe.within_reach?/2 that says whether a returned list stays inside it.

Summary

Callbacks

The commands that build the arrangement at target, or a refusal.

How the recipe draws in the palette browser. ADR-0005 decision 10's map, unchanged.

Functions

Whether every command in commands stays inside clause 3C's bound for an insertion armed at target.

Callbacks

insert(target, document)

@callback insert(
  target :: StatifierBlocks.Edit.target(),
  document :: StatifierBlocks.Document.t()
) ::
  {:ok, [StatifierBlocks.Edit.t()]} | {:error, term()}

The commands that build the arrangement at target, or a refusal.

Called with the armed position and the document as it stands. Pure.

palette_entry()

@callback palette_entry() :: StatifierBlocks.BlockType.palette_entry()

How the recipe draws in the palette browser. ADR-0005 decision 10's map, unchanged.

Functions

within_reach?(arg, commands)

Whether every command in commands stays inside clause 3C's bound for an insertion armed at target.

The bound is two positions wide: the armed position itself, and any slot of the block that encloses it. Both are named by the same block id - the parent in target - so the check is that every command naming a position names that block, and that every command naming a block names one this compound itself inserted.

iex> StatifierBlocks.Recipe.within_reach?({"blk_g", "body", 0}, [])
true

iex> block = StatifierBlocks.Block.new("core.send", id: "blk_s")
iex> StatifierBlocks.Recipe.within_reach?(
...>   {"blk_g", "body", 0},
...>   [{:insert, {"blk_g", "interrupts", 0}, block}]
...> )
true

iex> block = StatifierBlocks.Block.new("core.send", id: "blk_s")
iex> StatifierBlocks.Recipe.within_reach?(
...>   {"blk_g", "body", 0},
...>   [{:insert, {"blk_elsewhere", "body", 0}, block}]
...> )
false