joule-ui-catalogue now holds the node form both ways: the reading of a node and the catalogue writer. joule-ui-a2ui keeps its names on that reading, so wai-app depends on the neutral crate and neither format's name is left under wai/, in a manifest or a lock file. A test holds the two lock files and the manifest to that. check_catalogue compares the document with what the registry writes, every keyword of it, and reads it as a schema: on each node it builds, the catalogue and the reading must agree. That includes a node with no props for a widget that requires some, a key folding onto a slot of an open widget, and every child a widget does not admit. Co-Authored-By: Claude Opus 5.5 <[email protected]>
88 lines
4.6 KiB
Markdown
88 lines
4.6 KiB
Markdown
# joule-ui
|
||
|
||
A typed declarative protocol for **agent-emitted user interfaces**.
|
||
|
||
joule-ui is the sibling to [JouleClaw](../jouleclaw/) for the moment when an
|
||
agent's resolution has to become something a human can see and interact
|
||
with. The agent emits a **widget tree** — `(name, props, children)`,
|
||
recursively — against a **widget registry** the host has published. The
|
||
host renders. No React in the spec, no HTML over the wire, no agent
|
||
holding the renderer.
|
||
|
||
The shape the field has converged on for this is *declarative components
|
||
against a registry*: agents are far more reliable emitting structured
|
||
data than emitting code, and renderers are far easier to harden when they
|
||
own their own widgets. joule-ui pins that shape as a protocol; the
|
||
renderer is product surface, owned by the consumer.
|
||
|
||
## What ships in v1
|
||
|
||
- `joule-ui-rs/crates/joule-ui-core/` — the protocol types (`Widget`,
|
||
`PropValue`, `WidgetSchema`, `Registry`) and the recursive validator.
|
||
Pure Rust, no IO, JSON-serialisable.
|
||
|
||
- `joule-ui-rs/crates/joule-ui-interaction/` (**v0.2**) — the layer no
|
||
other UI protocol carries: when a human *or an agent* fires an
|
||
`Action`, the invocation is an **accountable event**. Every fire names
|
||
the principal that made it — a `did:key`, the same whether a person or
|
||
an agent (the protocol draws no distinction; both are traced).
|
||
`run_interaction` authorizes it against a capability *before it runs*
|
||
(a denied action's handler never executes), meters it in joules,
|
||
charges its cost (a reading or an estimate, and which), and an
|
||
`InteractionLog` seals it as the next
|
||
link of a **hash-chained** receipt. The actor is bound into the signed
|
||
core, so an interaction cannot be re-attributed without breaking the
|
||
chain. A joule-ui session becomes a tamper-evident ledger of *who fired
|
||
it, what it computed, and what it cost* — verifiable by anyone holding
|
||
the host's key. The metering (Sandbox `PowerMeter`) and
|
||
the capability (JCP grant) are **recognized edges**; the receipt, the
|
||
chain, and the lifecycle are joule-ui's. See [`SPEC.md`](SPEC.md) §5–§7
|
||
and `examples/metered_surface.rs` for the full loop.
|
||
|
||
- `joule-ui-rs/crates/joule-ui-catalogue/` — the node form, both ways: a JSON
|
||
tree of `{component, props, children}` read into a validated widget tree, and
|
||
any registry written as a catalogue, a JSON Schema of those nodes, for an agent
|
||
to write against. Whatever a catalogue admits the reading accepts, and a
|
||
registry declaring what no node can state has none. See [`SPEC.md`](SPEC.md) §3.
|
||
|
||
- `joule-ui-rs/crates/joule-ui-a2ui/` — the A2UI ingest, on that reading.
|
||
|
||
- `joule-ui-rs/crates/joule-ui-lang/` (**v0.3**) — the line language: a
|
||
model writes a surface as a short program, one widget a line
|
||
(OpenUI Lang 1.0; see [`NOTICE`](NOTICE)), and joule-ui takes it in as it
|
||
streams. After every chunk a snapshot holds the tree as far as it can be
|
||
built, each widget validated against the registry, and every error with its
|
||
line and reason; a canonical writer turns a tree back into the language.
|
||
State, queries, expressions and sequences of actions are refused
|
||
`not-declarative`: joule-ui carries no behaviour on the wire. See
|
||
[`SPEC.md`](SPEC.md) §11, the pack in [`conformance/lang/`](conformance/lang/)
|
||
with its independent verifier, and [`docs/create.md`](docs/create.md), the
|
||
design of Create, the page that builds a WAI app as a model describes it.
|
||
|
||
The surface layer makes joule-ui a peer of the declarative-UI protocols
|
||
(A2UI, MCP Apps). The interaction layer makes it the only one you can
|
||
hand to an auditor: a UI whose every interaction is energy-accounted and
|
||
sealed, by construction.
|
||
|
||
What is **not** here:
|
||
|
||
- A renderer. The renderer is owned by the host (browser app, native
|
||
shell, terminal UI). joule-ui validates the surface and seals the
|
||
interaction; it does not paint pixels.
|
||
- A transport. The validated spec and the interaction stream ride MCP,
|
||
REST, SSE, gRPC, Web MoQ — any bytes-out protocol. Transport-agnostic.
|
||
- A "model emits React" path. The agent emits a declarative tree against
|
||
the registry; arbitrary code emission is out of scope.
|
||
|
||
## Why a sibling, not inside JouleClaw
|
||
|
||
The spec layer is protocol — small, stable, conformant. The renderer
|
||
registry is product surface — many of them, evolving fast. Splitting
|
||
them protects the *tools for use* line: JouleClaw stays the runtime
|
||
substrate; joule-ui is the UI protocol; consumers ship their own
|
||
widgets.
|
||
|
||
## Licence
|
||
|
||
Specification text and protocol document: CC-BY-4.0 (see [`LICENSE`](LICENSE)).
|
||
Reference implementation in `joule-ui-rs/`: Apache-2.0 (see [`joule-ui-rs/LICENSE`](joule-ui-rs/LICENSE)).
|