Skip to content

feat(composer): v0.9 theater, gallery fixes, and renderer improvements#1144

Open
GeneralJerel wants to merge 14 commits intogoogle:mainfrom
GeneralJerel:feat/composer-v09-theater-and-cleanup
Open

feat(composer): v0.9 theater, gallery fixes, and renderer improvements#1144
GeneralJerel wants to merge 14 commits intogoogle:mainfrom
GeneralJerel:feat/composer-v09-theater-and-cleanup

Conversation

@GeneralJerel
Copy link
Copy Markdown

@GeneralJerel GeneralJerel commented Apr 10, 2026

Summary

  • Theater: Update scenarios to v0.9 format, add JSON Pretty tab with syntax coloring, drop v0.8 support
  • Gallery: Fix card overflow/clipping across multiple widgets (financial data grid, recipe card, task card), add full-width containment wrapper for previews
  • Renderer: Fix <caption> hydration error, implement weight property for flex layouts, convert camelCase icon names to snake_case for Material Symbols, add inline markdown rendering for Text, add overflow: hidden to Card, make Tabs buttons flexible
  • Composer: Default to v0.9 across gallery/components/widget creation, replace favicon, rename sidebar nav entry, set v0.9 primary color, add error notifications for storage failures
image image

Supersedes #1134.

Test plan

  • Verify /gallery page renders all cards without overflow or clipping
  • Verify /theater page plays v0.9 scenarios with JSON Pretty tab
  • Verify /components page lists v0.9 components
  • Verify icons render correctly (snake_case conversion)
  • Verify markdown text renders in gallery cards
  • Verify no console hydration errors
  • Verify new widget creation defaults to v0.9

🤖 Generated with Claude Code

- Replace 4 theater scenarios with v0.9 protocol format (createSurface,
  updateComponents, updateDataModel with JSON Pointer paths)
- Add native v0.9 message handling in useA2UISurface (no more transpiler)
- Add JSON Pretty/Wire toggle with syntax coloring in theater Data tab
- Add collapsible chunks for large JSON objects in pretty mode
- Add theme support threaded through v09Viewer and A2UIViewer adapter
- Add storage migration system with version tracking and auto-wipe
- Add widgets context improvements (removeAllWidgets, migration notice)
- Remove all v0.8 support: SpecVersion type, version selector, transcoder,
  viewerTheme, v0.8 gallery data, v0.8 AI prompt, v0.8 component docs,
  and all version-switching conditional branches
- Remove 11 unused theater scenario files
- Simplify sync-gallery script to v0.9-only generation
- Update all tests for v0.9-only Widget type

