StatifierBlocks.Editor.Inspector (StatifierBlocks v0.19.0)

Copy Markdown View Source

The tabbed inspector: Config, Findings, Condition, Fixtures (ADR-0005, the 2026-08-29 shell amendment, ruling 3A, and its 2026-09-05 amendment).

3A is one rule and a list, and the rule is the part that matters: the inspector is about the selected block, and anything about the document goes to the drawer. The list of tabs follows from it rather than the other way round, which is why the Datamodel - an inspector tab in the spike - is a drawer tab here. It was never about the selected block.

One thing a reader will look for and not find:

  • There is no tab here that is about the document. A pane that is about the document has one place to go, and 3A exists so that "which inspector tab does this become" stops being asked. The Fixtures tab below is admitted by that rule, not in spite of it: a fixture row is attached to exactly one block.
  • The Findings tab is not the document-level findings panel decision 13 names. That panel still exists, still lists every finding in the document, and still lives beside the canvas. This tab is the selected block's own findings, which is the distinction the campaign-014 polish pass filed as sb-3l1 item a.

The Findings tab with nothing selected (sb-dbqq)

3A's rule decides what a tab is about; it does not say what a tab does when its subject is missing. With no selection the Findings tab has no block to be about, and what it did was render the same "select a block" line the Condition tab does - a chip-less tab beside a document with four things wrong with it.

So with node: nil the tab reads the document: the chip carries StatifierBlocks.Shell.findings_count/1 over the findings the editor passes in - the one number this package means by "the document's findings", the same one the drawer's strip and StatifierBlocks.Editor.findings_count/3 report - and the panel lists them grouped by block, each row selecting its block through the same select event the canvas and the drawer's list push.

This is the tab's empty state and not a fourth surface. The moment anything is selected the tab is that block's findings again, unchanged, and the document-level list an author navigates is still the drawer's (R4). What the inspector adds is an answer where there was a dead end: the count is not recomputed here, the list is not filtered here, and selecting a row hands the author back to the pane's real subject.

The Condition tab reads; it does not evaluate

It renders the per-arm predicator source the block carries, one entry per :expression field, and it evaluates nothing. Evaluation is StatifierBlocks.Predicates, it is server-side, and what it produces - a truth table - is tabular and document-level, so 1A puts it in the drawer. The tab that shows an author what a branch asks and the drawer that shows what it answers are different surfaces on purpose.

Editing a condition stays on the Config tab, where every other field is edited, so there is exactly one form in the editor and one place a draft can live (decision 9).

The Fixtures tab is the selected block's rows (sb-0l36)

ADR-0005's 2026-09-05 amendment, "3A admits a Fixtures tab in the inspector". A fixture row attaches to one block - the fixtures assign is %{block_id => [TruthTable.t()]}, with no document-level bucket and no row belonging to two blocks - so the selected block's rows are about the selected block, which is the whole of what 3A asks of an inspector tab.

The drawer's own Fixtures tab is unchanged in every particular. The two coexist by pane, exactly as Findings already does: the drawer's tab is every row in the document with the block named in a column, and this one is one block's rows with the block named by the pane. Nothing here filters a different list or derives a second number - both read the runs the editor already holds.

It adds no execution path. The rows come from the same StatifierBlocks.Runtime.FixtureRuns result the drawer's tab renders, which the editor recomputes in refresh_fixture_runs/1; this tab selects the runs whose block_id is the pane's subject and renders them. What that costs is one comparison per run, on a struct the editor computed for the drawer's sake anyway.

Its count is the selected block's row count, in the Findings tab's chip and style. It counts rows and not failures for the reason the drawer's strip does: the number beside a tab says how much is in it, and a count that vanished when everything passed would read as "no fixtures" on the document an author most wants to see the number for.

Its empty states are three, and they are different questions: no selection (the pane has no subject), no fixtures source at all, and a source that holds nothing for this block. The mid-edit compile failure is a fourth, and it reads as mid-edit rather than as a failure of the fixtures - the same words the drawer's panel uses, for the same reason.

The pane header, and what its status says (parity item 1.1)

Above the tabs sits the pane's own header row: the name of the pane, and a right-aligned status. The status is the inspector's subject stated in words - the selected block's type label, or no selection - and it is there because 3A's rule ("the inspector is about the selected block") is invisible in an empty pane otherwise: three tabs with nothing under them do not say whether the author has selected nothing or selected something that has nothing to show.

It reads the type's label, not the block's id. A label is what the author picked the block by in the palette and what its card reads on the canvas, so the header names the block in the vocabulary the rest of the editor already uses. An unresolvable block reaches the placeholder entry by the ordinary route and falls back to its raw type name, which is the only thing about it that is still known.

