StatifierUI

CI Hex.pm Version Hex Downloads Hex Docs License

Viewers and inspectors for statifier executions: a Livebook inspector, LiveView components, and an expression field for predicator expressions. Every pane reads one trace stream, written down as a language-neutral wire format, so a UI in another language can read the same stream.

Why a UI over the trace

An execution in a session is a process holding a configuration and a datamodel, and without a viewer the way to learn why it stands where it does is to read logs or add prints to the host. Statifier already emits a trace effect at every phase boundary of its algorithm, stamped with its macrostep and round, with source locations kept on states, transitions and expressions. This package folds that stream into panes - the active configuration as a diagram, an event log by macrostep and round, the datamodel with what the last macrostep changed - and nothing in the engine changes to support it. Each pane is a pure function of the message list, so the same view renders in Livebook, in a LiveView page, or as a string in a test.

Install

def deps do
[
{:statifier_ui, "~> 0.10.0"}
]
end

The :kino (Livebook) and :phoenix_live_view integrations are optional dependencies: add whichever your host renders with.

Basic usage

A library loan, renewed once and then due, observed by a subscriber and rendered as panes:

xml = """
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="on_loan">
<datamodel>
<data id="renewals" expr="0"/>
</datamodel>
<state id="on_loan">
<transition event="loan.renew" target="on_loan">
<assign location="renewals" expr="renewals + 1"/>
</transition>
<transition event="loan.due" target="due"/>
</state>
<state id="due">
<transition event="loan.returned" target="returned"/>
<transition event="loan.lost" target="lost"/>
</state>
<final id="returned"/>
<final id="lost"/>
</scxml>
"""
{:ok, chart} = Statifier.compile(xml)
# Hand the subscriber to the session at start, so it sees the initialize burst.
{:ok, sub} = StatifierUI.Trace.Subscriber.start_link(machine: chart, source: xml)
{:ok, session} =
Statifier.Session.start_link(chart, trace: true, subscribers: [sub], session_id: "loan_42")
:ok = StatifierUI.Trace.Subscriber.attach(sub, session, subscribe: false)
# send_event/2 is a cast; the snapshot call returns once both macrosteps are done.
:ok = Statifier.Session.send_event(session, "loan.renew")
:ok = Statifier.Session.send_event(session, "loan.due")
_ = Statifier.Session.snapshot(session)
messages = StatifierUI.Trace.Subscriber.messages(sub)
StatifierUI.Inspector.diagram(chart, messages) # Mermaid, with "due" classed active
StatifierUI.Inspector.event_log(messages) # Markdown, one section per macrostep
StatifierUI.Inspector.datamodel(messages) # Markdown, "renewals" at 1

messages is the whole execution so far, and every pane is a function of it. StatifierUI.Trace.Json.encode_lines(messages) writes the same stream as JSON Lines in the trace wire format, for a UI written in something other than Elixir.

Documentation

Compatibility

Contributing

mise install # provision erlang + elixir
mix deps.get
mix quality # the full gate: format, compile, credo, dialyzer, docs, tests

mix quality --profile loop is the faster inner-loop variant. CI runs the full gate on every push and pull request; see .quality.exs.

License

MIT. See LICENSE.