Skip to content

feat(a2ui_flutter): add the v0.9 basic catalog - #3088

Merged
andrewkolos merged 22 commits into
mainfrom
feat/flutter-adapter-basic-catalog
Oct 15, 2026
Merged

andrewkolos merged 22 commits into
mainfrom
feat/flutter-adapter-basic-catalog

Conversation

@andrewkolos

Copy link
Copy Markdown
Collaborator

Adds the v0.9 basic catalog to a2ui_flutter.

Stuff in this PR

  • package:a2ui_flutter/basic_catalog.dart: all 18 components, built from Material widgets, with the functions from a2ui_core's BasicCatalog.v0_9. Use basicCatalog() as is, or put BasicComponents and your own components in a catalog of your own.
  • The basic components use the surface theme's primaryColor as the Material theme's primary color, and keep the app's other colors.
  • openUrl fails unless the app passes a callback. A bound openUrl call runs when it resolves, not only on a tap, so pass one only for an agent you trust.
  • A2uiMarkdown: wrap a surface in it to render Text as Markdown with a package of your choice.
  • a2ui_core adds BasicCatalog.v0_9Api(), which returns the v0.9 basic catalog document the core already embeds. The components read their schemas from it instead of copying them.

Of the 8.5k added lines, lib/ is ~2.5k and the tests ~5.8k.

Design notes

  • Modal is built on OverlayPortal rather than showDialog, so its content stays under the surface's inherited widgets and closes when the Modal goes away.
  • In a Row with a bounded width, a child without a weight gets at most an equal share of the width. Icon, Button, CheckBox and icon or avatar Images keep their own width.

Known differences from the blueprint

  • No gallery app yet. The spec examples are rendered in tests instead. The first goal was the agent, core and Flutter working together on v0.9, which #PR3's chat example shows. A gallery needs only the public API, so it can follow without changes here.
  • Basic components look up the surface in the widget tree, to apply its theme and to report a write the data model rejects to onError. Section 2 keeps the basic catalog apart from the surface, but both need the surface model, and they reach it through the ambient context the blueprint has the surface publish, as web_core's basic components do. They import neither the surface widget nor the node view.

Known gaps

  • accessibility attributes are not applied yet.
  • Row, Column, List and Tabs use LayoutBuilder, so IntrinsicWidth and IntrinsicHeight cannot measure them. In an AlertDialog, give the surface a fixed width or use a Dialog.
  • The package depends on video_player, so every app that uses it links that plugin. We made the decision to include this consciously in the past, but it's worth revisiting IMO.
  • A literal min or max on DateTimeInput is rejected: the schema tells date, time and date-time apart only by format, which a2ui_core's validator does not check.

Pre-launch Checklist

One time:

For this PR:

  • I have updated the relevant CHANGELOG.md file.
  • I updated/added relevant documentation.
  • My code changes (if any) have tests.
  • If my branch is on a fork, I have verified that scripts/e2e_test.sh passes.

A2uiSurface renders a SurfaceModel through a NodeResolver, and NodeView
renders each ComponentNode through its catalog entry, rebuilding when the
node's props change. ComponentImplementation pairs a component schema with
a widget builder, WidgetCatalog is a catalog of them, and ComponentProps
reads a node's resolved properties, bindings and actions. A component
that has not arrived shows a small progress indicator.
BasicCatalog.v0_9Api() returns the published v0.9 basic catalog document
parsed with Catalog.fromJson: its components, function signatures and theme
schema, so a framework adapter can read the component schemas without
embedding its own copy of the document.

@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 implements the A2UI v0.9 basic catalog in Flutter using Material widgets, introducing a comprehensive set of basic components (such as Button, CheckBox, ChoicePicker, DateTimeInput, Modal, and Video) along with their respective tests and documentation. The code review identified several important issues to address: a compilation error in ChoicePicker due to missing required parameters in RadioListTile, a potential crash in MediaPlayer when disposing of the controller synchronously, unhandled key events in Modal, a missing empty check before accessing _opened.last, and the need for more robust exception handling when parsing SVG paths in Icon.

