# `StatifierBlocks.ViewModel.Node`
[🔗](https://github.com/riddler/statifier_blocks/blob/v0.44.1/lib/statifier_blocks/view_model.ex#L319)

One block, rendered: its resolved status, its presentation metadata,
its slots (recursive), its form, and its own findings.

`outcome` is the outcome this block produces for the slot it sits in,
when the **parent's** type declared a `slot_outcome_key` for that slot
and this block's config holds a well-formed outcome name there; `nil`
otherwise, which is every block whose parent declared nothing. It is
resolved here rather than by a consumer so that the picture-drawing
side reads one field instead of a metadata key plus a config lookup,
and so it never learns that `core.on_event` is the type with an
`outcome`.

`join_label` is what the join marker under a side-by-side arrangement
reads: the string this block type's `join_label` callback returned for
this block's config, normalized by `StatifierBlocks.BlockType.join_label/2`,
and `nil` when the type declared none or the callback answered with
something the refusal set rejects (ADR-0002 amendment B). It is resolved
here for the same reason `outcome` is - the rendering side reads a string
and never learns that `core.parallel` is the type with a completion rule.

It is derived for **every** node, including the ones whose slots stack
and never draw a marker: a field that exists only sometimes is a field
every consumer has to remember to guard, and `entry.layout` already says
whether the marker is drawn. An unresolvable block reaches `nil` by the
ordinary route rather than by a special case - its entry is the
placeholder's, which declares no callback.

`title` is the author's own name for THIS block - the value of a
declared `:string` field keyed `label` - and `nil` when the type
declares no such field or the config holds nothing usable there, which
is every block in the `core.*` vocabulary. `nil` rather than "the
entry's label repeated" so that the card can tell the two apart: a
block with a name of its own reads as its name over its type, and a
block without one reads as its type alone rather than as its type
printed twice (`ViewModel.title/1` and `ViewModel.subtitle/1` are that
pair). It is derived here for the same reason `outcome` is - the
rendering side reads a string and never learns which key an author's
name lives under.

`sentence` is this block as one line of prose (ADR-0005's 2026-09-07
amendment): the string this block type's `sentence/1` callback
returned for this config where it declares one and the return is
usable, else the author's own `title` where they gave one, else the
type's label falling back to the type name. It is resolved by
`build/3` like every field beside it rather than being a function a
consumer calls later, so a list view, an outline pane and a test read
one field.

It is a fourth thing a node carries, not a re-spelling of `title`:
`ViewModel.title/1` keeps both its clauses and every consumer of it
reads what it read before. It is **not** a chip - it is uncapped,
appears in no chip list, and a card draws what it drew yesterday.

An unresolvable block has no callback to ask and no label, so it lands
on the type name as the document stores it: a list view draws a line
for every block in the document and never a blank one.

`summary_titles` is the raw text behind each chip in `summary`, `nil`
where the chip is drawn as its type declared it (ADR-0005 decision
10w). It is index-aligned with `summary` by construction rather than
by maintenance - `StatifierBlocks.BlockType` derives both from one
pass over one callback - and it is read through
`ViewModel.summary_chip_titles/1`, which realigns it against
`summary_chips/1` for a node built by hand.

`invoke_type` is what this block's config carries under `invoke_type`,
when that is a non-empty string. It is a config key rather than a type
name, so a host type that calls out to a handler gets the same third
line `core.invoke` does by carrying the same key, and no component
learns that `core.invoke` exists. Whether the value is WELL FORMED is
`validate_config/1`'s question and its answer arrives as a finding on
the same card; hiding the string while a finding complains about it
would be the card disagreeing with the form.

# `status`

```elixir
@type status() :: :ok | {:unresolvable, term()}
```

# `t`

```elixir
@type t() :: %StatifierBlocks.ViewModel.Node{
  block_id: StatifierBlocks.Block.id(),
  entry: StatifierBlocks.BlockType.palette_entry(),
  findings: [StatifierBlocks.Finding.t()],
  findings_count: non_neg_integer(),
  form: StatifierBlocks.ViewModel.Form.t() | nil,
  invoke_type: String.t() | nil,
  join_label: String.t() | nil,
  outcome: String.t() | nil,
  raw_config_json: String.t() | nil,
  sentence: String.t() | nil,
  slots: [StatifierBlocks.ViewModel.Slot.t()],
  status: status(),
  summary: [String.t()],
  summary_titles: [String.t() | nil],
  title: String.t() | nil,
  type: StatifierBlocks.Block.type_name(),
  type_version: pos_integer()
}
```

---

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