# `StatifierUI.Live`
[🔗](https://github.com/riddler/statifier-ui/blob/v0.11.0/lib/statifier_ui/live.ex#L2)

Read-only LiveView components for a host application's ops views: the
current-state diagram and the run-history event log, side by side over
one trace stream in wire format v1 (`docs/wire-format.md`), live or
persisted.

Compiled only when the optional `:phoenix_live_view` dependency is
present (ADR-0004); a host without it gets a stub whose every component
raises with instructions. Nothing else in this package touches
`Phoenix.Component`.

Every component is a **function component over
`StatifierUI.Live.State`** - there is no LiveComponent, no process, and
no `mount/3` here. The host owns the socket, the subscription, and the
events; this module owns markup. That is what makes these embeddable in
a page the host already has rather than a page it has to give up.

## Full fidelity

These are dev/ops surfaces and they render what the stream carries. If
the stream was produced under an ADR-0012 projection profile, `status/1`
says so in a banner and redacted slots render as the stream's own
sentinel - the redacted tenant-facing story is a different component
(sui-hmn), not a flag on this one.

## Embedding

Full worked example: `docs/ops-embedding.md`. The short version, for a
live stream:

    # mount/3
    {:ok, subscriber} = Subscriber.start_link(machine: machine, source: source)
    :ok = Subscriber.attach(subscriber, session, catch_up: true)
    :ok = Subscriber.add_listener(subscriber, self())

    socket =
      assign(socket, :trace, State.new(machine) |> State.sync(subscriber))

    # handle_info/2 - the subscriber's fan-out shape
    def handle_info({:statifier_ui, _session, message}, socket) do
      {:noreply, update(socket, :trace, &State.push(&1, message))}
    end

    # handle_event/3 - the two events the components emit
    def handle_event("statifier_ui_scrub", %{"move" => move}, socket) do
      {:noreply, update(socket, :trace, &State.scrub(&1, String.to_existing_atom(move)))}
    end

    def handle_event("statifier_ui_select", %{"macrostep" => n}, socket) do
      {:noreply, update(socket, :trace, &State.select(&1, String.to_integer(n)))}
    end

    # render/1
    <StatifierUI.Live.ops_view id="ops" state={@trace} />

A persisted stream drops the subscriber and the `handle_info/2` clause:
`State.new(machine, messages: messages)` is the whole difference.

## The diagram is Mermaid source, and no Mermaid client ships

`diagram/1` emits `StatifierUI.Diagram`'s `stateDiagram-v2` source into
a `<pre class="mermaid">`, the shape a Mermaid client renders in place.
Attaching that client is the host's - pass `hook={"MyMermaid"}` and the
element carries `phx-hook`. The JavaScript this package does ship is the
expression field's hooks under `assets/js`, as source for the host's own
bundler to compile (ADR-0009); nothing here renders the diagram for you.
ADR-0008's client-side elkjs SVG renderer is the eventual full-fidelity
diagram and is not built yet; when it is, it replaces this pane's body
and not its contract.

## Class names, not styles

Every element carries a `statifier-ui-*` class and the engine's own
document-order identities as `data-*` attributes (`data-macrostep`,
`data-round`, `data-configuration`), per ADR-0007's sync contract. No CSS
ships: a host page styles these with its own design system, and the
`data-*` stamps are what tests assert on.

# `diagram`

```elixir
@spec diagram(map()) :: Phoenix.LiveView.Rendered.t()
```

The current-state diagram: `StatifierUI.Diagram`'s Mermaid source for
the configuration the selection implies, in a `<pre class="mermaid">`
for the host's Mermaid client to render in place.

The active configuration is also stamped on the element as
`data-configuration` (a space-separated index list), so a test - or a
host's own renderer - can read what is highlighted without parsing
Mermaid.

`active_style` is `StatifierUI.Diagram`'s `t:StatifierUI.Diagram.active_style/0`.
It defaults to `:default`, the shipped light palette. A dark host passes
`:none` and styles the `active` class from its own stylesheet, or passes
its own `classDef` body as a binary.

## Attributes

* `id` (`:string`) (required)
* `state` (`StatifierUI.Live.State`) (required)
* `hook` (`:string`) - Defaults to `nil`.
* `active_style` (`:any`) - Defaults to `:default`.

# `event_log`

```elixir
@spec event_log(map()) :: Phoenix.LiveView.Rendered.t()
```

The run history: one collapsible entry per macrostep, each holding its
rounds and the effects that carry no round.

Clicking an entry sends `select_event` with `macrostep`, which is the
link `StatifierUI.Kino`'s Markdown pane cannot have - a Markdown
document has no click target to send back. The selected entry is open
and marked `- selected`; the mark does not name a diagram, because a
host may compose this pane with no diagram mounted. Where `diagram/1`
is mounted beside it, that entry's configuration is what it draws.

A message list the log refuses - more than one session on one timeline,
which `docs/wire-format.md` forbids - renders as a visible error rather
than raising, so the rest of the ops view keeps working.

## Attributes

* `id` (`:string`) (required)
* `state` (`StatifierUI.Live.State`) (required)
* `target` (`:any`) - Defaults to `nil`.
* `select_event` (`:string`) - Defaults to `"statifier_ui_select"`.

# `ops_view`

```elixir
@spec ops_view(map()) :: Phoenix.LiveView.Rendered.t()
```

The composed ops view: status, scrubber, diagram, and event log.

A host that wants a different layout composes `status/1`, `scrubber/1`,
`diagram/1`, and `event_log/1` itself - this component adds nothing but
an arrangement.

## Attributes

* `id` (`:string`) (required) - DOM id root; panes derive theirs from it.
* `state` (`StatifierUI.Live.State`) (required) - the read model - see `StatifierUI.Live.State`.
* `target` (`:any`) - `phx-target` for the emitted events, when mounted inside a LiveComponent. Defaults to `nil`.
* `scrub_event` (`:string`) - Defaults to `"statifier_ui_scrub"`.
* `select_event` (`:string`) - Defaults to `"statifier_ui_select"`.
* `hook` (`:string`) - `phx-hook` for the diagram element. Defaults to `nil`.
* `active_style` (`:any`) - how the diagram's active highlight is styled - see `diagram/1`. Defaults to `:default`.
* `class` (`:string`) - Defaults to `nil`.

# `scrubber`

```elixir
@spec scrubber(map()) :: Phoenix.LiveView.Rendered.t()
```

The four scrubber controls - First, Prev, Next, Live - and the note
saying which point is on screen.

Each button sends `scrub_event` with `move` set to `first`, `prev`,
`next`, or `live`; `StatifierUI.Live.State.scrub/2` takes it from there.
The buttons are always enabled: with nothing to select every move
resolves to `:live`, which is where the view already is.

## Attributes

* `id` (`:string`) (required)
* `state` (`StatifierUI.Live.State`) (required)
* `target` (`:any`) - Defaults to `nil`.
* `scrub_event` (`:string`) - Defaults to `"statifier_ui_scrub"`.

# `status`

```elixir
@spec status(map()) :: Phoenix.LiveView.Rendered.t()
```

The stream's own header: session id, subscriber status and counts, one
line per diagnostic, and the ADR-0012 projection banner when the stream
carries one.

A persisted stream has no `StatifierUI.Trace.Subscriber` stats, and the
pane says "persisted" rather than inventing a status.

## Attributes

* `id` (`:string`) (required)
* `state` (`StatifierUI.Live.State`) (required)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
