Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ac9f0e7
feat(cockpit): complete cockpit application with 14 capability exampl…
blove Apr 5, 2026
562687e
ci: remove MCP job and references from workflows
blove Apr 8, 2026
7ac7bd7
docs: add library landing pages & home page refactor design spec
blove Apr 9, 2026
7a67f38
docs: add implementation plan for library landing pages
blove Apr 9, 2026
41632df
docs: add A2UI Phase 3 design spec and implementation plan
blove Apr 10, 2026
9ff8e70
feat(render): add RenderEvent types for handler, state change, and li…
blove Apr 10, 2026
0d10d95
feat(render): add emitEvent callback to RenderContext
blove Apr 10, 2026
bc00079
feat(render): add events output with lifecycle and state change emiss…
blove Apr 10, 2026
6e2c346
feat(render): add opt-in element lifecycle emission to RenderElementC…
blove Apr 10, 2026
c888d56
feat(a2ui): add Video, AudioPlayer, Slider, DateTimeInput, Tabs, and …
blove Apr 10, 2026
4bc4b82
feat(a2ui): register 6 new components in catalog (18 total)
blove Apr 10, 2026
17fe174
feat(a2ui): map actions to spec on bindings and initialize state from…
blove Apr 10, 2026
f133ef0
feat(a2ui): add default handlers and events output to A2uiSurfaceComp…
blove Apr 10, 2026
e6735bb
feat(chat): add renderEvent output and remove hardcoded A2UI catalog
blove Apr 10, 2026
7eda0d3
feat(chat): export ChatRenderEvent type from public API
blove Apr 10, 2026
e314bf7
docs: add render events, update A2UI docs, add custom catalogs guide
blove Apr 10, 2026
67322fc
fix: address code review findings
blove Apr 10, 2026
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
1 change: 0 additions & 1 deletion .claude/worktrees/blissful-bartik
Submodule blissful-bartik deleted from 44e21d
1 change: 0 additions & 1 deletion .claude/worktrees/optimistic-jang
Submodule optimistic-jang deleted from 0a6d25
14 changes: 1 addition & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,6 @@ jobs:
- run: npm ci
- run: npx tsx apps/cockpit/scripts/deploy-smoke.ts --url https://cockpit.cacheplane.ai --dry-run

mcp:
name: MCP — build / smoke
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
- uses: actions/setup-node@v6.3.0
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx nx test mcp --skip-nx-cache

chat-agent-smoke:
name: Chat Agent — smoke
runs-on: ubuntu-latest
Expand Down Expand Up @@ -162,7 +150,7 @@ jobs:

deploy:
name: Deploy → Vercel
needs: [library, website, cockpit, cockpit-examples-build, cockpit-smoke, cockpit-secret-integration, cockpit-deploy-smoke, mcp, chat-agent-smoke, cockpit-e2e, website-e2e]
needs: [library, website, cockpit, cockpit-examples-build, cockpit-smoke, cockpit-secret-integration, cockpit-deploy-smoke, chat-agent-smoke, cockpit-e2e, website-e2e]
runs-on: ubuntu-latest
# Only deploy on pushes to main, not on pull requests
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
Expand Down
181 changes: 179 additions & 2 deletions apps/website/content/docs/chat/a2ui/catalog.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Component Catalog

The built-in A2UI catalog provides 12 Angular components covering display, layout, and interactive controls. It is included automatically when using `ChatComponent`, and can be instantiated directly for custom rendering setups.
The built-in A2UI catalog provides 18 Angular components covering display, layout, interactive controls, media, and advanced inputs. Pass `a2uiBasicCatalog()` to the `ChatComponent` `views` input to enable A2UI rendering, or instantiate it directly for custom setups.

**Import:**

Expand All @@ -14,7 +14,7 @@ import { a2uiBasicCatalog } from '@cacheplane/chat';
function a2uiBasicCatalog(): ViewRegistry
```

Returns a `ViewRegistry` mapping 12 A2UI type names to their Angular component implementations. Pass the result to `A2uiSurfaceComponent` or use it as a starting point for a custom registry.
Returns a `ViewRegistry` mapping 18 A2UI type names to their Angular component implementations. Pass the result to `A2uiSurfaceComponent` or use it as a starting point for a custom registry.

```typescript
const catalog = a2uiBasicCatalog();
Expand Down Expand Up @@ -229,6 +229,177 @@ A dropdown select control with a list of string options.
| `_bindings` | `Record<string, string>` | Bind `selected` to a data model path |
| `emit` | injected | Event emitter provided by the render engine |

### DateTimeInput

A date, time, or datetime input with two-way binding.

