Skip to content

feat(v1.0): add rootOnly component metadata to enforce root-level rendering - #2120

Open
wrenj wants to merge 10 commits into
mainfrom
rootonly
Open

feat(v1.0): add rootOnly component metadata to enforce root-level rendering#2120
wrenj wants to merge 10 commits into
mainfrom
rootonly

Conversation

@wrenj

@wrenj wrenj commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

What changed

Introduces an optional rootOnly boolean metadata property to component definitions in the A2UI v1.0 Catalog schema (catalog_definition.json).
When "rootOnly": true, renderers must enforce that instances of the component appear only at the root of a surface and reject payloads that nest the component within another component.

Why this is needed

A2UI v1.0 added support for mixing catalogs, so standalone components (SuggestionBox, Canvas, full-page iframe, ToastMessage) are now mixable with composable widget catalogs (Button, Table, TextBox). A mechanism is needed for catalog components to explicitly opt out of nesting. Since this involves interaction between catalogs using the ChildList in common_types, this can't be solved by individual catalogs themselves and we need a framework wide mechanism. Additionally, since A2UI represents component trees as a flat adjacency list on the wire, standard JSON Schema validation cannot detect when a standalone component is invalidly nested inside another component.

Why this approach

  • Zero wire overhead: Defining rootOnly as static catalog metadata (parallel to callableFrom and returnType for functions) avoids adding validation properties to wire payloads in common_types.json.
  • Targeted scope: A top-level rootOnly boolean cleanly solves the most common structural issue when mixing catalogs. More complex parent-child constraints (eg, MenuItem is the only child of Menu) remain the responsibility of individual component schemas.

Testing

  • Updated specification/v1_0/json/catalog_definition.json, a2ui_protocol.md, and evolution_guide.md.
  • Added schema validation test cases in catalog_root_only_checks.json and updated run_tests.py.

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

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.

Code Review

This pull request introduces a new rootOnly structural validation property for component schemas in the A2UI specification, allowing renderers to prevent standalone components from being nested inside other components. This is documented in the protocol and evolution guides, and implemented in the catalog_definition.json schema. The review feedback suggests wrapping the $ref and local properties in ComponentDefinition within an allOf block to ensure compatibility with older JSON Schema parsers, adding test cases to verify the new validation behavior, and including an example of rootOnly in the documentation's schema template.

Comment thread specification/v1_0/json/catalog_definition.json Outdated
Comment thread specification/v1_0/json/catalog_definition.json Outdated
Comment thread specification/v1_0/docs/a2ui_protocol.md Outdated
@wrenj wrenj changed the title feat(v1.0): Add rootOnly catalog metadata for structural component validation feat(v1.0): add rootOnly component metadata to enforce root-level rendering Jul 30, 2026
@wrenj

wrenj commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

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

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.

Code Review

This pull request introduces a new rootOnly boolean property to component schemas in the A2UI protocol, allowing components to specify that they must only be rendered at the root of a surface. The changes include updates to the protocol documentation, the evolution guide, the JSON schema definition (catalog_definition.json), and the addition of validation tests. The review feedback points out that renaming the example component from Text to ToastMessage in the protocol documentation template breaks an existing reference under "$defs/anyComponent". It is recommended to retain the name Text and demonstrate the rootOnly property on it to keep the template valid.

Comment on lines +553 to 557
"ToastMessage": {
"type": "object",
// Rule 7: Root-only components like "ToastMessage" cannot be nested inside other components.
"rootOnly": true,
// Rule 5: Components must combine ComponentCommon and local properties using "allOf".

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

Renaming the component from Text to ToastMessage in this template breaks the reference under "$defs/anyComponent" (line 627), which still points to #/components/Text. To keep the template valid and consistent without dangling references, please retain the component name as Text and demonstrate the rootOnly property on it.

Suggested change
"ToastMessage": {
"type": "object",
// Rule 7: Root-only components like "ToastMessage" cannot be nested inside other components.
"rootOnly": true,
// Rule 5: Components must combine ComponentCommon and local properties using "allOf".
"Text": {
"type": "object",
// Rule 7: Root-only components like "Text" cannot be nested inside other components.
"rootOnly": true,
// Rule 5: Components must combine ComponentCommon and local properties using "allOf".

Comment on lines 567 to 569
"component": {
"const": "Text",
"const": "ToastMessage",
},

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

To align with keeping the component name as Text to avoid breaking the anyComponent reference, the constant value for the component property should also remain Text.

Suggested change
"component": {
"const": "Text",
"const": "ToastMessage",
},
"component": {
"const": "Text",
},

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