package:a2ui_flutter/basic_catalog.dart implements the 18 components of the
v0.9 basic catalog with Material widgets, with their schemas from
a2ui_core's BasicCatalog.v0_9Api() and the functions of
BasicCatalog.v0_9(). basicCatalog() builds the whole catalog under the
basic catalog's id; BasicComponents holds each component for a catalog of
the app's own. The components apply the surface theme's primaryColor as
the primary color, and every specification example renders in a test.
@andrewkolos
andrewkolos marked this pull request as ready for review October 8, 2026 20:25
@andrewkolos
andrewkolos force-pushed the feat/flutter-adapter-basic-catalog branch from 5d9cb3e to aa0ad11 Compare October 8, 2026 22:15
/// returns whether the data model accepted it, or null when the payload gave
/// [key] no path. A write the data model rejects is reported on the
/// surface's `onError` with the rejection's code, message and path.
bool Function(Object? value)? writerFor(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should be within the core or framework adapter layers, rather than in the catalog? I guess different catalogs might want different error handling? But our SDKs should provide the most obvious approaches out of the box.

themeSchema: BasicComponents.api.themeSchema,
schemaId: BasicComponents.api.schemaId,
title: BasicComponents.api.title,
description: BasicComponents.api.description,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also forward protocolVersion and instructions from BasicComponents.api here to match BasicCatalog.v0_9() in a2ui_core?

@diegolopezrm

Copy link
Copy Markdown
Contributor

I ran the cases I fixed in genui against this catalog. Two didn't carry over: a component's accessibility label and description are dropped (a Button with accessibility.label still just says "Go"), and Slider's label isn't on the slider node, so it reads "4, 40%" with "Volume" as a separate node. genui has both in flutter/genui#1035 and flutter/genui#1042. Happy to send them as a follow-up once this lands.

Also, sliders over a whole range snap to integers, but v0.9.1 has no steps (#2737). Is that on purpose?

…r-core

# Conflicts:
#	blueprints/modules/a2ui_framework_adapter.blueprint.md
A component whose catalogId changes is recreated with the same instance
id, like one whose type changes. Keying the subtree by the type name kept
the old implementation's state when only the catalog changed. Keying by
the resolved implementation resets it in both cases.
Pairs it with CatalogApi in a2ui_core, as ComponentImplementation and
FunctionImplementation pair with ComponentApi and FunctionApi.

🤖 Agent-generated
…iption

NodeView wraps every resolved component in a Semantics that carries the
component's accessibility label as its label and its description as its
hint, so basic and custom components both get them. Without either
attribute the Semantics adds no node, and it stays in the tree so that
adding one later keeps the component's state.

🤖 Agent-generated
@diegolopezrm

Copy link
Copy Markdown
Contributor

Thanks for adding effca52, the label and description come through now. The README still lists accessibility attributes under "Not supported yet" (line 209), so that line can go.

Once this merges I'll send the Slider label fix, plus a small one for Image: when it fails to load, the broken-image icon drops the description, so a screen reader gets nothing.

I've also been running genui_gen's generated components on this branch (bindings, write-back, checks, templates) and they all work.

Base automatically changed from feat/flutter-adapter-core to main October 14, 2026 22:17
#3194 renamed A2uiClientAction and A2uiClientError to A2uiRendererAction
and A2uiRendererError, MessageProcessor.groupModel to model and
getClientDataModel to getRendererDataModel, and replaced
EventSource.addListener with subscribe. The adapter's tests and README
still used the old names, so the package's tests no longer compiled on
main.

⚠️ Agent-generated
…r-basic-catalog

Main now has the adapter core (#3084, squash-merged) and a2ui_core's
generated basic catalogs (#2998, #3129) and renderer renames (#3194).

- Drop this branch's a2ui_core change, BasicCatalog.v0_9Api(): main's
  BasicCatalog.v0_9() now carries the components, so the basic catalog
  reads its schemas from there.
- Pass the catalog's shared definitions along with its components. The
  generated component schemas reference #/$defs/CatalogComponentCommon,
  which a catalog without them cannot resolve. The README's custom
  catalog sample needs the same line.
- Adopt the #3194 renames in write_back.dart, the test harness and the
  README.
- Read Icon.name from the allOf envelope in icon_test.

⚠️ Agent-generated
#3205 standardized LICENSE files on http://www.apache.org/licenses/ and
made the license check enforce it. a2ui_flutter's LICENSE, added in
#3084, used https, so the static-checks job fails on main.

⚠️ Agent-generated
…r-basic-catalog

Brings in #3223 (the merged renames and LICENSE fix) and #3192
(a2ui_core 0.3.0). The README conflict is the basic catalog's Material
note next to lines #3223 left unchanged. Keep the note.

⚠️ Agent-generated
…bspec

The package now depends on a2ui_core ^0.3.0 (#3192), but the install
sample still showed ^0.2.2.

⚠️ Agent-generated
…list

NodeView applies a component's accessibility label and description, the
two attributes v0.9 defines, since effca52.

⚠️ Agent-generated
@andrewkolos

Copy link
Copy Markdown
Collaborator Author

Thanks for adding effca52, the label and description come through now. The README still lists accessibility attributes under "Not supported yet" (line 209), so that line can go.

Thanks for pointing this out :)
Removed in d855ffd.

Once this merges I'll send the Slider label fix, plus a small one for Image: when it fails to load, the broken-image icon drops the description, so a screen reader gets nothing.

Both fixes would be welcome. Feel free to tag me on those if you do make them.

Also, sliders over a whole range snap to integers, but v0.9.1 has no steps (#2737). Is that on purpose?

Yes. A range that is a whole number from 2 to 1000 moves in steps of 1, so the common 0 to 100 slider writes integers instead of values like 12.345. Any other range, including the 0 to 1 case from #2737, moves continuously. It goes beyond v0.9.1, which says nothing about snapping, but it avoids both of the problems described by #2737. Once this library supports v1.0, the slider snaps only when steps is set.

@andrewkolos
andrewkolos merged commit 60e8294 into main Oct 15, 2026
37 checks passed
@andrewkolos
andrewkolos deleted the feat/flutter-adapter-basic-catalog branch October 15, 2026 00:39
@github-project-automation github-project-automation Bot moved this from Todo to Done in A2UI Oct 15, 2026
diegolopezrm added a commit to diegolopezrm/genui_gen that referenced this pull request Nov 11, 2026
genui is being redesigned as a2ui_flutter, and this moves the runtime
across. a2ui_core resolves a component against its schema before the
builder runs, so the helpers generated code needed to bind values, run
actions, build children and evaluate checks on genui are gone. What is
left is what a2ui_core does not do:

- `GenUiSchemas`, the shapes a2ui_core resolves by, with each property's
  own description, plus `accessibility` and `weight` on every component
  and no other property, as on a basic component.
- `genUiWriter`, which writes through the binding of a property and
  reports a write the data model rejects on the surface, and is null for
  a literal, which a basic control also leaves read-only.
- `genUiMissingIn`, which reports a required field missing from bound
  data once per node, as a validation failure naming its path.
- `GenUiClientFunction` on `FunctionImplementation`, whose async and
  streaming forms keep one answer per set of arguments, so a2ui_core's
  re-evaluation does not start the call over.

a2ui_flutter and the a2ui_core it needs are not published yet, so the
workspace overrides both with a checkout of a2ui-project/a2ui#3088.

The inspector, the tracing and the testing tools (fuzz, coverage, example
surfaces, surface graph) still use genui and do not build yet.
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.

3 participants