| A2UI type | Angular component | Selector |
|-----------|-------------------|----------|
| `DateTimeInput` | `A2uiDateTimeInputComponent` | `a2ui-date-time-input` |

| Prop | Type | Description |
|------|------|-------------|
| `label` | `string` | Input label |
| `value` | `string` | Current value (bind via `_bindings`) |
| `inputType` | `'date' \| 'time' \| 'datetime-local'` | HTML input type. Defaults to `'date'` |
| `min` | `string` | Minimum allowed value |
| `max` | `string` | Maximum allowed value |
| `_bindings` | `Record<string, string>` | Bind `value` to a data model path |
| `emit` | injected | Event emitter provided by the render engine |

```json
{
"id": "date-field",
"component": "DateTimeInput",
"label": "Appointment date",
"value": {"path": "/appointmentDate"},
"inputType": "date",
"_bindings": {"value": "/appointmentDate"}
}
```

### Slider

A range slider input with two-way binding.

| A2UI type | Angular component | Selector |
|-----------|-------------------|----------|
| `Slider` | `A2uiSliderComponent` | `a2ui-slider` |

| Prop | Type | Description |
|------|------|-------------|
| `label` | `string` | Slider label |
| `value` | `number` | Current value (bind via `_bindings`) |
| `min` | `number` | Minimum value |
| `max` | `number` | Maximum value |
| `step` | `number` | Step increment |
| `_bindings` | `Record<string, string>` | Bind `value` to a data model path |
| `emit` | injected | Event emitter provided by the render engine |

```json
{
"id": "volume",
"component": "Slider",
"label": "Volume",
"value": {"path": "/volume"},
"min": 0,
"max": 100,
"step": 1,
"_bindings": {"value": "/volume"}
}
```

## Layout Components (continued)

### Tabs

A tabbed container that shows one child panel at a time based on the selected tab index.

| A2UI type | Angular component | Selector |
|-----------|-------------------|----------|
| `Tabs` | `A2uiTabsComponent` | `a2ui-tabs` |

| Prop | Type | Description |
|------|------|-------------|
| `tabs` | `{label: string, childKeys: string[]}[]` | Tab definitions with labels and child component IDs |
| `selected` | `number` | Currently selected tab index. Defaults to `0` |
| `_bindings` | `Record<string, string>` | Bind `selected` to a data model path |
| `spec` | `Spec` | Injected automatically by the render engine |
| `emit` | injected | Event emitter provided by the render engine |

```json
{
"id": "info-tabs",
"component": "Tabs",
"tabs": [
{"label": "Overview", "childKeys": ["overview-content"]},
{"label": "Details", "childKeys": ["detail-list"]}
],
"selected": 0,
"_bindings": {"selected": "/activeTab"}
}
```

### Modal

A dialog overlay that renders child content when open. Supports an optional title and dismissible close button.

| A2UI type | Angular component | Selector |
|-----------|-------------------|----------|
| `Modal` | `A2uiModalComponent` | `a2ui-modal` |

| Prop | Type | Description |
|------|------|-------------|
| `title` | `string` | Optional modal heading |
| `open` | `boolean` | Whether the modal is visible |
| `childKeys` | `string[]` | Child component IDs rendered inside the modal body |
| `dismissible` | `boolean` | Shows a close button when `true`. Defaults to `true` |
| `_bindings` | `Record<string, string>` | Bind `open` to a data model path |
| `spec` | `Spec` | Injected automatically by the render engine |
| `emit` | injected | Event emitter provided by the render engine |

```json
{
"id": "confirm-dialog",
"component": "Modal",
"title": "Confirm Action",
"open": {"path": "/showConfirm"},
"childKeys": ["confirm-message", "confirm-buttons"],
"dismissible": true,
"_bindings": {"open": "/showConfirm"}
}
```

## Media Components

### Video

Renders an HTML5 `<video>` element.

| A2UI type | Angular component | Selector |
|-----------|-------------------|----------|
| `Video` | `A2uiVideoComponent` | `a2ui-video` |

| Prop | Type | Description |
|------|------|-------------|
| `url` | `string` | **Required.** Video source URL |
| `poster` | `string` | Poster image URL shown before playback |
| `autoplay` | `boolean` | Start playback automatically. Defaults to `false` |
| `controls` | `boolean` | Show native playback controls. Defaults to `true` |

```json
{
"id": "intro-video",
"component": "Video",
"url": "https://example.com/intro.mp4",
"poster": "https://example.com/poster.jpg",
"controls": true
}
```

### AudioPlayer

Renders an HTML5 `<audio>` element.

