joule-ui takes in OpenUI Lang 1.0 the way it takes in A2UI: a model writes a surface as a program, one widget a line, and joule-ui-lang builds a widget tree against the host's registry as the program streams. After every chunk a snapshot holds the tree as far as it can be built, each widget checked by joule-ui's own validate, and every error with its source line and reason code. State, queries, operators, conditionals, built-in functions and sequences of actions are parsed and refused not-declarative. A canonical writer turns a tree back into the language; it parses back to the same tree. - joule-ui-core: WidgetSchema::positional, the order a positional call passes props in (SPEC §3); absent from the wire form when undeclared. - joule-ui-interaction: begin_interaction and Pending::finish, the lifecycle's three phases in two steps for a host whose handler is asynchronous (SPEC §6); run_interaction is built on them and seals what it sealed before. - conformance/lang/v1: L1 statements, L2 ingest, L3 streaming, L4 the canonical form, L5 refusals, with an independent Python verifier written from §11. - docs/create.md: the design of Create. Co-Authored-By: Claude Opus 5.5 <[email protected]>
579 lines
34 KiB
Markdown
579 lines
34 KiB
Markdown
# 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.
|
||
|
||
## §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). 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).*
|