StatifierUI.DatamodelExplorer.Diff (StatifierUI v0.9.1)

Copy Markdown View Source

What the datamodel gained, lost, or changed between two points in one run (sui-2uz) - the reading that makes stepping through a persisted trace worth doing, rather than watching a table of current values redraw.

A pure comparison of two StatifierUI.DatamodelExplorer.t/0 panes. Nothing here folds a stream, reads a message, or decides what a macrostep is: StatifierUI.Inspector.datamodel_diff/2 cuts the stream at two adjacent points, builds a pane for each with StatifierUI.DatamodelExplorer.build_live/2, and hands both here. Both halves are therefore reads of what the engine already stamped, and a stream reconstructed by catch-up got there through statifier ADR-0034 replay - which re-drives the core rather than rewinding anything (ADR-0002's inherited clause).

What a slot is

One comparable slot per entry, plus one per Entry.children element qualified as "parent.child" - the same flattening StatifierUI.DatamodelExplorer.Markdown renders rows with, so a slot named in a diff is a row a reader can find in the pane beside it.

Only value-bearing tiers are compared: :data and :runtime (tier 1), :scenario (tier 3), and :system (tier 2a). :function entries are excluded - a tier 2b entry is a predicator provider function in scope, whose "value" is its own description, and a provider set that changed between two macrosteps of one run would be a fact about the host's configuration rather than about the run.

Absence is :absent, not nil

nil is a value the datamodel can genuinely hold (a JSON null), and :undefined is the one StatifierUI.Value decodes $undefined to, so neither can stand for "this slot did not exist". The :absent atom does. A decoded wire value is never a bare atom other than nil, :undefined, :redacted, true, or false (StatifierUI.Value), so :absent cannot collide with one.

Ordering

:added and :changed in the later pane's own entry order, then :removed in the earlier pane's - so the list reads down the table the reader is looking at, with anything that vanished gathered at the end.

Summary

Types

One difference. from is :absent on an :added change and to is :absent on a :removed one; tier is the tier the slot has in whichever pane still holds it.

How one slot differs: it appeared, it holds a different value, or it is gone.

Functions

The atom standing in for a slot that does not exist on one side.

The differences between earlier and later, oldest pane first.

Types

change()

@type change() :: %{
  name: String.t(),
  tier: StatifierUI.DatamodelExplorer.Entry.tier(),
  kind: kind(),
  from: term(),
  to: term()
}

One difference. from is :absent on an :added change and to is :absent on a :removed one; tier is the tier the slot has in whichever pane still holds it.

kind()

@type kind() :: :added | :changed | :removed

How one slot differs: it appeared, it holds a different value, or it is gone.

Functions

absent()

@spec absent() :: :absent

The atom standing in for a slot that does not exist on one side.

Exposed so a renderer or a host can pattern match on it without hardcoding the atom, and so the choice is documented in exactly one place.

Examples

iex> StatifierUI.DatamodelExplorer.Diff.absent()
:absent

between(earlier, later)

The differences between earlier and later, oldest pane first.

Values compare with ===, not ==: 1 and 1.0 are a change in a datamodel, and a diff that called them equal would hide an assignment that happened.

Examples

iex> alias StatifierUI.DatamodelExplorer
iex> {:ok, earlier} = DatamodelExplorer.build_live([])
iex> {:ok, later} = DatamodelExplorer.build_live([])
iex> StatifierUI.DatamodelExplorer.Diff.between(earlier, later)
[]