Skip to content

feat(a2ui-toolkit): validate catalog-derived child ref-fields (#1948)#1960

Merged
ranst91 merged 2 commits into
mainfrom
claude/a2ui-1948-ref-fields
Jun 16, 2026
Merged

feat(a2ui-toolkit): validate catalog-derived child ref-fields (#1948)#1960
ranst91 merged 2 commits into
mainfrom
claude/a2ui-1948-ref-fields

Conversation

@ranst91

@ranst91 ranst91 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Closes #1948.

What

#1944 closed the singular-child + cycle gaps, scoped to the child/children fields. This extends both the dangling-ref check and the cycle graph to every child reference a component makes, derived from the catalog rather than a hardcoded field list — covering Modal trigger/content, Tabs tabItems[].child, etc. Kept byte-aligned with the .NET sibling (microsoft/agent-framework#6494).

How

A property is a child reference only when its catalog schema marks it:

  • "format": "componentRef" — single ref
  • "format": "componentRefList" — list ref (array of ids, or a { componentId, path } template)

For an array property whose items is an object schema, marked sub-properties are honoured per element (this is how tabItems[].child is found — derived, never hardcoded).

Why a marker and not shape inference: the validator catalog carries no machine-readable ref marker today, and a ref value ("card") is indistinguishable from a data value ("$289") — both are bare strings. Shape-based detection would flag every non-id data string as a dangling child. So detection is marker-driven; an unmarked property is data, never a reference.

child/children remain implicit references always, so #1944 and the catalog-free structural path are unchanged.

A new collectComponentRefEdges / _collect_component_ref_edges returns (path, ref) pairs consumed by both the dangling check and the cycle adjacency, so a cycle routed through a marked field (e.g. Modal content) is now detected.

Path grammar (byte-aligned across TS / Python / .NET)

ref shape path
single-ref field components[i].<field>
list-ref (array), k-th components[i].<field>[k]
list-ref (single template) components[i].<field>
nested array-of-object components[i].<arrayField>[k].<refField> (and [j] if that sub-field is itself a list)

Error code reuses unresolved_child (field-specific paths); cycle stays child_cycle.

Gating & scope

Extended fields are derived only when a marked catalog is supplied; with no catalog, only child/children are checked — exactly as before. The dojo demos pass no catalog, so there is no dojo behaviour change; this fires for hosts that supply a marked catalog. Enriching catalog producers to emit format markers is downstream follow-up, out of scope here.

Tests

vitest + unittest, byte-aligned: Modal trigger/content dangling, unmarked data string not flagged, nested tabItems[k].child per-index, cycle through a marked field, list-ref array per-index, marked fields ignored without a catalog. All #1944 child/children + deep-chain tests stay green. TS 90/90, Python 89/89, tsc --noEmit clean.

Cross-language parity

.NET ships in microsoft/agent-framework#6494 (xunit 117/117). Emitted paths diffed byte-for-byte across all three toolkits before commit.

#1944 closed the singular-child + cycle gaps for the `child`/`children`
fields. This extends both the dangling-ref check and the cycle graph to every
child reference a component makes, derived from the catalog rather than a
hardcoded field list, matching the .NET sibling (microsoft/agent-framework#6494).

A property is treated as a child reference only when its catalog schema marks it
`"format": "componentRef"` (single) or `"componentRefList"` (list); an array
property whose `items` is an object schema has its marked sub-properties honoured
per element (this is how Tabs `tabItems[].child` is found, derived, never
hardcoded). `child`/`children` stay implicit references always, so the #1944 and
catalog-free structural behaviour is unchanged. An unmarked property is data,
never a reference: a bare data string ("$289") and a bare ref string ("card")
are otherwise indistinguishable, so shape-based detection is unsafe.

A new `collectComponentRefEdges` / `_collect_component_ref_edges` returns
(path, ref) pairs consumed by both the dangling-ref check and the cycle
adjacency, so a cycle routed through a marked field (e.g. Modal `content`) is
now detected. Paths are byte-aligned with the sibling toolkits: single-ref
`components[i].<field>`, list-ref `components[i].<field>[k]`, nested
`components[i].<arrayField>[k].<refField>`. Extended fields are gated on a marked
catalog; with no catalog only `child`/`children` are checked, exactly as before.

Note: hosts must enrich their catalogs to emit the `format` markers for this to
fire; the dojo demos pass no catalog, so there is no dojo behaviour change.
Catalog producers emitting markers is downstream follow-up.

Tests (vitest + unittest): Modal trigger/content dangling, unmarked data string
not flagged, nested tabItems[k].child per-index, cycle through a marked field,
list-ref array per-index, and marked fields ignored without a catalog. All #1944
child/children + deep-chain tests stay green.
@ranst91 ranst91 requested a review from a team as a code owner June 15, 2026 12:08
@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ag-ui-dojo Ready Ready Preview, Comment Jun 16, 2026 9:22am

Request Review

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Python Preview Packages

Version 0.0.0.dev1781601470 published to TestPyPI.

Warning: These packages are built from contributor code that may not yet have been vetted for correctness or security. Install at your own risk and do not use in production.

Install with uv

Add the TestPyPI index to your pyproject.toml:

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = true

Then install the packages you need:

# Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1781601470' --index testpypi

# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1781601470' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1781601470' --index testpypi
# NOTE: ag-ui-agent-spec depends on pyagentspec (git-only, not on PyPI).
# You will need to install pyagentspec separately from its git repo.
uv add 'ag-ui-agent-spec==0.0.0.dev1781601470' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1781601470' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1781601470' --index testpypi

Install with pip

pip install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  ag-ui-protocol==0.0.0.dev1781601470

Use --extra-index-url https://pypi.org/simple/ so pip can resolve
transitive dependencies (pydantic, fastapi, etc.) from real PyPI.


Commit: fedbb37

@pkg-pr-new

pkg-pr-new Bot commented Jun 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

@ag-ui/a2a-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a-middleware@1960

@ag-ui/a2ui-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-middleware@1960

@ag-ui/event-throttle-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/event-throttle-middleware@1960

@ag-ui/mcp-apps-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-apps-middleware@1960

@ag-ui/mcp-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-middleware@1960

@ag-ui/a2a

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a@1960

@ag-ui/adk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk@1960

@ag-ui/ag2

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/ag2@1960

@ag-ui/agno

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/agno@1960

@ag-ui/aws-strands

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/aws-strands@1960

@ag-ui/claude-agent-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-agent-sdk@1960

@ag-ui/crewai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/crewai@1960

@ag-ui/langchain

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langchain@1960

@ag-ui/langgraph

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langgraph@1960

@ag-ui/llamaindex

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/llamaindex@1960

@ag-ui/mastra

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mastra@1960

@ag-ui/pydantic-ai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/pydantic-ai@1960

@ag-ui/vercel-ai-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/vercel-ai-sdk@1960

@ag-ui/watsonx

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/watsonx@1960

@ag-ui/a2ui-toolkit

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-toolkit@1960

create-ag-ui-app

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/create-ag-ui-app@1960

@ag-ui/client

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/client@1960

@ag-ui/core

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/core@1960

@ag-ui/encoder

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/encoder@1960

@ag-ui/proto

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/proto@1960

commit: e67aa33

@ranst91 ranst91 merged commit 28e605a into main Jun 16, 2026
2 of 3 checks passed
@ranst91 ranst91 deleted the claude/a2ui-1948-ref-fields branch June 16, 2026 09:17
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.

a2ui-toolkit: derive child ref-fields from the catalog so Modal/Tabs dangling refs and cycles are validated

2 participants