open-standards/joule-ui/SPEC.md
Transaction Science 84768074ca
Some checks failed
energy meters on real hardware / RAPL and NVML on jetson-hub (push) Has been cancelled
energy meters on real hardware / sink decode table · RAPL on jetson-hub (NVML surveyed) (push) Has been cancelled
energy meters on real hardware / sink decode table · per-task energy and the GPU rail on macOS (push) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Has been cancelled
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (push) Has been cancelled
joule-code conformance gate / joule-moq-relay, its own energy on a counter (macos) (push) Successful in 2m15s
wai-app and Create / wai-app · does this change touch it (push) Successful in 1m45s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Has been cancelled
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Has been cancelled
joule-code conformance gate / Build + round-trip C1-C4 vectors (push) Successful in 5m43s
joule-code conformance gate / joule-moq-ingest, every target (push) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (push) Has been cancelled
Every text that states the IOReport liveness rule states it as the meters apply it
The meters count an IOReport interval only within three of the probe's ticks, and
the texts that state the rule said nothing of it, so an implementation following
any of them would call a lump-refreshed rail live once load stretched its intervals.
Each now says what an interval is (from one sample in hand to the next), that only
one no longer than three of the probe's ticks (75 ms at its 25 ms tick) counts, that
a longer one counts as nothing (not among the intervals, not as an advance, not
towards the 300 ms), and that the 300 ms is the summed length of the intervals that
count.

The normative clauses in arl/ARL-S.md, jcp/SPEC.md, joule-code/spec/receipt.md,
joule-ui/SPEC.md, jouleclaw/SPEC.md, JCR-1-spec.md, map/spec/receipt.md,
mesh/SPEC.md (a block quote), proof/SPEC.md and sense/SPEC.md; proof-schema's
quotation of proof/SPEC.md's; jouleclaw-energy's conformance README; and the proof
and sandbox READMEs, which say the cap is IOReport's alone. Map's and Joule Code's
unreleased changelog entries, which record the rule as it was added, are amended,
since neither release has gone out. No wire or conformance-vector change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 08:20:02 -05:00

