-
Notifications
You must be signed in to change notification settings - Fork 143
Description
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
TextFieldupdates 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 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, SurfaceDefinition acts as a static snapshot of the UI.
- Deprecated:
SurfaceDefinition - New (
genui_core):SurfaceModel,SurfaceComponentsModel, andComponentModel.- Instead of a static map,
SurfaceComponentsModelacts as a live registry. - Each component is backed by a
ComponentModelwhich holds its properties and exposes anonUpdatedevent stream. - Why? This allows a Flutter widget to subscribe only to its specific
ComponentModel. If a message updates theButton's label, only theButtonrebuilds, not the whole surface.
- Instead of a static map,
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/cautomatically creates nested maps and lists. - Must implement the v0.9 Bubble & Cascade Notification Strategy: A change to
/user/namenotifies listeners of/user/name,/user,/, and/user/name/first(if it existed).
- Will be moved to
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
A2uiMessageobjects and mutates theSurfaceGroupModel(which holds allSurfaceModels).
- A pure Dart class. It takes a stream of
- Restructured (
genui):SurfaceControllerbecomes a thin Flutter wrapper around theMessageProcessor, 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&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.
- New (
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 Surface widget wraps itself in a ValueListenableBuilder watching the entire SurfaceDefinition.
- Restructured:
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
- 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_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. - Ecosystem Consistency: This architecture mirrors the
@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.
- Phase 1: Agnostic Model Layer: Build
genui_core,DataModel,MessageProcessor, and the live component models. - Phase 2: Context & Expression Layer: Implement
ExpressionParser,DataContextscoping, and core functions (e.g.,formatString). - Phase 3: Flutter Renderer Integration: Refactor
genuito usegenui_core, updating theSurfacewidget andBasicCatalogcomponents for granular reactivity.