fix(chat): A2UI surface renderer must route through render-spec (nested children)#371
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…r-spec
The slot-based progressive renderer (introduced alongside surface-store
readiness tracking) mounted root components but never populated their
child-related inputs — leaving Columns/Rows/Modals/etc. with empty
childKeys and rendering as empty containers. Catalog components
declare `childKeys: input<string[]>([])` and `spec: input.required<Spec>()`
because they internally use `<render-element>` for recursive children;
the slot's `pushProps(view.props)` had no knowledge of either.
Worse, `view.props` was stored as `{ TypeKey: { ...inner } }` (wire
format) while `a2ui-slot.pushProps` iterated those entries and called
`setInput("Column", {...})` — which silently failed because no such
input exists. Net effect: ZERO inputs ever made it to the catalog
component, so even `distribution`/`alignment` were unset.
This shipped as a silent regression in v0.0.32 and v0.0.33: any GenUI
surface containing a Column/Row with nested children (i.e. virtually
all real-world A2UI surfaces) rendered empty.
Root cause confirmed via live DOM inspection + Angular's own NG0303
warning ("Can't set value of the 'Column' input"). The legacy
`<render-spec>` path was the original (and correct) flow:
`surfaceToSpec` unwraps the type key, treats `children.explicitList`
/ `child` / `entryPointChild` / `contentChild` / `tabItems` as
reserved keys, and translates them into `spec.elements[id].children`
which `render-element` then maps to the catalog component's
`childKeys` input. The progressive renderer was an aspirational layer
on top of this that was never finished.
Fix: route ALL state-set surfaces through `<render-spec>` too. Extend
`spec()` computed to also build from `state().surface`. The progressive
component-view readiness tracking in surface-store is preserved for
future use, but rendering goes through the proven legacy path. Per-
element readiness still works via `render-element.notReady` (mounts
fallback if any resolved prop is undefined).
Tests:
- New integration test asserts a Column with children.explicitList
renders its child Text leaf using the real a2uiBasicCatalog
(reproduces the contact-form bug; was failing before this commit).
- Surface.component.spec rewritten to exercise the new contract
(fake type / empty Map tests no longer apply — they were testing
slot-path behavior that is now bypassed).
- All 744 chat lib tests pass.
Verified live: the "Demo: render a contact form" suggestion now
renders a fully functional contact form (heading + 4 text fields +
multi-line message + Send button) in the local dev stack.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a7a538a to
d360ac1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a silent regression in v0.0.32 + v0.0.33: any A2UI surface containing a Column/Row/Modal/Tabs with nested children rendered as an empty container. Affects essentially every real-world GenUI prompt (contact forms, settings cards, polls, etc.).
Root cause (verified live + via NG0303 warning)
The slot-based progressive renderer in `A2uiSurfaceComponent` mounted root components via `a2uiSlot`, but its `pushProps(view.props)` had no knowledge of:
Net effect: `setInput("Column", {...})` silently failed (no such input on `A2uiColumnComponent`), `childKeys` stayed empty, `@for` rendered nothing.
Confirmed by:
Fix
Route ALL state-set surfaces through the proven legacy `` path. Extend `spec()` computed to also build from `state().surface` (was only checking the legacy `surface` input). `surfaceToSpec` already correctly:
The slot-based path is dead-coded (template no longer reaches it for state surfaces). The directive + view-readiness tracking stay for back-compat / future use. Per-element readiness still works via `render-element.notReady` (mounts fallback if any resolved prop is undefined).
Verified live
http://localhost:4200/embed → "Demo: render a contact form" → renders:
(Before this fix: empty container, just the tool-call header.)
Test plan
🤖 Generated with Claude Code