Skip to content
This repository was archived by the owner on Jul 13, 2026. It is now read-only.

Rewrite component/ to match A2UI v0.9 schema (1/2) - #1

Open
joestump wants to merge 1 commit into
mainfrom
fix/a2ui-schema
Open

Rewrite component/ to match A2UI v0.9 schema (1/2)#1
joestump wants to merge 1 commit into
mainfrom
fix/a2ui-schema

Conversation

@joestump

Copy link
Copy Markdown
Owner

Summary

Rewrites the component/ package against the actual A2UI v0.9 basic catalog. Addresses REVIEW.md issue 2a — the initial scaffold's "kind"-discriminated, invented-catalog shape would never route a real A2UI document.

Spec verified against:

Wire shape (v0.9, flat-string): discriminator is "component" (not "kind"), values are PascalCase, children are referenced by ID via "child" (single) or "children" (array). Components are delivered as a flat adjacency list keyed by ID; component.Unmarshal decodes a single entry — the renderer assembles the tree.

Catalog implemented

Matches the 9 types in the brief, drawn from the v0.9 basic catalog:

Type Notes
Card child (single ID)
Button child (single ID), variant, action (raw JSON, open shape)
Text text is a Value (literal / path / call binding), variant
Row children (ID array or template), justify, align
Column same shape as Row
TextField label, value (Value), textFieldType, validationRegexp
Modal entryPointChild + contentChild
Tabs tabItems[] of {title, child}
List children (ID array or template), direction, align

All nine support accessibility (raw JSON) and weight.

Notable types

  • Value — distinguishes literal ("John Doe"), path binding ({"path": "/contact/firstName"}), and function-call binding ({"call": "...", "args": {...}}); preserves raw JSON for lossless round-trips.
  • ChildrenSpec — accepts both static ["id1", "id2"] and template object {"path": "/employees", "componentId": "employee_card_template"}.
  • Action / Accessibility — kept as json.RawMessage (open shapes — let renderers/event routers decide).
  • Per-type MarshalJSON re-emits the "component" discriminator so json.Marshal(card) produces a spec-shaped object.

Errors

No more silent _ = json.Unmarshal(...) swallowing. Unmarshal returns:

  • ErrMissingDiscriminator when "component" is absent
  • ErrUnknownComponent (wraps the offending value) for unknown types
  • A typed fmt.Errorf wrapping the inner decode error for malformed payloads (e.g. Row.children: 42 → "decode Row: …")

Tests

component/component_test.go — table-driven, snippets sourced from the A2UI docs (URLs cited inline):

  • One test per catalog entry, including both literal and path-binding Text, both static-array and template-object List
  • Unknown component type → ErrUnknownComponent
  • Missing discriminator → ErrMissingDiscriminator
  • Malformed JSON (empty, trailing garbage, unclosed object, discriminator not a string)
  • Malformed payload that passes the discriminator check (e.g. Row.children: 42)
  • Nested round-trip: a Card → Column → two Buttons → two Text labels decoded from a 6-entry adjacency list, asserting IDs line up
  • Kind-constant smoke test pinning each Kind* constant to the spec's PascalCase value

go test ./component/... passes; go vet ./component/... clean; gofmt -l component/ examples/ clean.

sample.json

examples/hello/sample.json is now a real v0.9 updateComponents envelope (Card → Column with greeting Text, subtitle Text, and a Row of OK/Cancel Buttons with Text labels). 11 lines of components inside a 13-line file.

KNOWN: this PR alone breaks go build ./...

This is expected and called out by the brief. render/* (and a small amount of a2tea.go) still reference the old component names. Specifically, go build ./... from main of this branch fails with:

render/choice.go:14:14: undefined: component.Choice
render/form.go:14:14: undefined: component.Form
render/input.go:14:14: undefined: component.Input
render/markdown.go:14:14: undefined: component.Markdown
render/progress.go:14:14: undefined: component.Progress
render/stream.go:14:14: undefined: component.Stream

This is Agent 2's job in PR #2 (stacked on fix/a2ui-schema). PR #2 will rebuild render/* against the new catalog, fix the a2tea.Render entry-point (REVIEW.md issue 2c), wire huh/v2 (REVIEW.md issue 2b), and restore a green full-tree build.

Scope kept clean: this PR only touches component/component.go, component/component_test.go, and examples/hello/sample.json.

Test plan

  • go test ./component/... passes
  • go vet ./component/... clean
  • gofmt -l component/ examples/ clean
  • cd component && go build . succeeds
  • Confirmed go build ./... fails only on the expected render/* references (Agent 2)

Do not merge before PR #2 lands — the tree won't build.

Refs REVIEW.md issue 2a.

The initial scaffold modeled components with a "kind" discriminator and an
invented catalog (card/form/input/choice/progress/markdown/stream) — none of
that matches the A2UI protocol. A real A2UI document would never route to a
renderer.

This rewrites component/ against the A2UI v0.9 basic catalog, using the
spec's actual wire shape and component types.

Wire shape: flat-string discriminator on the field "component" (not "kind"),
PascalCase values, children referenced by ID via "child" (single) or
"children" (array). Per the v0.9 spec, components arrive as a flat
adjacency list keyed by ID; component.Unmarshal decodes a single entry.

Catalog: Card, Button, Text, Row, Column, TextField, Modal, Tabs, List.

Notable types:
- Value distinguishes literal / path-binding / function-call forms, preserves
  raw JSON for lossless round-trips
- ChildrenSpec accepts both static []string and template-object forms (per
  the v0.9 List/Row/Column spec)
- Action and Accessibility kept as json.RawMessage — open shapes we don't
  want to constrain prematurely
- Per-type MarshalJSON re-emits the "component" discriminator

Errors are surfaced: malformed JSON, unknown component type, and missing
discriminator each return a typed error (ErrUnknownComponent,
ErrMissingDiscriminator). No more silent _ = json.Unmarshal swallowing.

Tests: table-driven coverage for every catalog entry, error cases, and a
nested round-trip (Card -> Column -> Buttons -> Text labels) that exercises
the flat adjacency-list pattern. Snippets are sourced from
a2ui.org/specification/v0.9-a2ui/ and a2ui.org/reference/components/.

examples/hello/sample.json is now a real v0.9 document (updateComponents
envelope with a Card containing a Column of Text + Row of Buttons).

This PR alone breaks `go build ./...` — render/* still references the old
component names (Form, Input, Choice, Progress, Markdown, Stream). Fixing
render/* and the a2tea.Render entry-point is Agent 2's job in a stacked
follow-up PR.

Refs REVIEW.md (issue 2a).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant