StatifierBlocks.Runtime.Marks (StatifierBlocks v0.26.0)

Copy Markdown View Source

A trace read model and a provenance map become the canvas's run marks.

The editor already draws run marks - %{active: MapSet.t(block_id), invoke: {block_id, outcome} | nil} threaded down StatifierBlocks.Editor.Canvas to every StatifierBlocks.Editor.BlockNode - but nothing in this package produced them: a host holding a run was expected to name the blocks itself. This is that step, done once and in the open, so the canvas can sit in the debugger's diagram seat rather than beside it.

The input is a trace read model - statifier-ui's StatifierUI.Live.State, the struct an ops view already holds - and the provenance map the same document compiled to. The output is the marks map, or nil when there is nothing to mark, which is the same "no run over this document" the editor already threads.

The seam is a read on one side and a resolution on the other

statifier-ui answers where a run is, in the chart's own state ids (StatifierUI.Inspector.active_configuration_ids/2 and active_invokes/2, both added in 0.9.0). This package answers which block a state id came from (StatifierBlocks.Provenance.owners_of_states/2). Neither half knows the other's vocabulary, and neither needed a callback to be handed one: the whole of the join is the two reads composed here.

A state id the provenance map has never heard of is dropped rather than raised on, because owners_of_states/2 drops it - a configuration naming a state from a different chart marks the blocks it can and no more.

invoke_type, not an outcome

The mark's second element is the canvas's data-invoke-outcome: how a call came back, nil while it is still out. statifier-ui's read pairs a live invocation with its invoke_type instead, and says why in its own docs: the wire format defines no outcome field for an invocation, and deciding when one has "finished" is the engine's call rather than a read's. So a live invocation is marked with nil - a call with no answer yet, which is precisely what a live invocation is - and the type is not stamped into an attribute that means something else. A block whose invocation has ended carries no mark at all, because it is no longer live.

Unguarded, and no compile-time dependency on statifier-ui

Like StatifierBlocks.Runtime.FixtureRuns, this module carries no LiveView presence wrapper and names no LiveView module: it is a pure function of a read model and a map, and the headless suite - the CI job that proves this package compiles and passes with LiveView absent - exercises it directly.

statifier_ui is optional here for the same reason LiveView is, so the two reads are never named as call targets either. The module is read from application config and dispatched dynamically, the way StatifierBlocks.Editor.Field reaches statifier-ui's expression input and for the same reasons: the compiler stays quiet in a tree without the package, and a test can point the key at a module of its own to exercise the resolution with the package absent or present. Without a resolvable read, from_trace/2 marks nothing.

The read model itself is taken as data - messages, selection and initial_configuration are public fields of StatifierUI.Live.State, and reading them costs no dependency at all. That is also why the two reads go to StatifierUI.Inspector rather than to State.configuration_ids/1: State wraps the configuration read but exposes no invoke read, so one module answers both halves from the same options rather than one half coming from each.

Summary

Types

t()

What the canvas accepts: the blocks a configuration is inside, and the block whose invocation is live.

Functions

The run marks for state's current selection, resolved through provenance.

Types

t()

@type t() :: %{
  active: MapSet.t(String.t()),
  invoke: {String.t(), String.t() | nil} | nil
}

What the canvas accepts: the blocks a configuration is inside, and the block whose invocation is live.

Functions

from_trace(state, provenance)

@spec from_trace(map(), StatifierBlocks.Provenance.t()) :: t() | nil

The run marks for state's current selection, resolved through provenance.

state is statifier-ui's StatifierUI.Live.State - anything carrying its messages, selection and initial_configuration fields - and the selection is whatever the scrubber left there: the live tip, or one macrostep. A macrostep that stamped no configuration of its own draws the newest one below it (statifier-ui's carried-configuration rule), so a carried point marks the same blocks the point it was carried from does.

nil when there is nothing to mark: a stream carrying no session.start to resolve names through (the late-attach case, and the empty stream), a configuration naming no block this map owns, or a tree where the read itself does not resolve.