| A2UI type | Angular component | Selector |
|-----------|-------------------|----------|
| `AudioPlayer` | `A2uiAudioPlayerComponent` | `a2ui-audio-player` |

| Prop | Type | Description |
|------|------|-------------|
| `url` | `string` | **Required.** Audio source URL |
| `autoplay` | `boolean` | Start playback automatically. Defaults to `false` |
| `controls` | `boolean` | Show native playback controls. Defaults to `true` |

```json
{
"id": "podcast",
"component": "AudioPlayer",
"url": "https://example.com/episode.mp3",
"controls": true
}
```

## Built-in Functions

Props that use `A2uiFunctionCall` can reference these built-in functions:
Expand Down Expand Up @@ -260,6 +431,12 @@ Props that use `A2uiFunctionCall` can reference these built-in functions:
| `TextField` | `A2uiTextFieldComponent` | Interactive |
| `CheckBox` | `A2uiCheckBoxComponent` | Interactive |
| `ChoicePicker` | `A2uiChoicePickerComponent` | Interactive |
| `Tabs` | `A2uiTabsComponent` | Layout |
| `Modal` | `A2uiModalComponent` | Layout |
| `Video` | `A2uiVideoComponent` | Media |
| `AudioPlayer` | `A2uiAudioPlayerComponent` | Media |
| `DateTimeInput` | `A2uiDateTimeInputComponent` | Interactive |
| `Slider` | `A2uiSliderComponent` | Interactive |

## What's Next

Expand Down
30 changes: 21 additions & 9 deletions apps/website/content/docs/chat/a2ui/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A2UI is an open standard for agent-driven user interfaces. It lets an AI agent describe a structured UI — components, layout, and live data — using a simple JSON protocol, and have that UI rendered automatically inside a chat session.

The `ChatComponent` includes built-in A2UI support with no extra configuration. When an agent response begins with the `---a2ui_JSON---` sentinel, the chat switches to A2UI rendering mode automatically.
When an agent response begins with the `---a2ui_JSON---` sentinel, the chat switches to A2UI rendering mode automatically — provided you supply a component catalog via the `views` input.