50 files changed, -12,710 net lines. Build and all 32 tests pass.
Rename "Components" to "Basic Components" in sidebar navigation for
clarity, and set --a2ui-primary-color to Google Red (#d93025) so the
v0.9 renderer picks up a branded accent instead of the default blue.
Card's `width: 100%` combined with its 8px margin caused overflow beyond
the parent container. Image had the same issue inside flex rows. Column
lacked `minWidth: 0`, preventing proper flex shrinking.
- Implement the v0.9 `weight` common property in the React renderer
  adapter, applying flex-grow via a wrapper div when weight is set
- Button: add alignSelf flex-start so it sizes to content instead of
  stretching full-width in a Column
- Image: use calc(100% - 16px) to account for leaf margin and prevent
  overflow into adjacent flex siblings; add border-radius to header variant
- Theater: adjust restaurant-finder weights to 3:2 (image:column) for
  better visual balance
The HTML <caption> element is only valid inside <table>, causing React
hydration errors when used in flex layouts on the gallery page.
…kdown support

Icon: Material Symbols requires snake_case names (calendar_today) but
A2UI protocol uses camelCase (calendarToday). Added conversion in the
Icon component so icons render correctly.

Text: The v0.9 spec states Text supports "simple Markdown formatting"
but it was never implemented. Added lightweight parser for headings,
bold, italic, lists, and links with HTML escaping for security.
Swap the default Next.js favicon.ico for the CopilotKit logo-mark SVG.
Next.js App Router serves icon.svg from src/app/ automatically.
Simplified financial data grid to 3 columns (Asset, Price, Change) with
pre-formatted values to prevent overflow in gallery preview. Reduced
recipe card gallery height from 380 to 300 to avoid clipping.
Wrap A2UIViewer in a full-width container to force block flow layout,
preventing Cards from shrink-wrapping as flex items and overflowing.
Make Tabs buttons flexible with equal distribution and text truncation
so they fit within narrow gallery card containers.
Prevents child content from visually spilling outside the Card border,
which was visible in gallery previews at narrow widths.
@google-cla
Copy link
Copy Markdown

google-cla bot commented Apr 10, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

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 migrates the A2UI composer and React renderers to exclusively support the v0.9 specification, removing all v0.8 legacy code and transpilation logic. Key enhancements include a new markdown parser for the Text component, improved flex layout weighting, and a revamped Theater playground with JSON syntax highlighting. Feedback identifies several technical issues: a regex bug in icon name conversion that prepends underscores to capitalized names, potential SSR crashes in the Theater page due to direct URL access during state initialization, and a markdown list parser that incorrectly discards multi-line items. Additionally, the JSON Pointer implementation should be updated to handle array indices, and the flex-grow wrapper requires minHeight: 0 to prevent overflow in vertical layouts.

if (/^[-*]\s/.test(block)) {
const items = block
.split('\n')
.filter((line) => /^[-*]\s/.test(line))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The .filter() call here will discard any lines within a list block that do not start with a list marker (- or *). This breaks multi-line list items or items containing nested content, as those lines will be silently removed from the rendered output. Consider a more sophisticated line-by-line parser or removing the filter if the intent is to support simple multi-line items.

let current: Record<string, unknown> = obj;
for (let i = 0; i < segments.length - 1; i++) {
const key = segments[i]!;
if (current[key] === undefined || typeof current[key] !== 'object') {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The setAtPath function defaults to creating an object ({}) when a path segment doesn't exist. If the path contains numeric segments intended to represent array indices (e.g., items/0), this will incorrectly create an object with a string key "0" instead of an array. This can cause issues with components like List that expect array data.

- Fix Icon camelCase→snake_case regex to not prepend underscore on
  capitalized names (e.g. "Search" → "search" not "_search")
- Fix Theater SSR hydration mismatch by using static useState defaults
  and restoring URL params in useEffect
- Fix markdown list parser to preserve multi-line continuation lines
- Fix setAtPath to create arrays when path segments are numeric
- Add minHeight: 0 to weight wrapper for vertical flex layouts
GeneralJerel added a commit to GeneralJerel/A2UI that referenced this pull request Apr 11, 2026
- Fix Icon camelCase→snake_case regex to not prepend underscore on
  capitalized names (e.g. "Search" → "search" not "_search")
- Fix Theater SSR hydration mismatch by using static useState defaults
  and restoring URL params in useEffect
- Fix markdown list parser to preserve multi-line continuation lines
- Fix setAtPath to create arrays when path segments are numeric
- Add minHeight: 0 to weight wrapper for vertical flex layouts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@GeneralJerel GeneralJerel force-pushed the feat/composer-v09-theater-and-cleanup branch from f3be746 to 4e12a1d Compare April 11, 2026 00:12
- Stack invitation builder editor/preview vertically instead of
  side-by-side for narrow gallery cards
- Add scale support to gallery widget for wide content
- Add explicit font-size/weight to Text heading variants (h1-h5)
  since Tailwind Preflight resets browser defaults
- Remove markdown '#' from spec since variant already handles styling
Copy link
Copy Markdown
Collaborator

@ditman ditman left a comment

Choose a reason for hiding this comment

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

Please, add an entry to the CHANGELOG of the react renderer describing the changes that happened in this PR! That way we know if we need to release it as a major/minor version.

justifyContent: mapJustify(props.justify),
alignItems: mapAlign(props.align),
width: '100%',
minWidth: 0,
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.

Isn't this the default?

const iconName =
const rawName =
typeof props.name === 'string' ? props.name : (props.name as {path?: string})?.path;
const iconName = rawName?.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
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 had something like this, but there's some icons that don't work. I ended up with:

const ICON_MAP: Record<string, string> = {
  favoriteOff: "favorite_border",
  play: "play_arrow",
  rewind: "fast_rewind",
  starOff: "star_border",
};

// ...

  private getIconName(rawName: string): string {
    if (ICON_MAP[rawName]) return ICON_MAP[rawName];
    return rawName.replace(/[A-Z]/g, (letter: string) => `_${letter.toLowerCase()}`);
  }

(Assuming Material Symbols Outlined font)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

this mustve been affected while i was debugging the issue with some icons not working...

...getBaseLeafStyle(),
objectFit: mapFit(props.fit),
width: '100%',
width: `calc(100% - ${2 * 8}px)`,
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.

What are these 8px? Is this to offset the margin from the base leaf style?

padding: '8px 16px',
flex: '1 1 0',
minWidth: 0,
padding: '8px 8px',
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.

This is the same as padding: 8px

* Parses simple markdown (headings, bold, italic, lists, links) to HTML.
* Supports the subset described in the v0.9 spec.
*/
function parseSimpleMarkdown(text: string): string {
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.

Prefer using the package @a2ui/markdown-it? it has a renderMarkdown function that you should be able to use. End users shoudl be able to inject it as a dependency into the renderer, and then the renderer use it (we can discuss this on chat later!)

"id": "asset-market-cap",
"component": "Text",
"text": {
"call": "formatCurrency",
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.

Why did this example change? It'd be interesting to call the formatCurrency function, right?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Oh, the table was too wide that it was clipping on the gallery view so I thought of ways to make it fit the card. Are all columns required or is this one in particular the highlight of this example?

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 think the coolest part of this example is the formatCurrency call!

* Syncs gallery widget data from the canonical spec examples.
* Syncs gallery widget data from the canonical v0.9 spec examples.
*
* Reads JSON examples from specification/v0_8 and v0_9 catalogs and
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.

Nice! 🟥🟥🟥🟥!

…ata grid

- Add ICON_MAP for icons that don't follow camelCase→snake_case naming
- Add CHANGELOG entries for all renderer fixes in this PR
- Restore original financial-data-grid.json with formatCurrency calls
- Add clarifying comments on Column minWidth and Image width calc
- Simplify Tabs padding, remove unused LEAF_MARGIN import
@GeneralJerel
Copy link
Copy Markdown
Author

Thanks for the thorough review @ditman! Addressed everything in 9fcb685:

CHANGELOG — Added entries for all renderer fixes under Unreleased.

Column.tsx minWidth: 0 — Not the default actually — CSS min-width for flex items defaults to auto, which prevents children from shrinking below their content size. Added an inline comment to clarify.

Icon.tsx — Great call on the ICON_MAP approach. Adopted your map (favoriteOfffavorite_border, playplay_arrow, etc.) with the regex as fallback for standard camelCase names.

Image.tsx calc(100% - 16px) — Correct, it offsets the 8px leaf margin on each side. Added a comment explaining. Happy to explore a cleaner pattern (e.g. moving to padding) as a follow-up.

Tabs.tsx — Fixed, padding: 8 now.

Text.tsx / @a2ui/markdown-it — Noted! Happy to discuss migrating to the shared package in a follow-up. Left the inline parser as-is for now.

Financial data grid — Restored the original example with formatCurrency, icons, symbols, and market cap column. Good point — the function calls are a better demo.

<MemoizedRender props={props || ({} as Props)} buildChild={buildChild} context={context} />
);

// Apply the common `weight` property as flex-grow when present.
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.

The 'weight' property is part of the basic catalog and not part of the overall A2UI specification.

Please implement this within each Component of the basic catalog, rather than in the common React framework bindings. It should be possible for someone to implement a different catalog without weight and not be affected by this logic!

Copy link
Copy Markdown
Author

@GeneralJerel GeneralJerel Apr 11, 2026

Choose a reason for hiding this comment

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

Good call, moved the weight handling out of the adapter entirely in 3bd5c98.

Added a withWeight() helper in the basic catalog's utils and each component now handles its own weight wrapping. The adapter is fully catalog-agnostic now so a custom catalog won't inherit any of the basic catalog's layout logic

let me know if theres a better way of approaching this

…log components

The `weight` property is a basic catalog concern, not a core A2UI framework
concept. Moves the flex-grow wrapper from the shared adapter into each
individual basic catalog component via a `withWeight` utility, so that
custom catalogs are not affected by basic catalog layout logic.
@GeneralJerel GeneralJerel force-pushed the feat/composer-v09-theater-and-cleanup branch from e962972 to 3bd5c98 Compare April 11, 2026 05:44
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.

3 participants