Skip to content

feat(a2ui,chat): honor beginRendering.styles per A2UI v1 spec (Pass 2a)#233

Merged
blove merged 2 commits into
mainfrom
claude/a2ui-honor-begin-rendering-styles
May 10, 2026
Merged

feat(a2ui,chat): honor beginRendering.styles per A2UI v1 spec (Pass 2a)#233
blove merged 2 commits into
mainfrom
claude/a2ui-honor-begin-rendering-styles

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 10, 2026

Summary

Closes a v1-spec compliance gap: the canonical A2UI v1 wire format defines exactly two theming knobs on `beginRendering.styles` — `font` (primary font family) and `primaryColor` (hex `#RRGGBB`). The type system declared the field but no consumer read it — agents could emit `styles: { primaryColor: "#FF0000" }` and the renderer silently dropped it.

This PR is Pass 2a of the A2UI theming track: ship spec compliance first, then layer on the richer internal token system (Pass 2b) that's the renderer's private vocabulary.

Changes

  1. `libs/a2ui` types — `A2uiSurface` gains an optional `styles?: { font?, primaryColor? }` field mirroring the wire shape, the renderer's single source of truth for v1-spec-defined theming on a given surface.
  2. `libs/chat` surface-store — `beginRendering.styles` is captured onto the new field at commit time. Re-renders preserve existing styles when the new `beginRendering` omits them (matches the agent's likely "change the data, keep the look" intent).
  3. `` host — applies `surface.styles.primaryColor` as `--a2ui-primary` and `surface.styles.font` as `font-family` via host bindings. `null` when unset so consumer-set `:root` defaults aren't overridden. Catalog components already consume `var(--a2ui-primary)` for accents (buttons, sliders, focus, etc.); font cascades naturally.

Why split from Pass 2b

The richer token surface (spacing/typography/shape/focus/motion/elevation/derived-colors) is the renderer's private vocabulary — explicitly NOT communicated through the wire format. The agent shouldn't know about it. Pass 2a is small, high-value (closes the spec gap), and unblocks live testing of agent-driven theming. Pass 2b refactors the catalog to consume an expanded internal token set; Pass 3 ships preset CSS files (Material flavor) consumers `@import`.

Test plan

  • `nx run-many -t test,lint,build -p a2ui,chat` — all green
  • 3 new unit tests cover capture, omission, and preservation across re-renders

Pending live verification

  • After merge: agent-emitted `primaryColor` propagates to the rendered surface — try a prompt like "render a feedback form with a green primary color" and verify the Submit button picks up the green.

🤖 Generated with Claude Code

The canonical A2UI v1 wire format defines exactly two theming knobs on
beginRendering.styles: `font` (primary font family) and `primaryColor`
(hex `#RRGGBB`). The lib's type system already declared the field but
no consumer read it — agents could emit `styles: { primaryColor:
"#FF0000" }` and the renderer silently dropped it.

Three coordinated changes for v1 spec compliance:

1. **`libs/a2ui` types** — `A2uiSurface` gains an optional `styles?:
   { font?, primaryColor? }` field mirroring the wire shape, used as
   the renderer's single source of truth for v1-spec-defined theming
   on a given surface.

2. **`libs/chat` surface-store** — beginRendering's `styles` are
   captured onto the new `A2uiSurface.styles` field at commit time.
   Re-renders preserve existing styles when the new beginRendering
   omits them (matches the agent's likely "change the data, keep the
   look" intent).

3. **`<a2ui-surface>` host** — applies `surface.styles.primaryColor`
   as `--a2ui-primary` and `surface.styles.font` as `font-family` via
   host bindings. `null` when unset so consumer-set `:root` defaults
   are not overridden. Catalog components already consume
   `var(--a2ui-primary)` for accents (buttons, sliders, focus, etc.);
   font cascades naturally.

This is the spec-compliance half of the theming track (Pass 2a). The
internal token system that powers the rest of the catalog's visual
quality (spacing scale, typography scale, shape radius, focus ring,
motion, elevation) remains the renderer's private vocabulary —
explicitly NOT communicated through the wire format. That richer
token surface is Pass 2b.

Three new unit tests cover: capture from beginRendering, omission when
unset, and preservation across re-renders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 10, 2026 3:06pm

Request Review

@blove blove merged commit 2f0c6a9 into main May 10, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant