Skip to content

Commit 12e978b

Browse files
bloveclaude
andauthored
refactor(a2ui): core quality pass — type safety, code org, tests, DX (#105)
* docs: add A2UI quality pass design spec and implementation plan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(a2ui): add isPathRef and isFunctionCall type guards Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(chat): extract surfaceToSpec to dedicated file with UIElement types Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(chat): extract buildA2uiActionMessage to dedicated file Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(chat): extract shared emitBinding utility for catalog input components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(chat): add unit tests for A2UI input catalog components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(chat): add unit tests for A2UI display and complex catalog components Add @analogjs/vite-plugin-angular and tsconfig.spec.json to enable the Angular compiler in the vitest environment so that signal inputs work with setInput. This was required for all catalog component tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(chat): rewrite catalog tests without TestBed, revert vite config changes The angular() vite plugin caused module resolution failures in the chat lib test environment. Reverted to original vite config and rewrote all catalog component tests to test behavioral logic directly via emitBinding utility rather than requiring Angular template compilation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(chat): expand public API with catalog components and A2UI type re-exports Consumers can now import individual catalog components for custom catalog composition via withViews, and access core A2UI types directly from @cacheplane/chat without needing to import @cacheplane/a2ui. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(a2ui): add data model bindings section and validationResult to prop tables - New "Data Model Bindings" section in overview.mdx explaining the binding mechanism, emitBinding utility, and known limitations - Added validationResult prop to all input component prop tables in catalog.mdx Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 27d3d92 commit 12e978b

32 files changed

Lines changed: 2361 additions & 261 deletions

apps/website/content/docs/render/a2ui/catalog.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ A single-line text input with optional label and placeholder.
185185
| `label` | `string` | Input label |
186186
| `value` | `string` | Current value (bind via `_bindings`) |
187187
| `placeholder` | `string` | Placeholder text |
188+
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below input when invalid |
188189
| `_bindings` | `Record<string, string>` | Bind `value` to a data model path |
189190
| `emit` | injected | Event emitter provided by the render engine |
190191
@@ -210,6 +211,7 @@ A labeled checkbox with two-way binding for its checked state.
210211
|------|------|-------------|
211212
| `label` | `string` | Checkbox label |
212213
| `checked` | `boolean` | Current checked state (bind via `_bindings`) |
214+
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below checkbox when invalid |
213215
| `_bindings` | `Record<string, string>` | Bind `checked` to a data model path |
214216
| `emit` | injected | Event emitter provided by the render engine |
215217
@@ -226,6 +228,7 @@ A dropdown select control with a list of string options.
226228
| `label` | `string` | Select label |
227229
| `options` | `string[]` | List of available options |
228230
| `selected` | `string` | Currently selected value (bind via `_bindings`) |
231+
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below dropdown when invalid |
229232
| `_bindings` | `Record<string, string>` | Bind `selected` to a data model path |
230233
| `emit` | injected | Event emitter provided by the render engine |
231234
@@ -244,6 +247,7 @@ A date, time, or datetime input with two-way binding.
244247
| `inputType` | `'date' \| 'time' \| 'datetime-local'` | HTML input type. Defaults to `'date'` |
245248
| `min` | `string` | Minimum allowed value |
246249
| `max` | `string` | Maximum allowed value |
250+
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below input when invalid |
247251
| `_bindings` | `Record<string, string>` | Bind `value` to a data model path |
248252
| `emit` | injected | Event emitter provided by the render engine |
249253
@@ -273,6 +277,7 @@ A range slider input with two-way binding.
273277
| `min` | `number` | Minimum value |
274278
| `max` | `number` | Maximum value |
275279
| `step` | `number` | Step increment |
280+
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below slider when invalid |
276281
| `_bindings` | `Record<string, string>` | Bind `value` to a data model path |
277282
| `emit` | injected | Event emitter provided by the render engine |
278283

apps/website/content/docs/render/a2ui/overview.mdx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,42 @@ The data model is only sent with event actions — there are no passive change n
394394
/>
395395
```
396396

397+
## Data Model Bindings
398+
399+
When the agent sets component properties using path references (`{ "path": "/name" }`), the surface component
400+
tracks these as **bindings** — a mapping from prop name to JSON Pointer path. These bindings are passed to
401+
catalog components as the `_bindings` prop.
402+
403+
### How Bindings Work
404+
405+
1. **Agent sends components** with path references: `{ "value": { "path": "/form/name" } }`
406+
2. **`surfaceToSpec`** resolves the path to a current value AND records the binding in `_bindings`
407+
3. **Catalog component** reads the resolved value normally. When the user changes the value, it emits an `a2ui:datamodel` event via the `emit` callback
408+
4. **The event format** is `a2ui:datamodel:{path}:{value}`
409+
410+
### Using `emitBinding`
411+
412+
Custom catalog components can use the `emitBinding` utility for consistent binding emission:
413+
414+
```typescript
415+
import { emitBinding } from '@cacheplane/chat';
416+
417+
// In your component's change handler:
418+
onInput(event: Event): void {
419+
const val = (event.target as HTMLInputElement).value;
420+
emitBinding(this.emit(), this._bindings(), 'value', val);
421+
}
422+
```
423+
424+
### Known Limitations
425+
426+
The current binding mechanism is client-side only — the `a2ui:datamodel` events are emitted
427+
but do not yet flow through the render lib's `StateStore`. Data model updates from user input
428+
are not reflected back to other components in real time. Full `StateStore` integration is planned
429+
for a future release.
430+
431+
Data model state is refreshed when the agent sends an `updateDataModel` message.
432+
397433
## What's Next
398434

399435
<CardGroup cols={2}>

0 commit comments

Comments
 (0)