One named slot: its header, its children, its gaps, and its "+" buttons (ADR-0005 decisions 5, 6, 8, 11, 13).
Validity is a property of the slot, and it arrives as markup
Decision 5 makes drop-target validity per-slot rather than per-gap, and
decision 6 makes it reach the client as markup. So during a drag session
this component stamps data-drop="ok" on the slot when the server's
one-shot Edit.Targets.droppable_slots/3 accepted it and data-drop="no"
when it did not, and hover highlighting is CSS on that attribute. There is
zero round-trip per hover, the client holds no validity logic to fall out
of sync, and the highlight cannot disagree with the server because it is
the server's answer.
Outside a drag session the attribute is absent entirely, which is what distinguishes "not a target for the block being dragged" from "no drag is happening".
A refused slot that has a data-flow reason to give also carries
data-drop-reason - not_assignable, or fixable_by:<block id> naming
the block whose declaration an author would change (ADR-0003's 2026-08-29
amendment). It is absent when the refusal was structural, when it was for
room or for the dragged block's own subtree, or when different gaps in
the slot refused for different reasons: those are refusals with no single
honest sentence to show, and an attribute is not the place to guess one.
Nothing reads it yet; it is markup so that the hover affordance that
eventually does needs no round-trip and no JavaScript, exactly as
data-drop needs none.
The gaps, and why "+" is always there
A slot with n children has n+1 gaps, each carrying data-parent-id,
data-slot and data-index - the DOM contract decision 7's hook reads.
Each also carries a "+" button, and it is rendered whether or not a drag
is in progress: decision 8 wants every drop target reachable without
dragging, and an affordance that only appears mid-drag would be reachable
only by dragging. The palette that opens is filtered by the same predicate
the drag uses, not a parallel implementation of it.
The rail partition
:secondary and :failure are both attached rails rather than body
slots (amendment 10h's placement row), and ViewModel.rail?/1 is that
partition. What is stamped here is the placement the two share; the
vocabulary that tells them apart - dashed and warning-family for an
interrupt, solid and error-family for a failure path - belongs to the
bead that renders :failure, and data-slot-style is the hook it
needs. The container's boundary box is derived from the same partition,
one level up in BlockNode.
Recursion
Slot renders children via BlockNode and BlockNode renders slots via
Slot, and that is the whole tree. Groups, lanes, branch arms and
interrupt rails are these same two components differing only by the
layout and slot_style metadata decision 10 put on palette_entry/0.
There is no Group component and no Parallel component, and decision 13
says there must never be one.
Summary
Functions
One slot: header, findings, and alternating gaps and children.
Functions
One slot: header, findings, and alternating gaps and children.
Attributes
slot(StatifierBlocks.ViewModel.Slot) (required)parent_id(:string) (required)drag(:any) - Defaults tonil.selected_id(:string) - Defaults tonil.target(:any) (required)icon(:any) - Defaults tonil.class(:string) - Defaults tonil.