The v2 prototype's Data tab: repeating a node over a CMS collection.
An author selects a card, picks "Blog posts", and the card becomes one card per post. In the
template that is a :for comprehension — but the editor does not write raw source and then try to
read it back with a regex. The binding is held as a structured descriptor in the node's meta
and generated into :for on serialize, so it round-trips structurally: reopening the page shows
the picker set to "Blog posts", not an opaque amber chip.
This is the difference between the loop being editable and merely surviving. ADR-9 keeps
hand-written eex_block source byte-for-byte because the editor cannot understand it; a binding
it generated itself, it can.
Sources
A source names a collection the CMS can actually read. They are declared here rather than guessed
from the template so the picker can only ever offer bindings that resolve, and so an extension can
add one through builder:components' sibling registry without the editor learning about blogs.
Summary
Functions
Whether a node repeats.
The closing tag matching open/1.
Removes a node's binding, so it stops repeating.
The binding recorded on a node, or nil.
The <%= for … do %> opening a bound node serializes inside, or nil when it is not bound.
Reads a binding back out of a parsed :eex_block whose source this module generated.
How many preview rows the canvas renders for a bound node — the demo's three, with a badge — since the editor has no real data to loop over.
Binds id to a source. An unknown source key is refused rather than written, so the serializer
can never be handed a loop over something that does not exist.
One source by key, or nil.
The collections the Data tab can bind a node to.
Functions
@spec bound?(MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t()) :: boolean()
Whether a node repeats.
@spec close(MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t()) :: String.t() | nil
The closing tag matching open/1.
@spec delete(MishkaCmsCore.Runtime.PageBuilder.Tree.t(), String.t()) :: {:ok, MishkaCmsCore.Runtime.PageBuilder.Tree.t()} | {:error, term()}
Removes a node's binding, so it stops repeating.
@spec get(MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t()) :: map() | nil
The binding recorded on a node, or nil.
@spec open(MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t()) :: String.t() | nil
The <%= for … do %> opening a bound node serializes inside, or nil when it is not bound.
Reads a binding back out of a parsed :eex_block whose source this module generated.
Returns {:ok, binding} for a loop the editor recognises and :error for any other block — a
hand-written comprehension stays an opaque atomic chip, exactly as ADR-9 requires, because
pretending to understand it is how the old editor corrupted things.
@spec preview_count() :: pos_integer()
How many preview rows the canvas renders for a bound node — the demo's three, with a badge — since the editor has no real data to loop over.
@spec put( MishkaCmsCore.Runtime.PageBuilder.Tree.t(), String.t(), String.t(), keyword() ) :: {:ok, MishkaCmsCore.Runtime.PageBuilder.Tree.t()} | {:error, term()}
Binds id to a source. An unknown source key is refused rather than written, so the serializer
can never be handed a loop over something that does not exist.
One source by key, or nil.
@spec sources() :: [map()]
The collections the Data tab can bind a node to.