States, geometry and illustrative playback

Mermaid stateDiagram and stateDiagram-v2, and PlantUML state blocks, convert to moonrockz/diagrams/state. The model keeps source identities, ordered states, composite ownership, concurrent regions, descriptions, notes and typed pseudostates. Regions and internal descriptions never become transition edges.

Import only moonrockz/diagrams/state to construct a model. The executable model guide shows copied inputs and bounded validation. Dedicated readers avoid SVG and player dependencies. The Mermaid profile and PlantUML profile describe pinned syntax, canonical edits, source recovery and explicit refusals. Check diagnostics before using a model. A readable partial tree does not mean complete conversion succeeded.

Render and inspect

Pass State(model) to SvgRenderer, or call render_state(model). Adaptive, fixed light and fixed dark modes use the shared themes. Supply a different id_prefix for each inline copy. Labels remain escaped text. SVG contains no script, external assets or executable source configuration.

moonrockz/diagrams-svg/state-layout exposes layout, immutable geometry and check. State and region bounds, measured labels, original transition routes, semantic endpoints and displayed endpoints remain available. Geometry.separators() exposes the actual horizontal and vertical compartment lines, including mixed orientations. check checks containment, attachment, route collisions, 11 px minimum text and 4.5:1 minimum contrast. SvgRenderer.connector_geometry(State(model)) exposes final curves, bridge hops and underpass masks; inspect_connectors(svg) inspects the same canonical paths after rendering. check_connectors checks final paths against explicit protected bounds with its own work budget.

State transitions use rounded elbows by default. ConnectorOptions selects Elbow, Rounded or Curved; crossing=Plain opts out of default bridges. The shared router reserves marked approaches and distributes attachments. Region separators and shared fork/join endpoints are not unrelated crossings.

Set a composite state's expanded=false to collapse it. Hidden descendants retain their model identity. A crossing display route meets the visible composite outline, while its semantic target remains the original child. Hidden descendants are outside the visible query and inspection scope. Boundary symbols have explicit attachment points on their owning composite. Unsupported symbols return diagnostics and no substitute rectangle.

Layout limits bound nodes, edges, depth, work, dimensions and SVG output. Invalid input and exhausted limits return errors without successful partial SVG. Model validation, readers, printers and animation each have explicit budgets; do not treat an exhausted result as an empty successful diagram.

Finite scenarios and structural queries

Catalog::state(model) creates stable identities for states, regions, notes and transitions. Visible states and notes have visit occurrences. Regions stay inspectable without acquiring transitions. Parallel transitions retain separate occurrence keys even when both endpoints are equal.

A default illustration has a finite ordered occurrence set. Use PlaybackPlan::scenario with Motion::serial and Motion::parallel for chosen transitions, repeated self-loops, node visits and explicit concurrent groups. Step(key, duration) names a transition; Visit(key, duration) names a visit. Durations must be positive. Plans validate identities, source matching and size/work budgets. Pure sample_at uses only supplied elapsed time.

catalog.graph.route and reachable follow explicit transitions. Reverse queries keep reversed traversal and follow the actual rendered path backwards. Unheaded relations require opt-in undirected exploration. Results distinguish invalid endpoints, hidden endpoints, disconnection and exhaustion. Membership, concurrency, entry/do/exit descriptions and history add no edges.

The executable animation guide and fresh consumer templates show the complete APIs. AnimatedRenderer uses the SVG scene and a supplied plan. Its offline HTML starts still and provides keyboard play/pause, stepping, seek, speed, inspection, route and reachability controls. It respects reduced motion, hidden pages and printing.

Playback illustrates authored structure or caller-supplied scenarios. It does not evaluate guards, run actions, process events, synchronize concurrent regions, choose feasible branches or restore history. A transition to deep history ends at the history symbol without selecting a previous child.

Downloading SVG returns the exact direct static render for the same model, theme and connector options. Repetition, concurrency, visits, reverse paths and current selection do not enter the download or change viewer position, occurrence or speed.

Migration and verification

Exhaustive matches must add Diagram::State, Mermaid state parsed/tree cases, PlantUML Kind::State and Document::State, state occurrences, node/edge and visit kinds, and SVG NodeBindingKind::State. Existing sequence-only APIs keep their contracts. Ambiguous UML bodies require explicit family selection.

Run these commands from the repository root:

mise run state-mermaid-corpus:reference -- --check
mise run state-plantuml-corpus:reference -- --check
mise run validate:state-mermaid-corpus
mise run validate:state-plantuml-corpus
mise run test:state-consumer
mise run test:state
mise run gallery -- --existing-output --site-assets --qualify
mise run build:site
mise run check:site
mise run test:site

The reference tasks use pinned test-only Mermaid and PlantUML tools. Normal portable tests use committed independent fixtures without JavaScript, Java, Graphviz or network access. test:state qualifies stored artifacts without a workspace or docs rebuild. After publication, mise run test:state-consumer -- --published checks fresh registry dependencies without workspace overrides.

View source on GitHub