StatifierBlocks.Plan (StatifierBlocks v0.40.0)

Copy Markdown View Source

Whole-document questions a host asks before it hands a document to the editor.

expressible/3 asks whether every block in a document sits at a place the editor, mounted with a given palette, would itself have let an author put it, and whether every slot the document still has to fill is one that palette can fill. A host installing a document it did not author through the editor - a stored master, a document built in code, one migrated from an older palette - asks it once, and learns whether an author can go on editing that document with the palette they will be handed. expressible?/3 asks the same question and answers a boolean, for a host that only needs the yes or no.

The same predicate the editor uses, asked at each block's own position

StatifierBlocks.Edit.Targets answers "may this block go here" for a gap an author is about to drop into, with four rules (ADR-0005 decision 5): the slot is declared, assignability accepts the block, the slot has room, and the slot is not inside the block's own subtree. This module asks the first three of those rules about a block that is already in the document, at its own {parent_id, slot, index}. The fourth cannot fail for a block already in a tree.

Rule 2 is kind admission only: whether the block's kinds are ones its slot accepts (ADR-0003 decision 3). Its findings are read from StatifierBlocks.Assignability.validate/3, which checks every block at its own position once, and only its :kind_not_admitted findings are reasons here.

A :type_mismatch - a read the environment at the block's position contradicts - is not a reason. It is a validation finding, and the editor does not refuse a drop for one: a slot is offered to a drag when any of its gaps accepts the block, a drop at another gap is committed and flagged, and the editor never blocks an edit for a validation reason (ADR-0005 decision 5). A document holding such a read is one an author could have built in the editor, and can go on editing there.

Reasons

expressible/3 answers :ok, or {:no, reasons} with one reason per thing the editor would refuse, in StatifierBlocks.Document.blocks/1 pre-order and, within one block, in the order the table lists them. Every reason names the block an author has to look at second and the rule first:

ReasonThe rule
{:unresolved, block_id, error}the block's type does not resolve through the palette; error is StatifierBlocks.Palette.resolve/2's
{:slot_not_declared, block_id, {parent_id, slot}}the block sits in a slot its parent's type does not declare (rule 1)
{:no_room, block_id, {parent_id, slot, index}}the block sits past the first child of an :exactly_one or :zero_or_one slot (rule 3)
{:not_admitted, block_id, finding}the block's slot does not admit its kinds (rule 2); finding is the :kind_not_admitted member of StatifierBlocks.Assignability.finding/0
{:unfillable_slot, block_id, slot}the block's :exactly_one or :at_least_one slot is empty, and no block type in the palette is admitted there

A block whose parent does not resolve is not checked against rules 1 and 3 - there is no declared slot set to check against - and the parent's own :unresolved reason is the one an author acts on.

What it does not ask

  • Recipes. :unfillable_slot asks the palette's block types, through StatifierBlocks.Edit.Targets.accepted_types_at/5, not its recipes.
  • Profiles. A mount's profile can hide palette groups from an author; this function sees the palette it is handed. A host that mounts the editor with a narrowed palette passes that palette here.
  • Config validity, compile findings, or slot arity beyond room. An optional slot left empty, or a required slot left empty that the palette can fill, is something an author can finish in the editor.

Summary

Types

Why a document is not expressible through a palette. See the moduledoc's table.

Functions

:ok when every block in document sits where the editor, mounted with palette, admits it, and every required slot that is still empty is one some block type in palette could fill; otherwise {:no, reasons}.

true exactly when expressible/3 answers :ok for the same document, palette and ctx; false when it answers {:no, reasons}.

Types

reason()

Why a document is not expressible through a palette. See the moduledoc's table.

Functions

expressible(document, palette, ctx \\ %{})

:ok when every block in document sits where the editor, mounted with palette, admits it, and every required slot that is still empty is one some block type in palette could fill; otherwise {:no, reasons}.

ctx is the StatifierBlocks.Assignability.context/0 the assignability check is asked with, defaulting to %{} the way StatifierBlocks.Edit.Targets.admits_at?/5 defaults it. A host that mounts the editor with a datamodel document passes the same context here (StatifierBlocks.Assignability.context/1 builds it), so the two answer the same question.

Examples

iex> alias StatifierBlocks.{Block, Document, Palette, Plan}
iex> wait = Block.new("core.wait", id: "blk_WAIT", config: %{"duration" => "48h"})
iex> root = Block.new("core.sequence", id: "blk_ROOT", slots: %{"body" => [wait]})
iex> Plan.expressible(Document.new(root, id: "doc_1"), Palette.core())
:ok
iex> Plan.expressible(Document.new(root, id: "doc_1"), Palette.new(%{"core.sequence" => StatifierBlocks.Core.Sequence}))
{:no, [{:unresolved, "blk_WAIT", {:unknown_block_type, "core.wait"}}]}

expressible?(document, palette, ctx \\ %{})

true exactly when expressible/3 answers :ok for the same document, palette and ctx; false when it answers {:no, reasons}.

The reasons are dropped. A host that shows an author why a document cannot be edited with a palette asks expressible/3 instead.

Examples

iex> alias StatifierBlocks.{Block, Document, Palette, Plan}
iex> wait = Block.new("core.wait", id: "blk_WAIT", config: %{"duration" => "48h"})
iex> root = Block.new("core.sequence", id: "blk_ROOT", slots: %{"body" => [wait]})
iex> Plan.expressible?(Document.new(root, id: "doc_1"), Palette.core())
true
iex> Plan.expressible?(Document.new(root, id: "doc_1"), Palette.new(%{"core.sequence" => StatifierBlocks.Core.Sequence}))
false