# `StatifierBlocks.Editor.Inspector`
[🔗](https://github.com/riddler/statifier_blocks/blob/v0.44.1/lib/statifier_blocks/editor/inspector.ex#L2)

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 an earlier polish
    pass drew.

## The Findings tab with nothing selected

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
(the operator's ruling of 2026-08-29). 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

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. With a block selected, a third section, the **Note**, opens above
them; "The note" below says why it is there.

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
Note is the one section that does appear and disappear, and it does so
on purpose: it holds the selected block's own words rather than saying
what the pane is about, so with nothing selected it has nothing to hold,
and the two sections under it are still the ones always drawn.

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**
(an operator ruling). 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 note (ADR-0005's Amendment of 2026-09-29, 2s)

With a block selected, the Config tab opens on the block's **Note**: a
textarea holding the author-written note, above the Block and
Configuration sections. It is on this tab because this tab is where a
block is edited, and it is not a fifth tab for 3A's reason: a note is
about the selected block. It is its own form posting its own event, so
a change is one `{:update_note, id, note}` and never passes through the
config form's decode. Emptying it removes the note. A read-only mount
draws the note as text, and draws nothing when there is no note.

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.

# `inspector`

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

* `tab` (`:any`) (required)
* `tabs` (`:any`) - The tabs this strip draws, out of `StatifierBlocks.Shell.inspector_tabs/0`
  and in that order, or `nil` for all four. It is a profile's
  `inspector_tabs` list after the shell has intersected it (ADR-0005's
  2026-09-07 profile amendment), so an id the package does not know has
  already been dropped by the time it arrives.

  Defaults to `nil`.
* `read_only` (`:boolean`) - Whether this mount edits. A read-only inspector draws each config field
  as its value rather than as a control (`read_only?` clause 3), and every
  other panel it draws - findings, condition, fixtures - is unchanged,
  because all three are readings already.

  Defaults to `false`.
* `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`.
* `note` (`:any`) - the selected block's note, read off the document (`""` for none), 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 `%{}`.
* `type_candidates` (`:list`) - Passed through to `StatifierBlocks.Editor.Field`; see its moduledoc. Defaults to `[]`.
* `path_types` (`: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 `[]`.
* `field_candidates` (`:map`) - Passed through to `StatifierBlocks.Editor.ConfigForm`; see its moduledoc. Defaults to `%{}`.
* `capture_pairs` (`:any`) - Passed through to `StatifierBlocks.Editor.ConfigForm`; see its moduledoc. Defaults to `nil`.
* `capture_sources` (`:list`) - Passed through to `StatifierBlocks.Editor.ConfigForm`; see its moduledoc. Defaults to `[]`.
* `fixtures` (`:any`) - Passed through to `StatifierBlocks.Editor.ConfigForm`; see its moduledoc. Defaults to `nil`.
* `field_focus` (`:any`) - Passed through to `StatifierBlocks.Editor.ConfigForm`; see its moduledoc. Defaults to `nil`.
* `debounce` (`: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`.

---

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