feat(composer): v0.9 theater, gallery fixes, and renderer improvements#1144
feat(composer): v0.9 theater, gallery fixes, and renderer improvements#1144GeneralJerel wants to merge 14 commits intogoogle:mainfrom
Conversation
- 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.
|
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. |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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') { |
There was a problem hiding this comment.
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
- 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>
f3be746 to
4e12a1d
Compare
- 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
ditman
left a comment
There was a problem hiding this comment.
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, |
| 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(); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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)`, |
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
Why did this example change? It'd be interesting to call the formatCurrency function, right?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
…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
|
Thanks for the thorough review @ditman! Addressed everything in CHANGELOG — Added entries for all renderer fixes under Column.tsx Icon.tsx — Great call on the Image.tsx Tabs.tsx — Fixed, Text.tsx / Financial data grid — Restored the original example with |
renderers/react/src/v0_9/adapter.tsx
Outdated
| <MemoizedRender props={props || ({} as Props)} buildChild={buildChild} context={context} /> | ||
| ); | ||
|
|
||
| // Apply the common `weight` property as flex-grow when present. |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
e962972 to
3bd5c98
Compare
Summary
<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, addoverflow: hiddento Card, make Tabs buttons flexibleSupersedes #1134.
Test plan
/gallerypage renders all cards without overflow or clipping/theaterpage plays v0.9 scenarios with JSON Pretty tab/componentspage lists v0.9 components🤖 Generated with Claude Code