Skip to content

Visualization

Code and JSON render to the same diagram.

A definition you can read is a definition you can trust. Crucible renders any machine to two diagram formats with one method call each.

mermaid := m.ToMermaid() // GitHub-renderable stateDiagram-v2
dot := m.ToDOT() // GraphViz DOT, for richer SVG output

ToMermaid produces a stateDiagram-v2 that renders inline on GitHub and in docs. ToDOT emits GraphViz DOT, better for large, deeply hierarchical machines where Mermaid grows unreadable, and for high-fidelity SVG on slides and docs sites. Both are deterministic: states keep their declared order, edges are sorted, so repeated calls are byte-identical and golden-stable.

Both renderers express the full structure: compound states nest, parallel regions render with dividers (Mermaid) or dashed clusters (DOT), final states draw their terminal marker, and owned states color-code by owner.

The same variadic options tune either format:

m.ToMermaid(state.WithoutGuards()) // drop the [guard] suffixes
m.ToMermaid(state.WithoutOwners()) // drop owner color-coding
m.ToMermaid(state.LeftToRight()) // direction LR
m.ToDOT(state.TopToBottom()) // rankdir=TB

Rendering is a pure function of the machine graph, the very structure the IR carries. A machine loaded with LoadFromJSON and re-quenched renders identically to one forged in code:

ir, _ := state.LoadFromJSON[Stage, Signal, Order](b)
m := ir.Provide(reg).Quench()
fmt.Println(m.ToMermaid()) // same diagram, byte for byte

The diagram below is generated at build time from fooddelivery.NewModel() via ToMermaid(), the same machine the example’s tests exercise, so it can never drift from the code:

stateDiagram-v2
    [*] --> Placed
    state Active {
        [*] --> Active_Fulfillment__Cooking
        Active_Fulfillment__Cooking
        Active_Fulfillment__AwaitingCourier
        Active_Fulfillment__EnRoute
        Active_Fulfillment__AwaitingCourier --> Active_Fulfillment__EnRoute: PickedUp
        Active_Fulfillment__Cooking --> Active_Fulfillment__AwaitingCourier: PlatedUp
        --
        [*] --> Active_Watchdog__OnTime
        Active_Watchdog__OnTime
        Active_Watchdog__Overdue
        Active_Watchdog__Overdue --> [*]
        Active_Watchdog__OnTime --> Active_Watchdog__Overdue: after(30m0s) SLABreached
    }
    Delivered --> [*]
    Canceled --> [*]
    Rejected --> [*]
    Active --> Refunding: Cancel
    Active --> Settling: DroppedOff
    Authorizing --> Active: Authorized [or(generousOrder,and(subtotal ge 5000,priority in ["fast","express"]))]
    Authorizing --> Rejected: Declined
    Placed --> Authorizing: Submit
    Refunding --> Canceled: Refunded
    Settling --> Delivered

These renderers power this very docs site: every embedded statechart is forged from a live machine, never hand-drawn. For the full lifecycle walkthrough behind the diagram, see the food-delivery example.