<Callout type="info" title="Spec version">
The implementation follows the A2UI v0.9 specification. For the full protocol reference, see [a2ui.org](https://a2ui.org).
Expand All @@ -23,7 +23,7 @@ AI response starts with ---a2ui_JSON---

The surface store maintains a `Map<string, A2uiSurface>` keyed by surface ID. Each surface holds a flat component map, a data model, theme metadata, and the catalog ID used to resolve components.

The `ChatComponent` provides the built-in `a2uiBasicCatalog` automatically — you do not need to configure a catalog to start using A2UI.
To render A2UI surfaces, pass a `ViewRegistry` to the `ChatComponent` via the `views` input. The `a2uiBasicCatalog()` function provides the 18 built-in components.

## The Four Message Types

Expand Down Expand Up @@ -77,6 +77,17 @@ Removes a surface from the store and dismisses its rendered output.
{"deleteSurface": {"surfaceId": "order-status"}}
```

## Action-Event Bridge

A2UI actions (button clicks, form events) now map directly to render-spec `on` bindings. When `surfaceToSpec()` converts a surface to a spec, each component's `action` prop is translated into an `on` binding on the corresponding spec element. This means A2UI actions flow through the render-lib event system rather than being handled ad-hoc by individual components.

The bridge provides two default handlers:

- `a2ui:event` -- dispatches named events (e.g., `submit`, `cancel`) back to the agent
- `a2ui:localAction` -- executes local function calls (e.g., `openUrl`)

This unified event flow means consumers can observe all A2UI interactions through the `RenderEvent` stream on `RenderSpecComponent`, including handler execution, state changes, and lifecycle signals. See the [Render Events](/docs/render/events) page for the full event type reference.

## A2UI vs json-render

Both A2UI and json-render produce rendered UIs inside chat messages, but they serve different purposes.
Expand All @@ -93,24 +104,25 @@ Use **A2UI** when the agent needs to build an interface incrementally, bind it t

## Quick Setup

No configuration is required. Add `ChatComponent` to your template and A2UI content is detected and rendered automatically:
Pass `a2uiBasicCatalog()` to the `views` input to enable A2UI rendering:

```typescript
import { ChatComponent } from '@cacheplane/chat';
import { ChatComponent, a2uiBasicCatalog } from '@cacheplane/chat';

@Component({
template: `<cp-chat [stream]="stream" />`,
template: `<chat [ref]="agentRef" [views]="catalog" />`,
imports: [ChatComponent],
})
export class AppComponent {
stream = /* your AI stream */;
catalog = a2uiBasicCatalog();
agentRef = /* your AgentRef */;
}
```

When the AI response starts with `---a2ui_JSON---`, the chat renders the surfaces using the built-in `a2uiBasicCatalog`. No inputs to set, no catalog to register.
When the AI response starts with `---a2ui_JSON---`, the chat renders the surfaces using the provided catalog.

<Callout type="tip" title="Custom catalogs">
To use your own components or extend the built-in set, pass a `ViewRegistry` to the chat. See the [Catalog reference](/docs/chat/a2ui/catalog) for details.
To extend or replace the built-in components, compose catalogs with `withViews()` or `views()`. See the [Custom Catalogs guide](/docs/chat/guides/custom-catalogs) for details.
</Callout>

## What's Next
Expand All @@ -135,7 +147,7 @@ To use your own components or extend the built-in set, pass a `ViewRegistry` to
icon="grid"
href="/docs/chat/a2ui/catalog"
>
Reference for all 12 built-in components and their props.
Reference for all 18 built-in components and their props.
</Card>
<Card
title="Streaming"
Expand Down
18 changes: 15 additions & 3 deletions apps/website/content/docs/chat/a2ui/surface-component.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ import { A2uiSurfaceComponent } from '@cacheplane/chat';
| `surface` | `A2uiSurface` | Yes | The surface to render |
| `catalog` | `ViewRegistry` | Yes | Component registry used to resolve A2UI type names to Angular components |

## Outputs

| Output | Type | Description |
|--------|------|-------------|
| `events` | `RenderEvent` | Emits render events from the underlying `RenderSpecComponent` -- handler execution, state changes, and lifecycle signals |

The `events` output forwards all `RenderEvent` emissions from the render engine. This includes events triggered by the default A2UI handlers (`a2ui:event` and `a2ui:localAction`) as well as any state change or lifecycle events.

## How It Works

`A2uiSurfaceComponent` bridges the A2UI surface model to the json-render engine in three steps:
Expand All @@ -34,11 +42,15 @@ Before the spec is emitted, each component prop is evaluated against the surface
- A function call `{ call: 'formatCurrency', args: { ... } }` — executed by the built-in function registry
- A template string `"Hello ${/name}"` — interpolated with values from `dataModel`

**3. Expand template children**
**3. Map actions to `on` bindings**

`surfaceToSpec()` converts each component's A2UI `action` prop into a render-spec `on` binding on the corresponding element. Event actions map to the `a2ui:event` handler, and function call actions map to the `a2ui:localAction` handler. This bridges the A2UI interaction model to the render-lib event system.

**4. Expand template children**

When a component's `children` field is an `A2uiChildTemplate` (`{ path, componentId }`), the surface component expands it over the array at `path` in the data model. Each array item gets its own cloned element with props resolved in that item's scope.

**4. Render via RenderSpecComponent**
**5. Render via RenderSpecComponent**

The final `Spec` is passed to `RenderSpecComponent` along with the `ViewRegistry` (converted to an Angular registry via `toRenderRegistry`). Rendering is fully reactive — when the surface signal updates, the spec recomputes and only affected elements re-render.

Expand Down Expand Up @@ -111,6 +123,6 @@ Returns `null` when the surface has no `root` component. Otherwise returns a com
icon="grid"
href="/docs/chat/a2ui/catalog"
>
All 12 built-in components and their props.
All 18 built-in components and their props.
</Card>
</CardGroup>
6 changes: 5 additions & 1 deletion apps/website/content/docs/chat/a2ui/surface-store.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ const dashboard = store.surface('dashboard');
// dashboard() is A2uiSurface | undefined
```

## Data Model and Render-Lib State Sync

The surface's `dataModel` is synchronized into the render-lib `StateStore` when `surfaceToSpec()` converts the surface to a spec. The conversion sets `state: surface.dataModel` on the produced `Spec`, which initializes the render-lib's internal `StateStore` with the surface data. When components with `_bindings` update values (e.g., a text field changing), those updates flow through the render-lib `StateStore`, and each mutation emits a `RenderStateChangeEvent` through the render-lib event system. This means consumers observing the `events` output on `A2uiSurfaceComponent` see all data model changes as typed `RenderStateChangeEvent` objects with `path`, `value`, and `snapshot` fields.

## updateDataModel Semantics

The `updateDataModel` message uses JSON Pointer (RFC 6901) paths to address values in the data model.
Expand Down Expand Up @@ -143,6 +147,6 @@ The `components` map is keyed by component ID. The `dataModel` is a plain object
icon="grid"
href="/docs/chat/a2ui/catalog"
>
All 12 built-in components and their props.
All 18 built-in components and their props.
</Card>
</CardGroup>
Loading
Loading