Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,7 @@ jobs:
run: pip install pytest && pytest docs/scripts/test_convert_docs.py

- name: Copy schemas to documentation
run: |
cp -R specification/* docs/specification/
# Flatten JSON files to version roots to support clean URLs without redirects
for version in v0_8 v0_9; do
if [ -d "docs/specification/$version/json" ]; then
cp docs/specification/$version/json/*.json docs/specification/$version/
fi
done
run: python docs/scripts/prepare_docs.py

- name: Convert Admonitions in Documentation
run: python docs/scripts/convert_docs.py
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ site/

# Python virtual environment
.venv/

# Copied specification files
docs/specification/v*/

68 changes: 35 additions & 33 deletions docs/concepts/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ A2UI uses an **adjacency list model** for component hierarchies. Instead of nest

```json
{
"surfaceUpdate": {
"version": "v0.9",
"updateComponents": {
"surfaceId": "main",
"components": [
{"id": "root", "component": {"Column": {"children": {"explicitList": ["greeting", "buttons"]}}}},
{"id": "greeting", "component": {"Text": {"text": {"literalString": "Hello"}}}},
{"id": "buttons", "component": {"Row": {"children": {"explicitList": ["cancel-btn", "ok-btn"]}}}},
{"id": "cancel-btn", "component": {"Button": {"child": "cancel-text", "action": {"name": "cancel"}}}},
{"id": "cancel-text", "component": {"Text": {"text": {"literalString": "Cancel"}}}},
{"id": "ok-btn", "component": {"Button": {"child": "ok-text", "action": {"name": "ok"}}}},
{"id": "ok-text", "component": {"Text": {"text": {"literalString": "OK"}}}}
{"id": "root", "component": "Column", "children": ["greeting", "buttons"]},
{"id": "greeting", "component": "Text", "text": "Hello"},
{"id": "buttons", "component": "Row", "children": ["cancel-btn", "ok-btn"]},
{"id": "cancel-btn", "component": "Button", "child": "cancel-text", "action": {"name": "cancel"}},
{"id": "cancel-text", "component": "Text", "text": "Cancel"},
{"id": "ok-btn", "component": "Button", "child": "ok-text", "action": {"name": "ok"}},
{"id": "ok-text", "component": "Text", "text": "OK"}
]
}
}
Expand All @@ -46,7 +48,7 @@ Every component has:
3. **Properties**: Configuration specific to that type

```json
{"id": "welcome", "component": {"Text": {"text": {"literalString": "Hello"}, "usageHint": "h1"}}}
{"id": "welcome", "component": "Text", "text": "Hello", "variant": "h1"}
```

## The Standard Catalog
Expand All @@ -62,14 +64,14 @@ For the complete component gallery with examples, see [Component Reference](../r

## Static vs. Dynamic Children

**Static (`explicitList`)** - Fixed list of child IDs:
**Static (`children` array)** - Fixed list of child IDs:
```json
{"children": {"explicitList": ["back-btn", "title", "menu-btn"]}}
{"children": ["back-btn", "title", "menu-btn"]}
```

**Dynamic (`template`)** - Generate children from data array:
**Dynamic (`children` template)** - Generate children from data array:
```json
{"children": {"template": {"dataBinding": "/items", "componentId": "item-template"}}}
{"children": {"template": {"path": "/items", "componentId": "item-template"}}}
```

For each item in `/items`, render the `item-template`. See [Data Binding](data-binding.md) for details.
Expand All @@ -78,26 +80,26 @@ For each item in `/items`, render the `item-template`. See [Data Binding](data-b

Components get their values two ways:

- **Literal** - Fixed value: `{"text": {"literalString": "Welcome"}}`
- **Data-bound** - From data model: `{"text": {"path": "/user/name"}}`

LLMs can generate components with literal values or bind them to data paths for dynamic content.

## Composing Surfaces

Components compose into **surfaces** (widgets):

1. LLM generates component definitions via `surfaceUpdate`
2. LLM populates data via `dataModelUpdate`
3. LLM signals render via `beginRendering`
4. Client renders all components as native widgets

A surface is a complete, cohesive UI (form, dashboard, chat, etc.).

## Incremental Updates

- **Add** - Send new `surfaceUpdate` with new component IDs
- **Update** - Send `surfaceUpdate` with existing ID and new properties
81: - **Literal** - Fixed value: `{"text": "Welcome"}`
82: - **Data-bound** - From data model: `{"text": {"path": "/user/name"}}`
83:
84: LLMs can generate components with literal values or bind them to data paths.
85:
86: ## Composing Surfaces
87:
88: Components compose into **surfaces** (widgets):
89:
90: 1. LLM generates component definitions via `updateComponents`
91: 2. LLM populates data via `updateDataModel`
92: 3. LLM signals render via `createSurface`
93: 4. Client renders all components as native widgets
94:
95: A surface is a complete, cohesive UI (form, dashboard, chat, etc.).
96:
97: ## Incremental Updates
98:
99: - **Add** - Send new `updateComponents` with new component IDs
100: - **Update** - Send `updateComponents` with existing ID and new properties
- **Remove** - Update parent's `children` list to exclude removed IDs

The flat structure makes all updates simple ID-based operations.
Expand Down
38 changes: 21 additions & 17 deletions docs/concepts/data-binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,19 @@ Each surface has a JSON object holding state:
```

