One config field, dispatching on the closed field-type set (ADR-0005 decision 9, ADR-0002 decision 7).
The set is closed precisely so this renderer can be total, and the mapping is the record's, unchanged:
| Field type | Rendering |
|---|---|
:string | single-line text input |
:integer | number input, step 1 |
:boolean | checkbox |
{:select, choices} | select, choices in declared order |
:expression | single-line source input |
:duration | one text control; predicator duration strings primary, with on-screen examples |
{:list, t} | repeatable rows of t's renderer, with add and remove |
:duration's row is decision 9 as amended 2026-08-29. One text control,
not a value/unit pair and not a pair with an escape hatch beside it: the
compound control could not spell PT1H30M at all, and a control plus an
escape hatch is two ways to say one thing with a rule about which wins.
Predicator duration strings are primary with the examples on screen,
ISO-8601 stays accepted, and an empty field omits the key.
What the typed text means is StatifierBlocks.DurationInput's, not this
module's - it is a function of the text alone, so it is asserted with
LiveView absent. Where an omitted key is omitted is
StatifierBlocks.Editor.ConfigForm's, which owns where a decoded value
is written. This module renders the control and shows the refusal.
The refusal shown beneath a :duration is the inline check, and it
is earlier than decision 9's gate rather than a second one: the gate
still decides what reaches the document, and the inline sentence tells
the author which spelling they are failing while they are still typing
it. Nothing here is stored - the stored form is the author's string
verbatim, byte for byte.
A required field is marked with the word, not with an asterisk
(parity item 1.9). An asterisk is a convention that has to be learned
from a legend the editor does not have, it is read aloud as "star", and
it is one character wide next to a label it is easy to miss. Required
says the same thing to a reader and to a screen reader, and it is the
field record's own required? that decides - never a key name and never
a type.
Two control types carry a placeholder, and neither is chosen by key
or by type name: an :expression says what kind of thing belongs in it,
and a :duration shows the spelling it stores. A bare :string says
nothing, because there is nothing a type as wide as "string" can suggest -
which is the rule, and it is what makes the two that do say something the
editor's own presentation rather than a special case.
Whether a block type may declare a placeholder on its own fields is a
question this module deliberately does not answer: ADR-0002 decision 7
closes the field TYPE set, not the keys of a field record, so admitting
one is a widening of that record and belongs to whoever amends it.
:expression renders as a plain source input here. Predicator source is
statifier-ui's subject (sui-bob, sui-ADR-0006), and decision 9 records a
richer affordance as a deferral, so this component accepts an
expression_component override for exactly that seam.
This module is a renderer, not a gate. Nothing here decides whether a
value is acceptable: validate_config/1 does, through
StatifierBlocks.Edit.check_config/3, which is why an unparseable
integer reaches the draft config as the string the author typed rather
than being silently coerced or dropped.
Summary
Functions
Decodes one field's slice of a form's params back into a config value, dispatching on the same closed type set the renderer does.
One field: its label, its control, and its own findings (decision 11).
The DOM id for a field's control. Part of decision 7's DOM contract.
The form param name a field's control posts under.
Functions
@spec decode(StatifierBlocks.BlockType.field_type(), term()) :: StatifierBlocks.Block.json()
Decodes one field's slice of a form's params back into a config value, dispatching on the same closed type set the renderer does.
Total, and deliberately non-coercing at the edges: an integer field
whose input does not parse yields the string the author typed, so
validate_config/1 reports it and decision 9's gate keeps it out of
the document. Coercing to zero here would silently discard the author's
intent and commit a value they never asked for.
One field: its label, its control, and its own findings (decision 11).
Attributes
field(StatifierBlocks.ViewModel.Field) (required)target(:any) (required)class(:string) - Defaults tonil.expression_component(:any) - Override for:expression, per ADR-0005 decision 9's seam. Receives the same assigns. Defaults tonil.
@spec input_id(StatifierBlocks.ViewModel.Field.t()) :: String.t()
The DOM id for a field's control. Part of decision 7's DOM contract.
@spec input_name(StatifierBlocks.ViewModel.Field.t()) :: String.t()
The form param name a field's control posts under.