Skip to content

Composer: Replace Lit renderer with React, add v0.9 support#992

Open
lukasmoschitz wants to merge 14 commits intogoogle:mainfrom
CopilotKit:composer-to-v0.9
Open

Composer: Replace Lit renderer with React, add v0.9 support#992
lukasmoschitz wants to merge 14 commits intogoogle:mainfrom
CopilotKit:composer-to-v0.9

Conversation

@lukasmoschitz
Copy link
Copy Markdown
Contributor

Summary

Swaps the Lit-based renderer in the composer for @a2ui/react and adds v0.9 support with a version switcher in the sidebar.

Renderer swap

The composer previously used @copilotkit/a2ui-renderer which wrapped Lit web components in Shadow DOM. This PR replaces it with the native @a2ui/react renderer (Light DOM). The production viewer-theme is ported over, and the necessary CSS palettes are provided in globals.css.

v0.9 rendering

A V09Viewer wrapper creates a SurfaceModel via MessageProcessor and renders through A2uiSurface. The adapter layer (a2ui.tsx) is the single point that interprets component format — everything else treats components as opaque A2UIComponent[] arrays.

Version switching

A global context (persisted to localStorage) controls which version the composer uses. The sidebar has a v0.8/v0.9 toggle that affects:

  • Gallery — separate widget sets per version (29 each, in v08/ and v09/ subdirectories)
  • Components — version-specific docs, property names, and live previews
  • Icons — renders with the selected version's renderer
  • Create — new widgets use the selected version's format and AI agent
  • Editor — always uses the widget's own specVersion, isolated from the global toggle. Version badge shown in header.

AI integration

Two separate CopilotKit agents on the server, each with only their version's actual JSON spec from the specification/ directory. The client selects the agent based on specVersion. The editor chat uses the widget's stored version to pick the right agent.

Known v0.9 renderer limitations

These are in @a2ui/react v0.9 basic catalog, not fixable in the composer:

  • Text caption variant renders as <caption> HTML element (hydration warning)
  • ChoicePicker crashes when options are data-bound (preview disabled)
  • TextField not interactive in static preview mode
  • Icon component needs a .material-symbols-outlined CSS class definition (added in globals.css)

Test plan

  • v0.8 gallery styling matches production
  • v0.9 gallery renders all 29 widgets
  • Version toggle switches all pages correctly
  • Create + edit flow works in both v0.8 and v0.9
  • Widgets keep their version regardless of global toggle
  • Preview error boundary catches invalid JSON
  • CI green

- Swap dependency from @copilotkit/a2ui-renderer to @a2ui/react@^0.8.0
- Add adapter layer (src/lib/a2ui.tsx) as single import point for all
  renderer consumers, enabling future v0.8/v0.9 version switching
- Add composerTheme extending litTheme with additionalStyles to fix
  button text color (--n-10/--n-35 remap to white inside buttons)
- Add A2UI color palettes (--p-*, --n-*, --s-*) in globals.css to
  replace the palette that Lit's themed-a2ui-surface provided via
  Shadow DOM
- Fix nested <button> hydration error in gallery-widget by changing
  outer wrapper from <button> to <div role="button">
- Update all 9 import sites to use adapter instead of package directly
…ng margins

- Add viewerTheme.ts: exact port of the production viewer-theme used by
  A2UIViewer on a2ui-composer.ag-ui.com (from private_a2ui_demo repo)