- `/user/name` → `"Alice"`
- `/items/0` → `"Apple"`
- `/user/name` → `"Alice"`
- `/items/0` → `"Apple"`

## Literal vs. Path Values

**Literal (fixed):**
```json
{"id": "title", "component": {"Text": {"text": {"literalString": "Welcome"}}}}
{"id": "title", "component": "Text", "text": "Welcome"}
```

**Data-bound (reactive):**
```json
{"id": "username", "component": {"Text": {"text": {"path": "/user/name"}}}}
{"id": "username", "component": "Text", "text": {"path": "/user/name"}}
```

When `/user/name` changes from "Alice" to "Bob", the text **automatically updates** to "Bob".
Expand All @@ -61,11 +62,11 @@ When `/user/name` changes from "Alice" to "Bob", the text **automatically update
Components bound to data paths automatically update when the data changes:

```json
{"id": "status", "component": {"Text": {"text": {"path": "/order/status"}}}}
{"id": "status", "component": "Text", "text": {"path": "/order/status"}}
```

- **Initial:** `/order/status` = "Processing..." → displays "Processing..."
- **Update:** Send `dataModelUpdate` with `status: "Shipped"` → displays "Shipped"
- **Initial:** `/order/status` = "Processing..." → displays "Processing..."
- **Update:** Send `updateDataModel` with `status: "Shipped"` → displays "Shipped"

No component updates needed—just data updates.

Expand All @@ -76,11 +77,8 @@ Use templates to render arrays:
```json
{
"id": "product-list",
"component": {
"Column": {
"children": {"template": {"dataBinding": "/products", "componentId": "product-card"}}
}
}
"component": "Column",
"children": {"template": {"path": "/products", "componentId": "product-card"}}
}
```

Expand All @@ -96,11 +94,11 @@ Use templates to render arrays:
Inside a template, paths are scoped to the array item:

```json
{"id": "product-name", "component": {"Text": {"text": {"path": "/name"}}}}
{"id": "product-name", "component": "Text", "text": {"path": "/name"}}
```

- For `/products/0`, `/name` resolves to `/products/0/name` → "Widget"
- For `/products/1`, `/name` resolves to `/products/1/name` → "Gadget"
- For `/products/0`, `/name` resolves to `/products/0/name` → "Widget"
- For `/products/1`, `/name` resolves to `/products/1/name` → "Gadget"

Adding/removing items automatically updates the rendered components.

Expand All @@ -110,15 +108,15 @@ Interactive components update the data model bidirectionally:

| Component | Example | User Action | Data Update |
|-----------|---------|-------------|-------------|
| **TextField** | `{"text": {"path": "/form/name"}}` | Types "Alice" | `/form/name` = "Alice" |
| **TextField** | `{"value": {"path": "/form/name"}}` | Types "Alice" | `/form/name` = "Alice" |
| **CheckBox** | `{"value": {"path": "/form/agreed"}}` | Checks box | `/form/agreed` = true |
| **MultipleChoice** | `{"selections": {"path": "/form/country"}}` | Selects "Canada" | `/form/country` = ["ca"] |
| **ChoicePicker** | `{"value": {"path": "/form/country"}}` | Selects "Canada" | `/form/country` = ["ca"] |

## Best Practices

**1. Use granular updates** - Update only changed paths:
```json
{"dataModelUpdate": {"path": "/user", "contents": [{"key": "name", "valueString": "Alice"}]}}
{"version": "v0.9", "updateDataModel": {"surfaceId": "main", "path": "/user", "value": {"name": "Alice"}}}
```

