Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Worktrees
.worktrees/
.superpowers/
.claude/worktrees/

# Node
node_modules/
Expand Down
2 changes: 1 addition & 1 deletion apps/website/content/docs/chat/a2ui/catalog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ Renders a button that dispatches an action when clicked.
| `variant` | `'primary' \| 'borderless'` | Visual style. Defaults to `'primary'` |
| `disabled` | `boolean` | Disables the button when `true` |
| `action` | `A2uiAction` | Action to execute on click (event or function call) |
| `checks` | `A2uiCheck[]` | Validation checks — button is disabled if any fail |
| `validationResult` | `A2uiValidationResult` | Pre-computed validation result — button is disabled if `valid` is `false` |
| `emit` | injected | Event emitter provided by the render engine |

**Action types:**
Expand Down
102 changes: 102 additions & 0 deletions apps/website/content/docs/chat/a2ui/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,108 @@ If no consumer handler matches the `call` name, built-in handlers are used as fa

Handler return values are emitted on the `RenderHandlerEvent` — observe them via the `renderEvent` output on `ChatComponent`.

## Validation

A2UI v0.9 uses `CheckRule` objects for client-side validation. Input components and buttons can define a `checks` array — each check has a `condition` (a `DynamicBoolean`) and an error `message`.

### CheckRule Shape

```json
{
"checks": [
{
"condition": { "call": "required", "args": { "value": { "path": "/name" } } },
"message": "Name is required"
}
]
}
```

The `condition` can be:
- A **boolean literal**: `true` or `false`
- A **path reference**: `{ "path": "/agreed" }` — resolves to a data model value
- A **FunctionCall**: `{ "call": "required", "args": { ... } }` — invokes a named function
- A **composite**: `{ "call": "and", "args": { "values": [...] } }` — combines multiple conditions

### Built-in Functions

| Category | Functions |
|----------|-----------|
| Validation | `required`, `regex`, `length`, `numeric`, `email` |
| Logic | `and`, `or`, `not` |
| Formatting | `formatString`, `formatNumber`, `formatCurrency`, `formatDate`, `pluralize` |
| Navigation | `openUrl` |

### Input Component Behavior

Input components (`TextField`, `CheckBox`, `ChoicePicker`, `Slider`, `DateTimeInput`) validate continuously — errors display inline as the user interacts. The input border changes color to indicate validation state.

### Button Behavior

Per the v0.9 spec: if any check fails, the button is automatically disabled. Error messages display below the button.

### Composite Conditions

Use `and`, `or`, and `not` to compose complex validation rules:

```json
{
"condition": {
"call": "and",
"args": {
"values": [
{ "call": "required", "args": { "value": { "path": "/name" } } },
{ "call": "or", "args": { "values": [
{ "call": "required", "args": { "value": { "path": "/email" } } },
{ "call": "required", "args": { "value": { "path": "/phone" } } }
]}}
]
}
},
"message": "Name required, plus email or phone"
}
```

### Custom Catalog Components

Custom catalog components receive a pre-computed `validationResult` prop:

```typescript
interface A2uiValidationResult {
valid: boolean;
errors: string[];
}
```

Use the shared `A2uiValidationErrorsComponent` for consistent error display:

```typescript
import { A2uiValidationErrorsComponent } from '@cacheplane/chat';

@Component({
imports: [A2uiValidationErrorsComponent],
template: `
<input [value]="value()" />
<a2ui-validation-errors [result]="validationResult()" />
`,
})
export class MyCustomInputComponent {
readonly value = input('');
readonly validationResult = input<A2uiValidationResult>({ valid: true, errors: [] });
}
```

### Theming

Validation styling uses CSS custom properties:

| Property | Default | Usage |
|----------|---------|-------|
| `--a2ui-error` | `#ef4444` | Error text and invalid border color |
| `--a2ui-border` | `rgba(255,255,255,0.1)` | Default input border |
| `--a2ui-input-bg` | `rgba(255,255,255,0.05)` | Input background |
| `--a2ui-label` | `rgba(255,255,255,0.6)` | Label text color |

## What's Next

<CardGroup cols={2}>
Expand Down
22 changes: 20 additions & 2 deletions cockpit/chat/a2ui/python/src/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,20 @@
]},
{"id": "name_field", "component": "TextField",
"label": "Name", "value": {"path": "/name"}, "placeholder": "Your full name",
"_bindings": {"value": "/name"}},
"_bindings": {"value": "/name"},
"checks": [
{"condition": {"call": "required", "args": {"value": {"path": "/name"}}},
"message": "Name is required"},
]},
{"id": "email_field", "component": "TextField",
"label": "Email", "value": {"path": "/email"}, "placeholder": "you@company.com",
"_bindings": {"value": "/email"}},
"_bindings": {"value": "/email"},
"checks": [
{"condition": {"call": "required", "args": {"value": {"path": "/email"}}},
"message": "Email is required"},
{"condition": {"call": "email", "args": {"value": {"path": "/email"}}},
"message": "Must be a valid email address"},
]},
{"id": "dept_picker", "component": "ChoicePicker",
"label": "Department",
"options": ["Engineering", "Sales", "Support", "Marketing"],
Expand All @@ -38,6 +48,14 @@
{"id": "divider", "component": "Divider"},
{"id": "submit_btn", "component": "Button",
"label": "Submit",
"checks": [
{"condition": {"call": "and", "args": {"values": [
{"call": "required", "args": {"value": {"path": "/name"}}},
{"call": "email", "args": {"value": {"path": "/email"}}},
{"path": "/consent"},
]}},
"message": "Complete all required fields and agree to be contacted"},
],
"action": {"event": {"name": "formSubmit", "context": {"formId": "contact"}}}},
]}),
])
Expand Down
Loading
Loading