A2UI Progressive Rendering — Live Demo

Interactive illustration of four proposed A2UI v1.0 primitives: pending state, streaming lifecycle flag, append patch op, and write bindings + origin tracking. Every change in the rendered UI (left) corresponds to a wire message shown on the right — exactly as it would flow over a real A2UI WebSocket.

Fully client-side simulation. No backend. Source: index.html. RFC: RFC.md.

Proposal 1 — Pending state

A component with path-bound fields that resolve to undefined should render a shimmer placeholder by default. Today this is per-client convention; the proposal standardizes it.

Rendered UI

Wire messages

Without the proposal, different clients would render this skeleton differently (blank / spinner / shimmer / error). Same JSON, inconsistent UX. With the proposal, the pending behavior is spec-guaranteed.

Proposal 2 — Streaming lifecycle flag

updateDataModel messages carry an optional streaming flag. While true, the renderer shows a live indicator (typewriter caret). On false (or omitted), the value is final.

Rendered UI

Wire messages

The caret is a protocol signal, not just a style. Action buttons with path-bound event names can stay disabled while streaming: true and enable on streaming: false — no per-client guard code.

Proposal 3 — append patch op

Today every updateDataModel replaces the value at a path (set). Streaming a 2,000-char response takes ~100 KB of wire traffic because each delta re-sends the accumulated value. The append op sends only the delta — ~40× less bandwidth for long text.

Side-by-side: set vs. append

set (current v0.9)

bytes on wire0
messages sent0

append (proposed)

bytes on wire0
messages sent0
Savings grow with text length. A ~100-char headline shows 16× reduction. A 10,000-char article shows ~48×. Negligible for small values — the proposal keeps set as the default so small fields pay nothing.

Proposal 4 — Write bindings + origin tracking

A real PaginatedTable below. The server fetched /buffer (100 drinks ⬛) once. Every time you click Prev / Next, the component performs local writes to /page_meta and /visible_drinks ⬜ — mutations to the data model that don't round-trip to the agent. The writes log on the left records each one. The right side shows the resulting data-model state and what the agent would receive on the next user message.

Rendered PaginatedTable

Local writes log (newest first)

Surface data model

What the agent receives (on the next user message)

In client-origin mode the agent receives just /page_meta and /visible_drinks — enough to resolve "tell me about the 3rd one" to visible_drinks[2] without ever shipping the 100-row buffer. No exposure list. No data-layout coupling. The right state ships because the right entity (the component) wrote it.