593 lines
35 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# joule-ui — Specification
| | |
|---|---|
| **Standard** | joule-ui |
| **Status** | Draft for Review |
| **Version** | 0.3.0 |
| **Licence** | Specification text CC-BY-4.0 ([`LICENSE-SPEC`](LICENSE-SPEC)); reference code Apache-2.0 ([`LICENSE`](LICENSE)) |
| **Reference** | [`joule-ui-rs/`](joule-ui-rs/) |
## §1 What joule-ui is
joule-ui is a typed protocol for **the moment a computation has to become
something a human sees and acts on** — and for making that moment *accountable*.
It has two layers, and the second is the one no other UI protocol carries:
1. **The surface (§2–§4).** A host publishes a **registry** of widgets it can
render; an agent or program emits a **typed widget tree** against it. The
host renders. No HTML over the wire, no code emission, no agent holding the
renderer. This is the shape the field converged on (A2UI, MCP Apps SEP-1865,
CopilotKit's declarative middle); joule-ui pins it as a protocol.
2. **The interaction (§5–§7).** When a human fires an action on that surface,
the invocation is an **accountable event**: authorized against a capability,
metered in joules, and sealed into a tamper-evident receipt. A joule-ui
session is therefore not a stream of opaque callbacks — it is a hash-chained
ledger of *what the user did, what it computed, and what it cost*, verifiable
by anyone holding the host's public key.
The surface layer makes joule-ui a peer of the declarative-UI protocols. The
interaction layer makes it the only one you can hand to an auditor.
## §2 The widget tree
A **Widget** is `(name, props, children)`, recursive. `name` selects a widget
in the host's registry; `props` is a map of typed values; `children` is an
ordered list of Widgets. The wire form is plain JSON.
A **PropValue** is a closed set: `Null`, `Bool`, `Number`, `Text`, `List`,
`Object`, and **`Action`** — an opaque action id (plus optional pure-data
payload) that the host binds to a handler at render time. There are no
closures, no expressions, and no code on the wire. An interaction (§5) is the
firing of an `Action`.
## §3 The registry
A **Registry** is the host's published catalog: for each widget name, a
**WidgetSchema** declaring its prop slots (name → type, required/optional), the
children it accepts (`None` / `Any` / a named allow-list), and whether
unknown props are permitted. The registry is the host's contract; the renderer
is the host's product surface and is out of scope for this standard.
A schema MAY declare its **positional order** (`positional`): the order in which
a language that passes arguments by position, such as the line language of §11,
maps a call's arguments to the widget's props, the name `children` standing for
the child list. A schema that declares none has, as its order, its props in key
order followed by `children` when it accepts children. The order is absent from
the wire form when none is declared.
A registry is published in two forms, each written from the registry alone. A
**catalogue** is a JSON Schema (2020-12) of the nodes the A2UI ingest takes: each
widget a definition under `$defs`, `component` its name, `props` its slots and
`children` what it accepts, and the root, when the host names one, the schema's
top-level reference. Whatever a catalogue admits, the ingest accepts. A registry
declaring what no node can state (a prop whose key is not in snake case, an action
in a prop not named `on_<word>`, another type in a prop that is) has no catalogue,
and the writer says why. A **library** is the file of §11.9: each widget's schema,
its positional order written out, and the root.
## §4 Validation
`validate(widget, registry)` checks a tree against the registry and returns
**every** error, not just the first: unknown widget, unknown/missing/mistyped
prop, disallowed child. A conforming validator MUST be exhaustive and MUST be
pure (no IO). A surface that validates is renderable by any conforming host of
that registry; validation is the well-formedness attestation the surface layer
provides.
## §5 Energy and provenance
Every interaction carries a two-part energy figure in integer **microjoules**:
`measured_uj` (present only for a hardware reading) and `estimated_uj` (always
present), tagged with a **Provenance** — `HwShunt` (a hardware energy reading),
`ModelBased` (a calibrated apportionment), or `Estimator` (a coarse estimate).
This is the family's shared energy model; a figure is reported with the honesty
of how it was obtained, never promoted.
Measurement itself is a **recognized edge**, not part of this standard: a
`Meter` is any source of a cumulative microjoule counter, and it declares the
provenance of its readings. A meter's figures are `Estimator` unless the meter
declares otherwise: a meter opts in to `ModelBased` for a calibrated model, and
to `HwShunt` only for readings of a live hardware counter (below). A meter
MUST NOT declare `HwShunt` for figures that are not such readings — a
deterministic stub, a model, a replayed counter. (The conformance pack's
fixture meter, §8, is the one exception: it declares `HwShunt` only so the
vectors exercise the reading path of the signed core, and like the pack's
published steward key it attests nothing.) The sibling
**Sandbox** standard's `PowerMeter` is the reference meter: it reads vendor
energy counters (RAPL, NVML, Apple IOReport, and for work on its own process
the per-process CPU energy the OS attributes to it) while they are live,
apportions a reading to a process tree by CPU time (`ModelBased`), and falls
back to a wall-clock estimate, labelled `Estimator`, where no counter is
live. joule-ui defines what an interaction records; the host wires the meter
it trusts.
**Vendor energy counters.** A reading from a vendor energy counter (RAPL,
NVML, Apple IOReport, ...) MAY be graded as a hardware reading only while the
counter is demonstrably live for the domain the figure claims: the
implementation MUST have observed the counter covering that domain advance
under load (for Apple IOReport: the CPU energy rail itself advancing in at
least 5 sample intervals, in at least 4 of every 5, over at least 300 ms while
the probing thread burns CPU; an interval runs from when one sample is in hand
to when the next is, and only one no longer than three of the probe's ticks,
75 ms at its 25 ms tick, counts: a longer one counts as nothing, neither among
the intervals, nor as an advance, nor towards the 300 ms, which is the summed
length of the intervals that count). A counter that is present but not live — frozen,
or republished in lumps — MUST NOT produce a hardware-graded figure; the
implementation falls back to its estimator or reports the work unmetered, and
SHOULD record why. A figure MUST NOT claim coverage of a domain its counter
does not read; where the format has a coverage field, such a figure is
`Partial`. joule-ui's energy record has no coverage field, so a figure from a
counter that does not read the domain the work ran on — a GPU counter alone,
for a handler that ran on the CPU — MUST NOT be graded `HwShunt` for the
interaction: the reference adapters seal such a span as the meter's
`Estimator` figure for its wall time.
## §6 The interaction lifecycle
`run_interaction` is the protocol in one operation, in three phases:
1. **Authorize before running.** The action is checked against a capability
*before* its handler executes. A denied action's handler **MUST NOT run**.
The capability check is a recognized edge: an **Authorizer** is any
two-phase `(authorize, charge)` gate, and the sibling **JCP** standard's
runtime — gating on a signed, joule-budgeted grant — is the reference one.
2. **Meter the handler.** The handler runs once; the meter is sampled before
and after; the difference is the interaction's energy, graded with the
meter's declared provenance (§5). When the meter gives no reading — either
sample is absent — the handler's wall-clock duration is charged at the
meter's declared estimator power (20 W unless the meter declares its own),
rounded up to whole microjoules, and the figure is `Estimator`. A handler
that ran MUST NOT be recorded at 0 µJ for want of a reading: work that ran
is not free, and a figure of zero would let a budget never bind on a host
without a counter. A denied action's handler never ran, and its record
carries 0 µJ `Estimator`.
3. **Charge the cost, with its provenance.** The interaction's figure — a
reading or an estimate — is debited from the capability's remaining budget,
and the authorizer is handed the figure's provenance with it. So the budget
binds on every host, settled after the fact against the energy the work
cost as nearly as the host can know it; and an authorizer that keeps its own
record of the debit MUST record it with that provenance, never an estimate
as a reading.
A host whose handler cannot run inside one call (a browser's handler awaits the
network) runs the same three phases in two steps: `begin_interaction` performs
phase 1 and samples the meter, and gives a **Pending** only when the action is
allowed; `Pending::finish` takes the handler's output and its wall-clock time on
the host's own clock, and performs phases 2 and 3. A denied action gives no
Pending, so no handler can run under it, and the denial is the Interaction phase
1 describes.
The result is an **Interaction**: the surface's content address (what the user
was looking at), the action id, the content addresses of the inputs and the
outputs (the genesis sentinel when denied), the verdict, the energy, the budget
left, and the grant id. Inputs and outputs are content-addressed, not carried,
so the receipt commits to *what was supplied and produced* without embedding it.
## §7 The sealed, hash-chained interaction log
An **InteractionLog** seals each interaction as the next link of a chain. The
**canonical core** (§7.1) is signed with the host's Ed25519 key; each link's
`prev` is the content address of the previous link's core, and `seq` increments
from zero. **Allows and denies are both chained** — a denial excised from the
record is exactly the audit a malicious host would drop, so excising,
reordering, or splicing any interaction MUST break the chain at that point.
`verify_chain` checks every signature, the sequence, and every `prev` link, and
reports the first break.
### §7.1 The canonical core
The signed bytes are a fixed-order, length-prefixed concatenation: the profile
tag `joule-ui/interaction/2`, then surface, action, **actor**, inputs, outputs,
verdict (and deny reason, if any), provenance, `estimated_uj`, `measured_uj` (if
present), `budget_remaining_uj` (if present), `grant_id` (if present), `seq`,
and `prev`. Each field is preceded by its little-endian `u64` byte length.
The **actor** is the principal that fired the interaction — the `did:key:z…` of
a human or an agent (the standard makes no distinction; both are traced the
same). It is **mandatory**: every sealed interaction names who fired it, and
that name is bound into the signed core, so an interaction cannot be attributed
to a different principal without breaking the chain. A host with no per-client
authentication attributes fires to its own session principal (by convention its
own `did:key`). The profile tag is `…/interaction/2` precisely because the actor
is now part of the preimage — a `…/1` core (no actor) and a `…/2` core are
distinct signed bytes and never collide.
Deterministic and dependency-light; the BLAKE3 of this core is the link's
content address. (A host MAY additionally express a sealed interaction as a
JCR-1 / in-toto+DSSE envelope for cross-family verification — a recognized
edge, not required.)
## §8 Conformance
- **C1 — surface validation.** Given a registry and a widget tree, reproduce
the exhaustive error list (empty for a valid tree).
- **C2 — interaction metering.** Given a surface, action, actor, inputs, a stub
meter, and a handler, reproduce the Interaction: verdict, energy, content
addresses, and the sealed actor. The stub meter declares `HwShunt` as a
fixture grade, so the recorded energy carries `measured_uj` (§5).
- **C3 — authorization.** A denied action produces a deny Interaction whose
outputs are the genesis sentinel and whose handler did not run; the actor is
still named on the denial.
- **C4 — chain integrity.** Seal a sequence; `verify_chain` accepts it and
rejects any excision, reorder, or field mutation at the first broken link.
- **L1–L5 — the line language (§11).** Statements, ingest, streaming, the
canonical form and the refusals, from the pack in `conformance/lang/v1/`
(§11.9).
## §9 Recognized edges
joule-ui is self-contained at its core and composes outward at named edges:
the **Sandbox** standard supplies the reference `Meter` (a hardware reading
while a vendor counter is live, its labelled estimate otherwise); **JCP**
supplies the budgeted-grant `Authorizer`; **JouleContract** / in-toto+DSSE
supply an optional cross-family receipt envelope; and any transport (MCP, REST,
SSE, gRPC, Web MoQ) carries the validated surface and the interaction stream.
None are required to use the protocol; each is the reference answer where a
host needs that edge.
## §10 Non-goals
A renderer (the host's), a transport (the consumer's), a component library
(product surface), and code emission (an agent emits a typed tree against the
registry, never a renderer). joule-ui pins the protocol and the receipt; the
fast-moving surfaces stay the consumer's.
## §11 The line language
A host MAY take in user interfaces written in the line-per-component language of
the open-source OpenUI project, OpenUI Lang 1.0 (MIT licence; [`NOTICE`](NOTICE)): a
program of statements `name = Component(arg, ...)`, one a line, a name usable before
the line that defines it. This section is how such a program becomes a joule-ui
widget tree (§2) against a registry (§3), checked by the validator of §4, while the
program is still arriving. The reference implementation is
[`joule-ui-rs/crates/joule-ui-lang`](joule-ui-rs/crates/joule-ui-lang/).
The language's grammar is parsed whole. What joule-ui does not carry (state,
queries and mutations, operators, conditionals, member access, built-in functions,
sequences of actions) is refused with the code `not-declarative` (§11.8): joule-ui
puts no expression and no behaviour on the wire (§2), and the smallest piece that
holds one is dropped.
A **library** is a registry and, optionally, the name of its **root widget**. Every
widget name of a library starts with an uppercase ASCII letter and holds only ASCII
letters, digits and `_`; none is `Query`, `Mutation` or `Action`; every name of a
widget's positional order (§3) is one of its props, or `children` on a widget that
accepts children, and none appears twice; the root names one of its widgets.
### §11.1 Source text
The text is prepared in this order. Each line of the prepared text keeps the number
of the source line it came from (1-based, counted in the text as received), and every
line an error names (§11.8) is that source line.
1. Every carriage return immediately followed by a line feed is removed. The text is
then a sequence of lines separated by line feeds.
2. **Host marker lines.** A line whose text starts with `]]>openui:` is a marker line.
When the text holds one, the program is only the lines that follow a marker line
whose text after `]]>openui:` starts with `content`, up to the next marker line;
marker lines themselves and every other line are removed.
3. **Fences.** A line whose first three characters are backticks (`` ``` ``) outside a
fence opens a fence. When the text holds one, the program is only the lines inside
fences, joined by line feeds; fence lines and all other lines are removed. Inside a
fence a line whose first three characters are backticks closes it, unless a
double-quoted string is open at its start: each line inside the fence is scanned
for double quotes, a `"` toggling the string open or closed, and inside an open
double-quoted string a backslash makes the next character not count (a line feed
ends that effect). Single quotes do not count here. A fence still open at the end
of the text runs to the end.
4. **Comments.** Outside a string, `//` or `#` starts a comment that runs to the end of
its line; the comment is removed and the line feed kept. A string opens at `"` or
`'` and closes at the next unescaped occurrence of the same quote character, across
line feeds; inside a string a backslash takes the next character with it.
5. **Trimming.** Space, tab, line feed and carriage return are removed from both ends
of the text. Lines removed from the front leave the line numbering too.
### §11.2 Lexing
The prepared text is read left to right into tokens. A space or a tab separates
tokens; a line feed is a **newline** token. A character no rule below matches is
skipped: lexing never fails.
- **Identifiers**: `[A-Za-z_][A-Za-z0-9_]*`; `true`, `false` and `null` are keyword
literals instead. `$` followed by an identifier is a **state name** (the `$` is part
of it); `@` followed by an identifier is an **`@` name**. A `$` or `@` not followed by
an identifier start is skipped.
- **Numbers**: a digit, or a `-` followed by a digit when the previous token is not a
value (a string, a number, usable or not, an identifier, a state name, a keyword
literal, `)` or `]`; `}` is not one), starts a number: an optional `-`, digits, then a `.` followed by
digits only when a digit follows the `.`, then, if an `e` or `E` follows, an exponent:
the marker, an optional `+` or `-`, and digits. An exponent marker with no digit
after it makes the token an **unusable number**. The number's value is the decimal
value of its text, rounded to the nearest binary64.
- **Strings** open at `"` or `'` and run to the next occurrence of the same quote; a
backslash takes the next character with it (a raw line feed is part of the string).
A double-quoted string's content (between the quotes) is decoded as a JSON string
(RFC 8259 §7); if it is not a valid one (an unknown escape, a raw control character, a
`\u` escape that is short or names a lone surrogate) the content is taken exactly as
written, backslashes included. In a single-quoted string, `\n` is a line feed, `\t` a
tab, and a backslash before any other character is dropped and the character kept.
A string still open at the end of the text is closed there: if its content then ends
in an odd number of backslashes, one more backslash is added first; the token is
marked **open**.
- **Punctuation**, longest first: `==` `!=` `>=` `<=` `&&` `||`, then `=` `>` `<` `+` `-`
`*` `/` `%` `!` `?` `:` `.` `,` `(` `)` `[` `]` `{` `}`; a single `&` is `&&` and a
single `|` is `||`.
### §11.3 Statements
A statement starts at a token that is an identifier or a state name (its **name**)
followed by `=`. Newline tokens before it are skipped; a line that does not start this
way is skipped up to its next newline token, with no error. The statement's
expression is the tokens after the `=`, newline tokens inside it left out, up to the
newline token that ends it. A newline token ends it when all of these hold:
1. the bracket depth is 0: each of `(` `[` `{` adds one, and each of `)` `]` `}` takes one
away when the depth is above 0;
2. no ternary is open: at depth 0 a `?` opens one; a `:` at depth 0 moves the innermost
open ternary to its second branch when it is still in its first (otherwise the `:`
changes nothing); a value token at depth
0 (a string, a number or an unusable number, an identifier, a state name, a keyword
literal, or a closing bracket that brings the depth to 0) gives the innermost open
ternary's current branch an operand, and closes that ternary when the branch is its
second; and
3. the next token that is not a newline is not `?`.
A statement that reaches the end of the tokens ends there.
A statement is **complete** when the host has ended the stream, or when its ending
newline has arrived and a token that is not a newline follows it. Otherwise it is
**pending**: only the last statement can be. A statement that reached the end of the
tokens (rather than a newline) while its bracket depth was above 0 or its last token was
an open string is **open**, pending or not.
A statement's **kind**: `Query(...)` or `Mutation(...)` as its whole expression makes it
a query or a mutation; otherwise a state name makes it a state declaration; otherwise it
is a value statement.
### §11.4 Expressions
An open statement is first closed: a closing bracket that does not match the innermost
open bracket is dropped, and the brackets still open are then closed, innermost first.
The statement's tokens are then parsed whole by this grammar; tokens left after the
expression are an error:
```
expression = ternary
ternary = or [ "?" ternary ":" ternary ]
or = and { "||" and }
and = equality { "&&" equality }
equality = comparison { ( "==" | "!=" ) comparison }
comparison = additive { ( ">" | "<" | ">=" | "<=" ) additive }
additive = multiplicative { ( "+" | "-" ) multiplicative }
multiplicative = unary { ( "*" | "/" | "%" ) unary }
unary = { "!" | "-" } postfix
postfix = primary { "." key | "[" expression "]" }
primary = string | number | "true" | "false" | "null"
| "[" items "]" | "{" entries "}" | "(" expression ")"
| ComponentName "(" items ")" | AtName "(" items ")"
| StateName "=" expression | StateName | identifier
items = { "," } [ expression { { "," } expression } { "," } ]
entries = { "," } [ key ":" expression { { "," } key ":" expression } { "," } ]
```
A `ComponentName` is an identifier with an uppercase first letter followed by `(`; any
other identifier is a **reference**. An unusable number is an error wherever it
stands. A **key** is the next token, whatever it is: an identifier or a string gives
its text, a number its text as written, a state name its text without `$`, a keyword
literal its word, an `@` name its text with `@`; any other token is the key `?`.
Commas between items and entries are optional, and any number of them is ignored.
A complete statement that does not parse is refused `syntax` (§11.8) at the source
line of the token where parsing stopped (the last token when the tokens ran out, the
statement's own line when it has none), and binds nothing. A pending statement that does not parse is not produced, with no error.
### §11.5 Into a joule-ui tree
**Bindings.** Statements are taken in order; each produced statement binds its name,
replacing an earlier binding, except that an open pending statement does not replace a
complete one.
**Statement refusals.** A complete state declaration, query or mutation is refused
`not-declarative` at its line. A reference to one evaluates to *refused* (below), with
no further error.
**The entry** is the binding of `root`. With no `root`, it is the first value
statement (by position in the text, among the bindings) whose expression is a call of
the library's root widget.
**Evaluation** gives each expression a value: data (a joule-ui `PropValue`), null, a
list or an object of values, a widget, an action, *unresolved*, *held*, or *refused*.
Errors go to the statement whose text holds the expression.
- A string, number or keyword gives `Text`, `Number`, `Bool`, or null.
- A reference to a name with no binding, or to a statement being evaluated already (a
cycle), is *unresolved*, and the name is listed as unresolved. Otherwise it is the
value of the bound statement's expression, evaluated where it is used.
- A list or an object gives a list or an object of its items' values.
- A state name, an operator, a conditional, member access, indexing and an assignment
are refused `not-declarative` at the line where that expression starts (a binary
operation and a conditional start at their left operand, member access and indexing at
their base, a call at its name).
- `Query(...)` or `Mutation(...)` inside an expression is refused `inline-reserved`, at its
line, its arguments not evaluated.
- `@Name(...)` with a built-in function's name (`Count`, `First`, `Last`, `Sum`, `Avg`,
`Min`, `Max`, `Filter`, `Sort`, `Round`, `Abs`, `Floor`, `Ceil`, `Each`) or a state
action's (`Set`, `Reset`, `Run`) is refused `not-declarative`, its arguments not
evaluated.
- `Action([x])` or `@Action([x])` with a list of exactly one item is that item's value
when it is an action (*held* when it is unresolved or held, *refused* when refused, and
otherwise refused `type-mismatch` at the item's line); any other argument list is
refused `not-declarative` at the call's line, unevaluated.
- Any other `@Name(args)` is the action `Action { id: Name, payload }`. Every argument is
evaluated and placed as data (below, null kept); if one was refused the action is
*refused*, else if one is not yet written the action is *held*. The payload is absent
for no argument, the argument for one, and the list of them for two or more.
- A component call is built into a widget, below.
**Placing as data** (a prop other than `children`, an item of a list or object placed as
data, an action's argument): data and actions are themselves; null is `Null` inside a
list, an object or an action's arguments; a list or an object becomes `List` or
`Object` of its items placed as data, an item that is not yet written or refused being
left out; a widget is refused `type-mismatch` (at the line where the call's or the
action's argument holding it starts); *unresolved* and *held* are **not yet written**;
*refused* stays refused.
**Building a widget.** A call of a name the registry lacks is refused
`unknown-component` at its line, its arguments not evaluated. Otherwise its arguments
map, in order, to the names of the widget's positional order (§3); arguments past its end
are refused together, `excess-args`, at the first extra one's line, and dropped
unevaluated. Every other argument is evaluated, in order; an error about an argument is
at the line where that argument starts.
- The argument mapped to `children` must give a list. Its widgets become the widget's
children in order; *unresolved*, *held*, null and *refused* items are left out; any
other item is refused `type-mismatch` and left out. A widget instead of a list, or
data, is refused `type-mismatch` and gives no children; null, *unresolved*, *held* and
*refused* give no children.
- Any other argument is placed as data. Null leaves the prop absent ("null"); a value not
yet written leaves it absent ("not yet"); a refused one, or a widget (refused
`type-mismatch`), leaves it absent ("refused").
The widget is then checked by `validate` (§4). While the stream is open, a widget that
`validate` finds missing a required prop that is absent "not yet" is *held*, with no
error. Otherwise every error at the widget itself is resolved as below and reported;
then, if one of them drops the widget, it is *refused*; else the removals are made and
the widget is checked again, until it passes:
| `validate` error | Resolution |
|---|---|
| a required prop missing | absent "not yet" (the stream has ended): `missing-required`. Absent "null": `null-required`. Absent "refused": no further error. Otherwise `missing-required`. Each drops the widget. |
| a prop of the wrong type | `type-mismatch`; a required prop drops the widget, an optional one is removed |
| a prop the widget does not declare | `type-mismatch`; the prop is removed |
| a child the widget does not accept | `disallowed-child`, at the child's own line and statement; the child is removed |
An error the table names is reported at the line of the widget's call, unless it says
otherwise. A widget that passes is the value, its children already built and checked.
**The tree** is the entry's value when that is a widget, and there is none otherwise.
Each widget of the tree has a **key**: a widget that is a statement's value takes the
statement's name, `name#2` for the second use of that statement in the evaluation and so
on; any other widget takes its parent value's key followed by `/` and its position
(`root/0`; `{key}/{slot}` for a widget in a prop's slot). The **nodes** list each
widget of the tree in pre-order with its key, the source line of its call, and its
statement.
**Orphans** are the value statements the evaluation of the entry did not reach (none
when there is no entry). The unresolved names and the orphans are reported sorted.
**At the end of the stream:** with no entry, the error is `parse-failed` (line 0, no
statement) when no statement was produced and the prepared text is not empty, and
`no-root` (line 0, no statement) otherwise; with an entry
that gives no widget, `no-root` at the entry's line; with an entry found by the root
widget rather than `root`, `no-root` as a warning at its line, and the tree renders.
**Errors** are reported sorted by line, then statement, then code (as text), then
widget, then prop (an absent widget or prop before any), then warning (not one before
one), and of the errors equal in all of these only the first is kept. While the stream
is open, errors of a statement whose binding is pending are withheld.
A snapshot MUST NOT hold a widget its registry's `validate` refuses: the tree as a whole
passes `validate`.
### §11.6 Streaming
A host passes the text a chunk at a time; after each chunk, and when the host ends the
stream, the ingest gives a **snapshot**: the tree, its nodes, the errors, the unresolved
names, the orphans, and whether the text ended inside an open string or bracket
(**incomplete**: the last statement is open). A chunk after the end starts a new stream.
1. Each snapshot is that of §11.1–§11.5 for all the text so far, statements completing
as §11.3 says. A complete statement's text no longer changes, so its value only
changes as the statements it refers to arrive.
2. The pending statement is closed for the moment (§11.2 strings, §11.4 brackets) and
evaluated; its errors are withheld until it completes.
3. An open pending statement never replaces a complete one of the same name (§11.5).
4. Text that changes what came before it (a fence line arriving after unfenced lines)
changes the preparation, and so every later snapshot.
5. **Streaming equals parsing.** The snapshot at the end of a stream is the snapshot of
the whole text taken at once, however it was cut.
### §11.7 The canonical form
A tree that passes `validate` is written one statement a widget, in pre-order, each on
its own line ending with a line feed: the root is `root`; every other widget is named
by its widget name with its first letter in lowercase, followed by a count of that
lowercase name from 1, in pre-order (`card1`, `card2`). A statement is
`name = Widget(args)`, the arguments joined by `, `: one for each name of the widget's
positional order, `children` written `[name1, name2]` when the widget has children; a
missing prop or empty `children` is `null`, and the trailing ones are left off. Values:
- text as a JSON string: `"` and `\` escaped, `\b` `\f` `\n` `\r` `\t` for those, `\u00xx`
(lowercase) for the other characters below U+0020, everything else as itself;
- a number as the shortest decimal that reads back to the same binary64 value, in
positional notation, with no exponent and no fraction for an integer (`-0` for
negative zero); a number that is not finite cannot be written;
- `true`, `false`, `null`, and lists as `[a, b]`;
- an object as `{key: value}` in key order (`, ` between entries), a key written bare
when it is an identifier other than a keyword literal, and as a JSON string otherwise;
- an action as `@Id(...)`: nothing inside for no payload, the items of a list payload of
two or more, and the payload as the single argument otherwise. The id must be an
identifier other than `Action`, a built-in function's or a state action's name.
A tree holding a widget whose name a call cannot take, a prop outside its widget's
positional order, a prop whose value is `null` (a null argument is an absent one), or an
action id the form cannot carry, is not written. The canonical form of a tree parses
back (§11.5) to the same tree, and writing that tree again gives the same text.
### §11.8 Reason codes
| Code | Raised when | What is dropped |
|---|---|---|
| `unknown-component` | a call names a widget the registry lacks | the widget |
| `missing-required` | a required prop is absent (once the stream has ended, for one not yet written) | the widget |
| `null-required` | a required prop is written `null` | the widget |
| `type-mismatch` | a value is not of its prop's type; a widget stands where data goes, or data among children; children not written as a list | an optional prop or an item; a required prop drops the widget |
| `excess-args` | more arguments than the widget's positional order | the extra arguments |
| `disallowed-child` | a child the parent's `AllowedChildren` does not accept | the child |
| `not-declarative` | state, a query, a mutation, an operator, a conditional, member access, indexing, an assignment, a built-in function, a state action, or a sequence of actions | the statement, or the value |
| `inline-reserved` | `Query` or `Mutation` inside an expression | the value |
| `syntax` | a complete statement that does not parse | the statement |
| `no-root` | at the end of the stream, no entry or an entry that is not a widget; a warning when the entry is found by the root widget | everything (not for the warning) |
| `parse-failed` | at the end of the stream, text with no statement | everything |
An error names its source line (0 for the program as a whole), its statement (empty for
the whole program), its code, the widget and the prop it concerns where there is one,
and a message for a person or a model to act on. Messages are not normative.
The widget an error names: for `unknown-component`, `missing-required`,
`null-required`, `excess-args`, and a `type-mismatch` that `validate` raised or that is
about a call's own argument (a widget in a prop's slot, the `children` argument or an
item of it), the called widget; for `disallowed-child`, the child; for a `type-mismatch`
about a widget placed as data inside a list, an object or an action's arguments, that
widget. No other error names a widget. The prop an error names: for `missing-required`,
`null-required`, and a `type-mismatch` that names the called widget, the prop
(`children` for the children argument); no other error names one.
### §11.9 Conformance
The pack in [`conformance/lang/v1/`](conformance/lang/v1/) holds the library it is
written against (`library.json`, each widget's positional order written out) and five
classes; a conforming ingest reproduces every case's expected value, errors compared
without their messages:
- **L1, statements** (`l1-statements.json`): the statements of each text at the end of the
stream, in order (name, source line, kind, complete): each produced statement with its
kind (`value`, `state`, `query`, `mutation`), and each complete statement that did not
parse with the kind `syntax`; and whether it ended open.
- **L2, ingest** (`l2-ingest.json`): each text's snapshot at the end of the stream.
- **L3, streaming** (`l3-stream.json`): each case's snapshot after every chunk and at
the end, which equals L2's snapshot of the joined chunks.
- **L4, the canonical form** (`l4-write.json`): each tree's text, which parses back to
the tree.
- **L5, refusals** (`l5-refusals.json`): negative controls, each refused with its code at
its line, and its whole snapshot.
`MANIFEST.json` names each file by its SHA-256. The independent verifier
[`conformance/lang_verify.py`](conformance/lang_verify.py) is written from this section
alone and shares no code with the reference implementation.
---
*joule-ui v0.3.0 — the surface layer (v0.1), the metered, sealed interaction layer
that makes a UI accountable by construction (v0.2), and the line language a model
writes a surface in, taken in as it streams (v0.3).*