Design Document: A2UI Flutter Unified Architecture (v0.9)
1. Introduction & Motivation
Currently, the Flutter GenUI library processes A2UI streams and manages state using a monolithic, immutable data structure (SurfaceDefinition). Whenever an updateComponents or updateDataModel message arrives, the SurfaceController applies the changes and pushes a completely new SurfaceDefinition. The Surface widget listens to this global change and rebuilds the entire component tree.
As UI complexity grows, this approach has several drawbacks:
- Performance (Lack of Granular Reactivity): Typing in a
TextField updates the Data Model, which triggers a full surface rebuild.
- Coupling: A2UI message parsing, JSON Pointer resolution, and Flutter widget building are tightly entangled.
- Cross-Platform Inconsistency: The A2UI Web Renderers (React, Angular, Lit) all share a unified, framework-agnostic state engine (
@a2ui/web_core). Flutter's implementation is entirely bespoke, making it harder to maintain parity and share catalog logic.
The Solution: Implement the A2UI Unified Architecture. We will split the library into a Platform-Agnostic State Engine (genui_core, pure Dart) and a Framework-Specific Renderer (genui, Flutter).
2. The Great Decoupling (Package Structure)
We will introduce a new package into the monorepo:
packages/genui_core (Pure Dart): Has absolutely zero dependency on package:flutter. It is responsible for parsing A2UI messages, maintaining the live state tree, resolving JSON pointers, and evaluating logic/expressions.
packages/genui (Flutter): Depends on genui_core and package:flutter. It contains the Flutter Surface widget, the BasicCatalog Flutter widgets (Material/Cupertino), and layout delegates.
3. Architectural Changes: Old vs. New
This section maps the current Flutter-centric classes to their new, decoupled counterparts.
3.1. State Management: From Static to Live Models
Currently, SurfaceDefinition acts as a static snapshot of the UI.
- Deprecated:
SurfaceDefinition
- New (
genui_core): SurfaceModel, SurfaceComponentsModel, and ComponentModel.
- Instead of a static map,
SurfaceComponentsModel acts as a live registry.
- Each component is backed by a
ComponentModel which holds its properties and exposes an onUpdated event stream.
- Why? This allows a Flutter widget to subscribe only to its specific
ComponentModel. If a message updates the Button's label, only the Button rebuilds, not the whole surface.
3.2. Data Binding & JSON Pointers
Currently, InMemoryDataModel handles basic path resolution but lacks strict RFC 6901 compliance and advanced array manipulation (auto-vivification).
- Restructured (
genui_core): DataModel
- Will be moved to
genui_core.
- Must implement Auto-vivification: Setting
/a/b/0/c automatically creates nested maps and lists.
- Must implement the v0.9 Bubble & Cascade Notification Strategy: A change to
/user/name notifies listeners of /user/name, /user, /, and /user/name/first (if it existed).
3.3. Message Processing
Currently, SurfaceController.handleMessage manually applies changes to SurfaceDefinition and the DataModel.
- New (
genui_core): MessageProcessor
- A pure Dart class. It takes a stream of
A2uiMessage objects and mutates the SurfaceGroupModel (which holds all SurfaceModels).
- Restructured (
genui): SurfaceController becomes a thin Flutter wrapper around the MessageProcessor, bridging the pure Dart engine to Flutter's lifecycle.
3.4. Widget Rendering & Context
Currently, CatalogItem.widgetBuilder receives a CatalogItemContext containing raw JSON data. Widgets manually extract paths and set up data model listeners.
- Deprecated:
CatalogItemContext
- New (
genui_core): ComponentContext & DataContext
ComponentContext pairs a ComponentModel (the UI config) with a DataContext (the scoped data state).
DataContext handles evaluating expressions (e.g., ${/user/name} or ${formatDate(...)}) and resolving relative paths natively in Dart.
- New (
genui): Flutter widgets will now receive a ComponentContext. They will use utility builders (like a generic binder or updated BoundString) to listen to the exact resolved values coming from the DataContext.
3.5. Recursive Surface Rendering
Currently, the Surface widget wraps itself in a ValueListenableBuilder watching the entire SurfaceDefinition.
- Restructured:
Surface will take a SurfaceModel. It will simply render the component with ID root. The recursive buildChild pipeline will construct the Flutter widget tree. Each widget connects to its own ComponentModel and DataContext, achieving O(1) rebuilds for data/property changes.
4. Key Benefits
- Granular Reactivity: Dramatically improves rendering performance. Changes to data or properties only trigger rebuilds for the specific Flutter widgets listening to them.
- Strict Separation of Concerns: Core A2UI logic (JSON handling, pointers, expression parsing) is completely isolated and can be rigorously unit tested without booting a Flutter engine.
- Portability: The
genui_core package can be used in non-Flutter environments. For example, a pure Dart CLI tool could ingest A2UI streams and print terminal UI, or a backend Dart server could validate A2UI states.
- Ecosystem Consistency: This architecture mirrors the
@a2ui/web_core implementation. As A2UI evolves (and as future iOS/Android native renderers are built), having a shared mental model and architecture across platforms makes maintaining the specification and catalogs exponentially easier.
5. Implementation Phases
The detailed coding agent prompts for these phases are included in the comments below.
- Phase 1: Agnostic Model Layer: Build
genui_core, DataModel, MessageProcessor, and the live component models.
- Phase 2: Context & Expression Layer: Implement
ExpressionParser, DataContext scoping, and core functions (e.g., formatString).
- Phase 3: Flutter Renderer Integration: Refactor
genui to use genui_core, updating the Surface widget and BasicCatalog components for granular reactivity.
Design Document: A2UI Flutter Unified Architecture (v0.9)
1. Introduction & Motivation
Currently, the Flutter GenUI library processes A2UI streams and manages state using a monolithic, immutable data structure (
SurfaceDefinition). Whenever anupdateComponentsorupdateDataModelmessage arrives, theSurfaceControllerapplies the changes and pushes a completely newSurfaceDefinition. TheSurfacewidget listens to this global change and rebuilds the entire component tree.As UI complexity grows, this approach has several drawbacks:
TextFieldupdates the Data Model, which triggers a full surface rebuild.@a2ui/web_core). Flutter's implementation is entirely bespoke, making it harder to maintain parity and share catalog logic.The Solution: Implement the A2UI Unified Architecture. We will split the library into a Platform-Agnostic State Engine (
genui_core, pure Dart) and a Framework-Specific Renderer (genui, Flutter).2. The Great Decoupling (Package Structure)
We will introduce a new package into the monorepo:
packages/genui_core(Pure Dart): Has absolutely zero dependency onpackage:flutter. It is responsible for parsing A2UI messages, maintaining the live state tree, resolving JSON pointers, and evaluating logic/expressions.packages/genui(Flutter): Depends ongenui_coreandpackage:flutter. It contains the FlutterSurfacewidget, theBasicCatalogFlutter widgets (Material/Cupertino), and layout delegates.3. Architectural Changes: Old vs. New
This section maps the current Flutter-centric classes to their new, decoupled counterparts.
3.1. State Management: From Static to Live Models
Currently,
SurfaceDefinitionacts as a static snapshot of the UI.SurfaceDefinitiongenui_core):SurfaceModel,SurfaceComponentsModel, andComponentModel.SurfaceComponentsModelacts as a live registry.ComponentModelwhich holds its properties and exposes anonUpdatedevent stream.ComponentModel. If a message updates theButton's label, only theButtonrebuilds, not the whole surface.3.2. Data Binding & JSON Pointers
Currently,
InMemoryDataModelhandles basic path resolution but lacks strict RFC 6901 compliance and advanced array manipulation (auto-vivification).genui_core):DataModelgenui_core./a/b/0/cautomatically creates nested maps and lists./user/namenotifies listeners of/user/name,/user,/, and/user/name/first(if it existed).3.3. Message Processing
Currently,
SurfaceController.handleMessagemanually applies changes toSurfaceDefinitionand theDataModel.genui_core):MessageProcessorA2uiMessageobjects and mutates theSurfaceGroupModel(which holds allSurfaceModels).genui):SurfaceControllerbecomes a thin Flutter wrapper around theMessageProcessor, bridging the pure Dart engine to Flutter's lifecycle.3.4. Widget Rendering & Context
Currently,
CatalogItem.widgetBuilderreceives aCatalogItemContextcontaining raw JSON data. Widgets manually extract paths and set up data model listeners.CatalogItemContextgenui_core):ComponentContext&DataContextComponentContextpairs aComponentModel(the UI config) with aDataContext(the scoped data state).DataContexthandles evaluating expressions (e.g.,${/user/name}or${formatDate(...)}) and resolving relative paths natively in Dart.genui): Flutter widgets will now receive aComponentContext. They will use utility builders (like a generic binder or updatedBoundString) to listen to the exact resolved values coming from theDataContext.3.5. Recursive Surface Rendering
Currently, the
Surfacewidget wraps itself in aValueListenableBuilderwatching the entireSurfaceDefinition.Surfacewill take aSurfaceModel. It will simply render the component with IDroot. The recursivebuildChildpipeline will construct the Flutter widget tree. Each widget connects to its ownComponentModelandDataContext, achieving O(1) rebuilds for data/property changes.4. Key Benefits
genui_corepackage can be used in non-Flutter environments. For example, a pure Dart CLI tool could ingest A2UI streams and print terminal UI, or a backend Dart server could validate A2UI states.@a2ui/web_coreimplementation. As A2UI evolves (and as future iOS/Android native renderers are built), having a shared mental model and architecture across platforms makes maintaining the specification and catalogs exponentially easier.5. Implementation Phases
The detailed coding agent prompts for these phases are included in the comments below.
genui_core,DataModel,MessageProcessor, and the live component models.ExpressionParser,DataContextscoping, and core functions (e.g.,formatString).genuito usegenui_core, updating theSurfacewidget andBasicCatalogcomponents for granular reactivity.