--- ## A2UIView Renders a trusted-catalog A2UI (Agent-to-UI, v0.9.1 `basic` subset) payload into live, interactive Urbicon components. Fail-loud and whitelist-only: only the mapped catalog components and their declared props ever reach the DOM — unknown components/props, prototype-pollution keys and function-call bindings are rejected, never rendered, and surfaced through `onValidationError` (spec-compatible issues a consumer can relay to the agent as a `VALIDATION_FAILED` error). Inputs are two-way (typing writes into the local data model; bound text updates live); actions dispatch a spec-exact `A2uiActionEvent`. Images and links are gated by the same strict-by-default `urlPolicy` as StreamingMarkdown. It is deliberately NOT a default ChatMessage renderer — wire it in per surface via `partRenderers.a2ui` to keep it out of the base bundle. Generate the agent-side prompt with the shipped `a2uiSystemPrompt()`; validate a payload without a DOM with `createA2uiProcessor()` — never hand-roll either. Opt into the richer Urbicon-native catalog (real intents/variants, Section, RichText, Accordion) by passing `catalogs={[urbiconA2uiCatalog]}` (tree-shaken out otherwise), and type-check the data model with an optional `dataSchema`. One view owns the surfaces of ONE payload; to let an agent patch a surface it sent in an earlier chat turn (multi-step forms), route the later envelopes into that payload with `A2uiSurfaceRouter` — never give a second view the same surfaceId. **Import:** `import { A2UIView } from '@urbicon-ui/blocks';` ### Examples ```svelte {#snippet a2uiPart(part)} sendUserTurn(`[ui-action] ${JSON.stringify(event)}`)} onValidationError={(issues) => reportToAgent(issues)} /> {/snippet} ``` ```svelte console.log(e)} /> ``` ### Api | Prop | Type | Required | Default | Description | | --- | --- | :---: | --- | --- | | payload | `unknown` | yes | | The A2UI payload. Accepts an array of envelopes (the accumulated JSONL sequence — stream by extending it immutably: `[...prev, envelope]`), a single envelope object, or the golden-file `{ messages: [...] }` wrapper. | | ...HTMLAttributes | `HTMLAttributes` | no | | HTML attributes (excluding: 'class') | | blockedImageLabel | `string` | no | 'Image blocked' | Chip label shown in place of a policy-blocked image. | | catalogs | `readonly A2uiCatalog[]` | no | | Additional A2UI catalogs this view can render, beyond the always-present Basic catalog (which is prepended automatically as the default/fallback). A surface renders through the catalog whose id its `createSurface.catalogId` names. Pass the shipped `urbiconA2uiCatalog` to enable the Urbicon-native catalog. Resolved once at init (icon setup reads context) — keep it referentially stable. | | class | `string` | no | | Extra classes merged onto the root element. | | dataSchema | `A2uiDataSchema` | no | | Optional surface data schema. When set, every `updateDataModel` write is validated against it (type mismatch on a declared pointer → error; undeclared top-level branch → warning), reported via `onValidationError`. Document the same schema to the agent with `a2uiDataSchemaSection`. Keep it referentially stable. | | errorTitle | `string` | no | 'Invalid UI payload' | Title of the top-level error Alert for envelope-level faults. | | onAction | `(event: A2uiActionEvent) => void` | no | | Fired when a Button is activated, with the spec-exact resolved action event. | | onValidationError | `(issues: A2uiValidationIssue[]) => void` | no | | Fired whenever the validation-issue list changes (errors AND warnings, each with a `severity`). Relay error-severity issues to the agent as an A2UI `error` message. | | pendingLabel | `string` | no | 'Loading UI' | Screen-reader label of the streaming placeholder. | | preset | `string` | no | | Apply a named preset registered via ``. Prefer this over `class` overrides for reusable custom looks. | | slotClasses | `Partial>` | no | | Per-slot class overrides. Slots: `root`, `surface`, `errorList`, `errorChip`, `errorIcon`, `pending`, `column`, `row`, `list`, `listItem`, `heading`, `caption`, `inlineText`, `image`, `blockedChip`, `icon`, `svgIcon`, `choiceGroup`, `choiceLabel`. | | streaming | `boolean` | no | false | While true, a reference to a not-yet-defined component renders a placeholder instead of a fault chip (mid-stream tolerance). Flip to false when the stream settles so dangling references become errors. | | unstyled | `boolean` | no | | Strip the component's default tv() classes. | | unsupportedLabel | `string` | no | 'Unsupported component' | Fault-chip label for an unknown/unsupported/incomplete component. | | urlPolicy | `MarkdownUrlPolicy` | no | | URL policy for `Image` sources (and Text markdown links). Strict by default: every external image is blocked unless its prefix is allowlisted. Keep the object referentially stable. | Inherited from: - Omit, 'class'> (omit-pattern) ### Types ```ts interface MarkdownUrlPolicy { /** * Allowed link protocols (with trailing colon). Relative URLs are always * allowed for links. @default ['http:', 'https:', 'mailto:', 'tel:'] */ allowedLinkProtocols?: string[]; /** * Prefix allowlist for image sources, matched against the normalized * absolute URL. Empty (the default) blocks every external image; relative * sources are allowed. Prefer deep, specific prefixes — broad ones are * open-redirect bait. `data:` images are blocked unless a `data:` prefix * is listed explicitly. * @default [] */ allowedImagePrefixes?: string[]; /** Observability hook — fires once per blocked URL occurrence. */ onBlocked?: (kind: 'link' | 'image', url: string) => void; } ``` ```ts interface A2uiActionEvent { name: string; surfaceId: string; sourceComponentId: string; /** ISO 8601 timestamp (`new Date().toISOString()`). */ timestamp: string; context: Record; /** * The surface's complete data model — present only when the surface was * created with `sendDataModel: true`. * * The spec calls this the client data model and has the client append it to * the metadata of every message sent to the agent, but leaves WHERE metadata * travels to the transport binding (A2A `metadata`, HTTP headers, …). This * field is that payload; put it wherever your transport carries metadata. * * It is the antidote to a half-filled `context`: the agent sees everything the * user entered, including fields it forgot to list on the action. */ dataModel?: unknown; } ``` ```ts interface A2uiValidationIssue { severity: A2uiIssueSeverity; code: string; surfaceId?: string; path?: string; message: string; } ``` ```ts interface A2uiCatalog { Node: Component; createIcons: () => { icons: Readonly>; fallbackIcon: IconComponent; }; } ``` ```ts type A2uiDataSchema = Readonly> ``` ```ts type A2uiIssueSeverity = 'error' | 'warning' ``` ```ts type IconComponent = Component ``` ```ts interface A2uiSchemaField { type: A2uiSchemaType; /** Agent-facing description; emitted into the prompt verbatim. */ description?: string; /** Allowed literal values (documented + enforced for string/number fields). */ enum?: readonly (string | number)[]; /** A format hint (e.g. `date`, `time`, `email`) — documented, not enforced in v1. */ format?: string; } ``` ```ts type SlotNames = keyof ReturnType & string ``` ```ts interface A2uiRenderNode { key: string; id: string; instance: A2uiComponentInstance | null; /** Template-item scope for relative-path resolution and two-way write-back. */ scopePrefix: string | undefined; children: A2uiRenderNode[]; /** flex-grow, present only when this node is a direct Row/Column child with a finite `weight`. */ weight?: number; /** * The parent prop this child came from (e.g. Card `header`/`footer`/`child`). * Lets a dispatcher with several named childId slots pick the right child by * name instead of position; single-slot / list components ignore it. */ slot?: string; } ``` ```ts interface A2uiRenderContext { /** Resolved class string per slot (tv() + slotClasses + unstyled already applied). */ classes: Readonly>; urlPolicy: MarkdownUrlPolicy | undefined; /** Dangling refs render placeholders while true; fault chips once false. */ streaming: boolean; /** True inside a Button label (Text renders as an inline plain span). */ inline: boolean; surfaceId: string; onAction: ((event: A2uiActionEvent) => void) | undefined; /** * The data model to attach to a dispatched action, or `undefined` when the * surface did not ask for it (`createSurface.sendDataModel`). Read as a * function so the action carries the state at CLICK time, not at render time. */ actionDataModel: (() => unknown) | undefined; /** Resolve a Dynamic value (literal | { path } | function-call) against the model in `scope`. */ resolve: ( value: unknown, scopePrefix: string | undefined ) => { value: unknown; issue?: A2uiValidationIssue }; /** Write a value at an absolute JSON Pointer into the surface model, then bump. */ write: (pointer: string, value: unknown) => void; /** Delete the key at an absolute JSON Pointer from the surface model, then bump. */ remove: (pointer: string) => void; /** A2UI icon-name → resolved icon component (IconProvider override honoured, direct import fallback). */ icons: Readonly>; /** Fallback glyph for an unmapped icon name. */ fallbackIcon: IconComponent; labels: A2uiRenderLabels; /** * The catalog's recursive node dispatcher (Basic → `A2UINode`; a custom * catalog → its own dispatcher). A2UIView's `renderNode` snippet renders * THIS rather than importing one dispatcher directly, so each surface renders * through the component that matches its catalog. */ nodeComponent: Component; } ``` ```ts type A2uiSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' ```