The inspector folds too (ADR-0005, the 2026-08-30 shell amendment)

This pane used to say it had no collapse - that the palette's fold was the wide arrangement's answer to a cramped canvas, that one pane was enough of an answer, and that a second one would be a separate ruling. It is that separate ruling that has now been made: the 2026-08-30 amendment to the shell arrangement grants the inspector its own fold, and the paragraph above is superseded rather than merely out of date.

What it is, is the palette's fold on the other side of the canvas: collapsed in, "inspector-collapse" out, data-collapsed on the pane and data-inspector on the layout, and the width given back by one stylesheet rule. No hook, no client state, no fourth tab, and nothing about 3A changes - a folded pane is still about the selected block, it is just not on screen. The chevron points the way the press will MOVE the pane, which on this side is the mirror of the palette's.

The Config tab's two sections (parity item 1.9)

Under the tabs the Config tab is two labelled sections rather than a form dropped into a pane. Block states what the pane's subject IS - its type, its id, and the slot it sits in - and Configuration holds the form.

The Block section is the one that has to be there when nothing is selected, which is why its three rows render either way and read as a dash when they have no value. A section that appears and disappears with the selection teaches an author nothing about what the pane will show them; three rows that are always in the same place, sometimes empty, say what the inspector is about before they have selected anything.

The three rows are deliberately the three an author can act on. Type is the type's label, the same string the header status reads, so the pane names the block the way the palette and the canvas do. Id is the block id verbatim and set in mono, because it is the string that appears in a finding, in a provenance map and in a URL, and an author reading one of those needs to match it character for character. Slot is the slot's label - StatifierBlocks.Shell.slot_label/2's answer - because a card seen on its own does not say which arm of a branch it is in.

Under the three rows, and only for a block whose type did not resolve, the Block section holds that block's stored config as canonical JSON (campaign-017 ruling D4). It used to sit on the card, where it made the one broken block the widest thing in its lane; it is the same bytes in the same order, moved to the surface an author reaches by asking about that block in particular. It is read-only here for the reason the Config tab is empty there: nothing declares which of those values are editable, and a form over them would be invented rather than derived.

It is the only thing in this pane that wraps mid-token. A block id ellipsises and a condition source scrolls, because both are strings an author matches character by character and neither is long by design; a stored config carries whatever the host that wrote it carried, so the only two honest choices are wrapping anywhere and clipping bytes the author is here to read.

The Configuration section's empty state is a box, not the one-line sentence the other tabs use. It is the only empty state in the editor standing where a control would be, and an unboxed sentence in that position reads as a caption for the form below it rather than as the reason there is no form.

Summary

Functions

The four tabs and the panel of whichever one is showing.

Functions

inspector(assigns)

The four tabs and the panel of whichever one is showing.

Attributes

  • tab (:atom) (required)
  • collapsed (:boolean) - folded to a rail - the 2026-08-30 shell amendment's inspector fold. Defaults to false.
  • node (:any) - the selected ViewModel.Node, or nil. Defaults to nil.
  • slot_label (:any) - Shell.slot_label/2 for the selection - the slot it sits in, or nil. Defaults to nil.
  • pending (:list) - Defaults to [].
  • expression_component (:any) - Defaults to nil.
  • invoke_types (:list) - Passed through to StatifierBlocks.Editor.Field; see its moduledoc. Defaults to [].
  • path_candidates (:list) - Passed through to StatifierBlocks.Editor.Field; see its moduledoc. Defaults to [].
  • value_candidates (:map) - Passed through to StatifierBlocks.Editor.Field; see its moduledoc. Defaults to %{}.
  • event_candidates (:list) - Passed through to StatifierBlocks.Editor.Field; see its moduledoc. Defaults to [].
  • outcome_candidates (:list) - Passed through to StatifierBlocks.Editor.Field; see its moduledoc. Defaults to [].
  • fixtures (:any) - Passed through to StatifierBlocks.Editor.ConfigForm; see its moduledoc. Defaults to nil.
  • fixture_runs (:any) - StatifierBlocks.Runtime.FixtureRuns.t() for the Fixtures tab, or nil - the same struct the drawer's Fixtures tab reads, filtered here to the selected block's runs. Defaults to nil.
  • target (:any) (required)
  • class (:string) - Defaults to nil.
  • document_findings (:list) - ViewModel.findings - what the Findings tab reads with no selection. Defaults to [].
  • orphan_findings (:list) - ViewModel.orphan_findings - the subset with no block to select. Defaults to [].
  • root (:any) - the view model's root ViewModel.Node, read only for group labels. Defaults to nil.