**2. Organize by domain** - Group related data:
Expand All @@ -130,3 +128,9 @@ Interactive components update the data model bidirectionally:
```json
{"price": "$19.99"} // Not: {"price": 19.99}
```

**4. Use Client-Side Formatting** - For dynamic localization or complex updates, use client-side functions:
```json
{"text": {"call": "formatCurrency", "args": {"value": 19.99, "currency": "USD"}}}
```
See the **[Functions Reference](../reference/functions.md)** for a complete list of available functions.
52 changes: 33 additions & 19 deletions docs/concepts/data-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ Client (Stream Reader) → Message Parser → Renderer → Native UI
A2UI defines a sequence of JSON messages that describe the UI. When streamed, these messages are often formatted as **JSON Lines (JSONL)**, where each line is a complete JSON object.

```jsonl
{"surfaceUpdate":{"surfaceId":"main","components":[...]}}
{"dataModelUpdate":{"surfaceId":"main","contents":[{"key":"user","valueMap":[{"key":"name","valueString":"Alice"}]}]}}
{"beginRendering":{"surfaceId":"main","root":"root-component"}}
{"version": "v0.9", "createSurface": {"surfaceId": "main", "catalogId": "..."}}
{"version": "v0.9", "updateComponents": {"surfaceId": "main", "components": [...]}}
{"version": "v0.9", "updateDataModel": {"surfaceId": "main", "value": {"user": "Alice"}}}
```

**Why this format?**
Expand All @@ -30,41 +30,55 @@ A sequence of self-contained JSON objects is streaming-friendly, easy for LLMs t

**User:** "Book a table for 2 tomorrow at 7pm"

**1. Agent defines UI structure:**
**1. Agent signals render (v0.9 starts with creation):**

```json
{"surfaceUpdate": {"surfaceId": "booking", "components": [
{"id": "root", "component": {"Column": {"children": {"explicitList": ["header", "guests-field", "submit-btn"]}}}},
{"id": "header", "component": {"Text": {"text": {"literalString": "Confirm Reservation"}, "usageHint": "h1"}}},
{"id": "guests-field", "component": {"TextField": {"label": {"literalString": "Guests"}, "text": {"path": "/reservation/guests"}}}},
{"id": "submit-btn", "component": {"Button": {"child": "submit-text", "action": {"name": "confirm", "context": [{"key": "details", "value": {"path": "/reservation"}}]}}}}
]}}
{"version": "v0.9", "createSurface": {"surfaceId": "booking", "catalogId": "https://a2ui.org/specification/v0_9/standard_catalog.json"}}
```

**2. Agent populates data:**
**2. Agent defines UI structure:**

```json
{"dataModelUpdate": {"surfaceId": "booking", "path": "/reservation", "contents": [
{"key": "datetime", "valueString": "2025-12-16T19:00:00Z"},
{"key": "guests", "valueString": "2"}
]}}
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "booking",
"components": [
{"id": "root", "component": "Column", "children": ["header", "guests-field", "submit-btn"]},
{"id": "header", "component": "Text", "text": "Confirm Reservation", "variant": "h1"},
{"id": "guests-field", "component": "TextField", "label": "Guests", "value": {"path": "/reservation/guests"}},
{"id": "submit-btn", "component": "Button", "child": "submit-text", "action": {"name": "confirm", "context": {"path": "/reservation"}}},
{"id": "submit-text", "component": "Text", "text": "Confirm"}
]
}
}
```

**3. Agent signals render:**
**3. Agent populates data:**

```json
{"beginRendering": {"surfaceId": "booking", "root": "root"}}
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "booking",
"path": "/reservation",
"value": {
"datetime": "2025-12-16T19:00:00Z",
"guests": 2
}
}
}
```

**4. User edits guests to "3"** → Client updates `/reservation/guests` automatically (no message to agent yet)

**5. User clicks "Confirm"** → Client sends action with updated data:

```json
{"userAction": {"name": "confirm", "surfaceId": "booking", "context": {"details": {"datetime": "2025-12-16T19:00:00Z", "guests": "3"}}}}
{"action": {"name": "confirm", "surfaceId": "booking", "context": {"datetime": "2025-12-16T19:00:00Z", "guests": 3}}}
```

**6. Agent responds** → Updates UI or sends `{"deleteSurface": {"surfaceId": "booking"}}` to clean up
**6. Agent responds** → Updates UI or sends `{"version": "v0.9", "deleteSurface": {"surfaceId": "booking"}}` to clean up

