State diagrams

A state_diagram draws an entity lifecycle coordinate-free: states auto-rank by longest path from the transition graph (back-edges and self-loops route around), initial draws the filled entry dot, final the double border, and each transition carries a trigger [guard] edge label. It is a page-level block — not a diagram shape.

order lifecyclePendingPaidShippedCancelledpayment captureddispatched [stock reserved]customer cancelspartial refund
state_diagram {
  width = 640
  direction = :left_to_right

  state "pending"   { name = "Pending"   initial = true }
  state "paid"      { name = "Paid" }
  state "shipped"   { name = "Shipped"   final = true }
  state "cancelled" { name = "Cancelled" final = true }

  transition "t1" { from = "pending" to = "paid"      trigger = "payment captured" }
  transition "t2" { from = "paid"    to = "shipped"   trigger = "dispatched"  guard = "stock reserved" }
  transition "t3" { from = "pending" to = "cancelled" trigger = "customer cancels" }
  transition "t4" { from = "paid"    to = "paid"      trigger = "partial refund" }
}

Layout

States rank along the flow axis (direction, default :toptobottom) by longest path from the graph's roots; states sharing a rank stack along the cross axis in declaration order. A state with explicit x and y opts out of auto-layout for the rare manual case. layer_gap / node_gap tune the spacing.

Blocks

state_diagram

PropertyTypeRequiredDescription
widthf64noRendered width in pixels; the height follows the content.
directionsymbolnoFlow direction: :toptobottom (default) / :lefttoright.
layer_gapf64noSpacing between ranks (layers).
node_gapf64noSpacing between states within a rank.
ididentifiernoOptional explicit HTML id.
classlist<utf8>noOptional style classes on the <svg>.
descutf8noAccessible description (aria-label + <title>).

Child blocks

SlotAcceptsMultipleDescription
statesstateyesStates; auto-ranked from the transition graph in declaration order.
transitionstransitionyesTransitions between states (trigger [guard] edge labels).

state

PropertyTypeRequiredDescription
idutf8yesStable id transitions reference via from / to.
nameutf8noDisplay name shown in the box (defaults to the id).
initialboolnoEntry state: draws the filled-dot pseudo-state with an arrow into the box.
finalboolnoFinal state: draws the double-border marker.
linkutf8noLink the box to an in-site page (bare page name, or site:page).
xf64noManual x placement (with y, opts this state out of auto-layout).
yf64noManual y placement (with x, opts this state out of auto-layout).
widthf64noBox width.
heightf64noBox height.
classlist<utf8>noStyle classes for the box (replaces the theme defaults).

transition

A transition is a block (not an a -> b connection) because it carries payload: the trigger event and an optional guard, rendered as the standard trigger [guard] label at the edge midpoint. The same from and to renders a self-loop arc.

PropertyTypeRequiredDescription
idutf8yesStable id.
fromutf8yesSource state id.
toutf8yesDestination state id (same as from for a self-loop).
triggerutf8noThe event that fires the transition (the edge label).
guardutf8noGuard condition, rendered as trigger [guard].

Data-driven lifecycles

Like every @children slot, states / transitions accept computed splices — a state-machine model can generate its figure:

state_diagram {
  states = map(machine.states, fn(s: SmState) -> State { { id: s.key, name: s.label } })
  transitions = map(machine.edges, fn(e: SmEdge) -> Transition {
    { id: e.key, from: e.src, to: e.dst, trigger: e.event }
  })
}