# A2UI Surfaces

Render agent-generated UI with your own components: fold A2UI messages into surfaces, render them as forms, stream updates that keep their state, turn a submission into the spec's action, and run the catalog's functions and checks on the server and in the browser.

A2UI is the declarative generative-UI format: an agent emits a flat component list against a catalog the client owns, and the client renders it with its own components. Poetry ships both halves. The catalog: `Poetry::Agent::A2UI::Catalog.from_registry(Poetry::Ui.root)` projects the component registry into an A2UI v1.0 catalog document (`to_h` for a JSON endpoint, `inline` for a middleware) - this site serves it at /a2ui/catalog.json - and the spec's basic catalog is mapped onto the library too (Text as a Markdown subset, Row / Column / List as flex, Card, Tabs, Modal as Dialog, Button, TextField as Field + Input, CheckBox, ChoicePicker as a radio group / checkboxes / a Combobox when filterable, Slider, DateTimeInput, Image, Icon, Video, AudioPlayer, Divider as Separator). The renderer: `Session.new` folds the envelope (`createSurface`, `updateComponents`, `updateDataModel`, `deleteSurface`; `apply(message)` returns the changed surface ids, `session.errors` the renderer-to-agent messages to send back - a dangling child reference is not one, streaming delivers children later); `Renderer.new(surface, view: view_context, action_url:).call` renders a surface as a form whose bound inputs are named `a2ui[values][<pointer>]` and whose agent actions are submit buttons (no `action_url` renders a plain container; `renderer.warnings` lists what could not render - an agent's mistake never raises); `Streams.new(session:, container:, render:)` delivers every change as a Turbo Stream - append on first appearance, a morphing versioned replace after, remove on delete - for an `ActionController::Live` action behind a `<turbo-stream-source>`.

Actions: the endpoint calls `session.action(surface_id:, source:, values:)` with the submitted `a2ui` params; a valid action carries the spec's `action` message (`to_h`) and its AG-UI placement (`forwarded_props`, with the data model when the surface asked for `sendDataModel`), an invalid one carries `errors` by component key - re-render the surface with `errors:` and answer 422 as a Turbo Stream. Checks run twice: `Surface#failures` on the server (the failures render under their controls) and the `poetry--agent--a2ui-surface` controller in the browser as the user types (a button whose own checks fail is disabled, a failing input turns invalid and its error slot fills; unknown functions pass in the browser and are judged by the server). Functions: `Functions.basic` implements the basic catalog's set - `formatString` interpolation (`${/path}`, `${name(arg: value)}`, nested blocks), `formatNumber`, `formatCurrency`, `formatDate` (TR35 patterns), `pluralize`, the validators `required` / `regex` / `length` / `numeric` / `email`, `and` / `or` / `not`, `openUrl` - and `@index` inside templates; `Functions.basic.define(name, description:, returns:, params:, required:, callers:) { |args, evaluator| ... }` adds your own, and a function declared `callers: "rendererOrAgent"` answers an agent's `callRendererFunction` (`session.responses`). State survives updates: every rendered component carries a render-stable key, replaces morph, and the runtime's guard keeps tab selection, dialog state, expanded popups, and edited controls; pass `morph: false` to swap instead. Transports: over AG-UI the messages arrive as `a2ui-surface` activities (`session.apply_activity(content)`); over HTTP they are whatever your agent returns; over MCP they sit inside an `application/a2ui+json` resource - extract and `apply_all`. Setup: `gem "poetry-agent"` and `registerPoetryAgent(application)`.