## Transport Options

Expand Down
6 changes: 3 additions & 3 deletions docs/concepts/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ How components connect to application state using JSON Pointer paths. Covers rea

A2UI uses four message types:

- **`surfaceUpdate`**: Define or update UI components
- **`dataModelUpdate`**: Update application state
- **`beginRendering`**: Signal the client to render
- **`createSurface`**: Initialize a surface and catalog
- **`updateComponents`**: Define or update UI components
- **`updateDataModel`**: Update application state
- **`deleteSurface`**: Remove a UI surface

For complete technical details, see [Message Reference](../reference/messages.md).
11 changes: 6 additions & 5 deletions docs/guides/agent-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ root_agent = Agent(
)
```

Don't forget to set the `GOOGLE_API_KEY` environment variable to run this example.
Don't forget to set the `GOOGLE_API_KEY` environment variable to run this example.

```bash
echo 'GOOGLE_API_KEY="YOUR_API_KEY"' > .env
Expand All @@ -92,7 +92,7 @@ Select `my_agent` from the list, and ask questions about restaurants in New York

## Generating A2UI Messages

Getting the LLM to generate A2UI messages requires some prompt engineering.
Getting the LLM to generate A2UI messages requires some prompt engineering.

> ⚠️ **Attention**
>
Expand Down Expand Up @@ -131,7 +131,7 @@ To generate the response, you MUST follow these rules:
4. The JSON part MUST validate against the A2UI JSON SCHEMA provided below.

--- UI TEMPLATE RULES ---
- If the query is for a list of restaurants, use the restaurant data you have already received from the `get_restaurants` tool to populate the `dataModelUpdate.contents` array (e.g., as a `valueMap` for the "items" key).
- If the query is for a list of restaurants, use the restaurant data you have already received from the `get_restaurants` tool to populate the `updateDataModel.contents` object (e.g., set the "items" key to the list of restaurants).
- If the number of restaurants is 5 or fewer, you MUST use the `SINGLE_COLUMN_LIST_EXAMPLE` template.
- If the number of restaurants is more than 5, you MUST use the `TWO_COLUMN_LIST_EXAMPLE` template.
- If the query is to book a restaurant (e.g., "USER_WANTS_TO_BOOK..."), you MUST use the `BOOKING_FORM_EXAMPLE` template.
Expand Down Expand Up @@ -159,8 +159,9 @@ Your agent will no longer strictly output text. Instead, it will output text and

The `A2UI_SCHEMA` that we imported is a standard JSON schema that defines valid operations like:

* `render` (displaying a UI)
* `update` (changing data in an existing UI)
* `createSurface` (displaying a UI)
* `updateComponents` (changing the UI structure)
* `updateDataModel` (changing data in an existing UI)

Because the output is structured JSON, you may parse and validate it before sending it to the client.

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/client-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ TODO: Add action handling examples.
## Error Handling

Common errors to handle:
- **Invalid Surface ID**: Surface referenced before `beginRendering` was received
- **Invalid Surface ID**: Surface referenced before `createSurface` was received
- **Invalid Component ID**: Component IDs must be unique within a surface
- **Invalid Data Path**: Check data model structure and JSON Pointer syntax
- **Schema Validation Failed**: Verify message format matches A2UI specification
Expand Down
6 changes: 3 additions & 3 deletions docs/guides/custom-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ You register entire catalogs with your client application, not individual compon
2. **Client Registers Catalog**: You register the catalog (and its component implementations) with your client app.
3. **Client Announces Support**: The client informs the agent which catalogs it supports.
4. **Agent Selects Catalog**: The agent chooses a catalog for a given UI surface.
5. **Agent Generates UI**: The agent generates `surfaceUpdate` messages using components from that catalog by name.
5. **Agent Generates UI**: The agent generates `updateComponents` messages using components from that catalog by name.

## Defining Custom Catalogs

Expand All @@ -45,9 +45,9 @@ TODO: Add detailed guide for defining custom catalogs for each platform.

## Agent-Side: Using Components from a Custom Catalog

Once a catalog is registered on the client, agents can use components from it in `surfaceUpdate` messages.
Once a catalog is registered on the client, agents can use components from it in `updateComponents` messages.

The agent specifies which catalog to use via the `catalogId` in the `beginRendering` message.
The agent specifies which catalog to use via the `catalogId` in the `createSurface` message.

TODO: Add examples of:
- How agents select catalogs
Expand Down
Loading
Loading