Render a validated Spec to Html, driven by host-owned
state, with actions flowing back out as Effect values.
This is a small TEA component. The host owns the json-render state and passes it to
view on every frame; the renderer reads it but never mutates it behind the
host's back. The only local state the renderer owns is the pending confirm dialog. User
actions (button presses, checkbox toggles) surface to the host as Effects the host
applies, so the host stays the single source of truth.
Because the output is Elm Html (no innerHTML, no script escape hatch), the rendered
tree is XSS-safe by construction.
Advance renderer-local state in response to a Msg, optionally yielding an Effect
for the host to apply.
confirm is honored here: a press carrying a confirm opens the dialog and emits nothing;
the EmitAction only fires once the user accepts.
What the renderer asks the host to do. The renderer never performs the side effect itself.
EmitAction { verb, params }: a wired on:press fired (and any confirm was
accepted). params are already expression-resolved. The host re-checks the verb
against its allowlist and performs the side effect.EmitStateChange { path, value }: a two-way input ($bindState / $bindItem
checkbox) was toggled. The host treats this as the source of truth, writes it at the
given absolute JSON Pointer, and re-projects state.What the host tells the renderer about itself.
allowedIframeOrigins — the iframe origin allowlist: an Iframe element renders only when
its resolved src is an https URL whose origin is an exact member of this list. An empty
list disables all iframes (fail-closed).hostName — what the iframe provenance bar calls you, as in "not verified by <hostName>".
The bar is trust chrome, so the name has to be the one a reader recognizes; a generic
"the host application" is a weaker claim than naming the app they are looking at. A manifest
cannot reach this, which is the point.countPills — the vocabulary a CountPills element falls back to.The words a CountPills element uses when its manifest does not name its own.
The renderer counts rows and groups them; it has no opinion about what a row is. A host does:
it knows what one row is called, which field groups them, and which group belongs at the front. A
manifest published before CountPills could carry those keys cannot say so itself, so the host
that mounts the renderer supplies them and its already-published manifests keep reading correctly.
groupBy — the record field to group rows on.groupOrder — group values in display order. Anything outside the list sorts after the
listed ones by descending count then name, which is what an empty list does to everything.
Matching is case-insensitive, and a value listed more than once takes its FIRST position.itemNoun / itemNounPlural — what one row and many rows are called.Options for a host that has no vocabulary of its own: no iframes, an unnamed host, no group order (so counts fall back to descending), and rows that are simply "items".
Render the spec against the current host-owned state. The returned Html Msg
includes the confirm dialog overlay when one is pending.
Maps common run-state strings to a tone class; unrecognized values fall back to neutral.
Keyed on the leading whitespace-delimited token so a value carrying a trailing detail suffix
(e.g. "running · 0:15", a counting-up elapsed) still maps to its state tone.
The arms are that closed run-state vocabulary and nothing else. A publisher whose own vocabulary
has a word this table does not know gets the neutral fall-through, and supplies a variant if it
wants a specific tone — which is the mechanism that exists for exactly this, and is why no
extension's private verb needs an arm of its own here.
Renderer-local UI state. Holds only the pending confirm dialog (if any); the host owns everything else.