- Add appTheme.ts: the app-level theme used for chat view (for future use)
- Switch adapter to use viewerTheme instead of composerTheme
- Fix card background in gallery: define --a2ui-card-bg at :root level
  and reference it via --p-100 so the gallery wrapper can override it
  to transparent (matching Lit's themed-a2ui-surface behavior)
- Fix heading margins: add layout-m-0 to markdown.h4 and markdown.h5
  to prevent browser default margins on h4/h5 elements
- Add specVersion field ('0.8' | '0.9') to Widget type
- Add all existing gallery widgets as specVersion: '0.8'
- Create V09Viewer wrapper that builds SurfaceModel from v0.9
  component definitions and renders via A2uiSurface
- Update adapter (a2ui.tsx) to switch between v0.8 A2UIViewer
  and v0.9 V09Viewer based on specVersion prop
- Pass specVersion through preview-pane and gallery-widget
- Switch @a2ui/react to local file dependency for v0.9 exports
- Add @a2ui/web_core dependency for MessageProcessor
- Add SpecVersionContext with localStorage persistence
- Add VersionSelector toggle (v0.8/v0.9) in sidebar
- Reorganize gallery data into v08/ and v09/ subdirectories
- Gallery page switches widget set based on selected version
- Fix v0.8 gallery data: action string shorthand → object format,
  invalid 'baseline' alignment → 'center'
- Add v0.9 Contact Card sample widget
- Fix SSR error: dynamically import V09Viewer (client-only)
- Pass specVersion through widget-preview-modal
Convert all v0.8 gallery widgets to v0.9 format:
- Flat component structure (component: 'TypeName' discriminator)
- Property renames (usageHint→variant, alignment→align, distribution→justify)
- Value unwrapping (literalString→string, explicitList→array)
- Action format (action: { event: { name, context } })
- Native JSON data (no ValueMap arrays)

All samples render via V09Viewer → MessageProcessor → A2uiSurface.
Gallery switches between v0.8 and v0.9 sets via the sidebar toggle.
- Create page uses specVersion context for default components and
  widget creation (v0.8 or v0.9 format based on sidebar toggle)
- AI prompt rewritten to document both v0.8 and v0.9 formats
- User messages prefixed with [A2UI v0.8] or [A2UI v0.9] tag so
  the AI generates the correct component format
- Start Blank also respects the selected version
…n-agnostic

Two agents:
- v08 agent with actual v0.8 catalog spec from specification/v0_8/
- v09 agent with actual v0.9 catalog spec + rules from specification/v0_9/
- Client selects agent via useAgent({ agentId }) based on specVersion
- Editor chat uses widget.specVersion to pick the right agent
- No version mixing possible — each agent only knows its format

Version-agnostic component type:
- Replace v0.8-specific ComponentInstance with A2UIComponent
  (Record<string, unknown> & { id: string }) in Widget type
- Editor, preview, create all use the agnostic type
- Only the adapter interprets component structure for routing
- Remove as any[] casts from v0.9 gallery widgets

Also:
- Editor header shows version badge (v0.8/v0.9)
- Delete old combined a2ui-prompt.ts
- Components page switches docs/previews between v0.8 and v0.9
  based on global spec version toggle
- Add components-data-v09.ts with v0.9 property names, usage
  examples, and preview components for all 16 component types
- Add error boundary around preview pane — invalid components
  show fallback instead of crashing the editor
- Remove unused composerTheme.ts and appTheme.ts
- Disable ChoicePicker preview (requires live data binding)
- Icons page renders with correct renderer based on spec version toggle
- Add .material-symbols-outlined CSS class in globals.css — the v0.9
  Icon component uses this class but Google Fonts only provides
  @font-face rules, not the class definition
- Includes overflow: hidden to prevent icon name text from overflowing
  during font load
- TextField: rename 'text' prop to 'value', fix variant enum
  (remove 'date', add 'obscured', default 'shortText')
- ChoicePicker: fix variant enum to 'multipleSelection'/'mutuallyExclusive',
  add displayStyle and filterable props
- Image: fix 'scale-down' to 'scaleDown' in fit enum
- Row/Column: add 'stretch' to justify enum
- Button: add 'default' to variant enum
- List: add missing 'align' prop
- DateTimeInput: add missing label, min, max props
- Slider: add missing label prop, add default for min
- Divider: remove non-spec color/thickness props
- Add variant: 'primary' to main CTA buttons in 9 v0.9 gallery widgets
- Change default spec version to v0.9
- Move version selector above navigation in sidebar
- Fix music player: replace non-existent ProgressBar with Slider
  (max: 262, values in seconds)
- Add web_core and react renderer build steps before composer install
- Add renderer paths to CI trigger so composer rebuilds when
  renderers change (local file dependency)
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for A2UI protocol version 0.9 alongside the existing 0.8 version. It adds a global spec version context to manage the active version across the composer, updates the renderer adapter to handle both versions, and includes new gallery widgets for v0.9. My feedback highlights a potential hydration mismatch in the version context, suggests improving type safety for default components, and recommends refining the accessibility attributes for the gallery widget.

Copy link
Copy Markdown
Collaborator

@jacobsimionato jacobsimionato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey thanks so much! I tried this locally and it worked really well!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love it if we could deduplicate this data with the JSON files here (which I actually pulled from your code!) https://github.com/google/A2UI/tree/main/specification/v0_8/json/catalogs/basic/examples

In other examples, we have used some tricky imports and build scripts to pull from the central location e.g. https://github.com/google/A2UI/blob/main/renderers/react/a2ui_explorer/src/examples.ts

This is non-blocking though, let's merge this a deduplicate as a 'nice to have'.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also we can deduplicate v0.9 with https://github.com/google/A2UI/tree/main/specification/v0_9/json/catalogs/basic/examples

This is slightly higher priority perhaps, because I've updated these examples to use v0.9-specific functionality and will continue to add them probably!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, especially since you're actively updating the spec examples. Let's tackle this in a follow-up PR so the gallery stays in sync.

@jacobsimionato
Copy link
Copy Markdown
Collaborator

Can you please update the open source license headers then ask someone at Google to merge? Thank you!!

  - Fix hydration mismatch: use useEffect + isLoaded flag to defer
    version-dependent rendering until client has loaded localStorage
  - Fix type: replace any[] with A2UIComponent[] for v0.9 defaults
  - Fix accessibility: make role/tabIndex conditional on onClick
  - Add license header to pnpm-lock.yaml for CI check
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

2 participants