diff --git a/.agents/skills/a2ui-sdk-design/SKILL.md b/.agents/skills/a2ui-sdk-design/SKILL.md index 2c3796eec9..c93d4b9e3d 100644 --- a/.agents/skills/a2ui-sdk-design/SKILL.md +++ b/.agents/skills/a2ui-sdk-design/SKILL.md @@ -20,4 +20,5 @@ For the targeted version, analyze these core files to inform your design, typica 1. **JSON Schemas (`json/*.json`)**: Absolute authority for message format compliance. Check `server_to_client.json` for stream envelopes, `client_to_server.json` for action events, and `common_types.json` for dynamic binding primitives and other schema `$defs`. 2. **Component & Function Catalogs (`catalogs//catalog.json`)**: Authoritative definitions of supported visual components and registered evaluation/validation functions. 3. **Protocol Semantics (`docs/a2ui_protocol.md`)**: Semantic foundation covering message stream structures, pointer scopes, and two-way binding agreements. -4. **SDK APIs and architecture design (`docs/renderer_guide.md`)**: Required mechanics for state layer separation, reactive models, and component subscription lifecycles to prevent memory leaks. +4. **Core SDK Specification (`docs/core_sdk_spec.md`)**: Agnostic state management, reactive models, JSON processing, and validation rules. +5. **Framework Adapter Specification (`docs/framework_adapter_spec.md`)**: Component rendering strategy, framework-specific lifecycle adapters, and memory leak prevention. diff --git a/specification/v0_9_1/docs/renderer_guide.md b/specification/v0_9_1/docs/core_sdk_spec.md similarity index 55% rename from specification/v0_9_1/docs/renderer_guide.md rename to specification/v0_9_1/docs/core_sdk_spec.md index 86d5f0ed0f..e9845f6ca2 100644 --- a/specification/v0_9_1/docs/renderer_guide.md +++ b/specification/v0_9_1/docs/core_sdk_spec.md @@ -1,53 +1,31 @@ -# Unified Architecture & Implementation Guide +# A2UI Core SDK Specification -This document describes the architecture of an A2UI client implementation. The design separates concerns into distinct layers to maximize code reuse, ensure memory safety, and provide a streamlined developer experience when adding custom components. +This document describes the detailed programmatic specification and architecture of the A2UI Core SDK. The Core SDK serves as the foundational data, state, and processing layer of A2UI. -Both the core data structures and the rendering components interact with **Catalogs**. Within a catalog, the implementation follows a structured split: from the pure **Component Schema** down to the **Framework-Specific Adapter** that paints the pixels. +This layer handles JSON parsing, state models, JSON pointers, catalogs, and schemas. This logic remains completely framework-agnostic, allowing it to be implemented identically across all target environments (including server-side or headless languages where there is no renderer). -## 1. Unified Architecture Overview +For a high-level overview of the entire A2UI ecosystem (including the Inference SDK and Framework Adapter structure), see the [A2UI Unified SDK Architecture](sdks_spec.md). For UI framework integration and rendering details, see the [A2UI Framework Adapter Specification](framework_adapter_spec.md). -The A2UI client architecture has a well-defined data flow that bridges language-agnostic data structures with native UI frameworks. - -1. **A2UI Messages** arrive from the server (JSON). -2. The **`MessageProcessor`** parses these and updates the **`SurfaceModel`** (Agnostic State). -3. The **`Surface`** (Framework Entry View) listens to the `SurfaceModel` and begins rendering. -4. The `Surface` instantiates and renders individual **`ComponentImplementation`** nodes to build the UI tree. - -This establishes a fundamental split: - -- **The Framework-Agnostic Layer (Data Layer)**: Handles JSON parsing, state management, JSON pointers, and schemas. This logic is identical across all UI frameworks within a given language. -- **The Framework-Specific Layer (View Layer)**: Handles turning the structured state into actual pixels (React Nodes, Flutter Widgets, iOS Views). - -### Implementation Topologies - -Because A2UI spans multiple languages and UI paradigms, the strictness and location of these architectural boundaries will vary depending on the target ecosystem. - -#### Dynamic Languages (e.g., TypeScript / JavaScript) - -In highly dynamic ecosystems like the web, the architecture is typically split across multiple packages to maximize code reuse across diverse UI frameworks (React, Angular, Vue, Lit). - -- **Core Library (`web_core`)**: Implements the Core Data Layer, Component Schemas, and a Generic Binder Layer. Because TS/JS has powerful runtime reflection, the core library can provide a generic binder that automatically handles all data binding without framework-specific code. -- **Framework Library (`react_renderer`, `angular_renderer`)**: Implements the Framework-Specific Adapters and the actual view implementations (the React `Button`, `Text`, etc.). - -#### Static Languages (e.g., Kotlin, Swift, Dart) +--- -In statically typed languages (and AOT-compiled languages like Dart), runtime reflection is often limited or discouraged for performance reasons. +## 1. Core SDK Role & Architecture -- **Core Library (e.g., `kotlin_core`)**: Implements the Core Data Layer and Component Schemas. The core library typically provides a manually implemented **Binder Layer** for the standard Basic Catalog components. This ensures that even in static environments, basic components have a standardized, framework-agnostic reactive state definition. -- **Code Generation (Future/Optional)**: While the core library starts with manual binders, it may eventually offer Code Generation (e.g., KSP, Swift Macros) to automate the creation of Binders for custom components. -- **Custom Components**: In the absence of code generation, developers implementing new, ad-hoc components typically utilize a **"Binderless" Implementation** flow, which allows for direct binding to the data model without intermediate boilerplate. -- **Framework Library (e.g., `compose_renderer`)**: Uses the predefined Binders to connect to native UI state and implements the actual visual components. +The A2UI Core SDK acts as the central state coordinator. It is designed to represent core concepts and behaviors described in the A2UI specification, without any UI rendering logic. -#### Combined Core + Framework Libraries (e.g., Swift + SwiftUI) +Its core responsibilities include: -In ecosystems dominated by a single UI framework (like iOS with SwiftUI), developers often build a single, unified library rather than splitting Core and Framework into separate packages. +1. **Catalog Representation:** Define `Catalog` structures and pure technical component metadata/schemas (`ComponentApi`, `FunctionApi`). +2. **Protocol Definitions:** Model strongly-typed inbound and outbound message structures (e.g., `ClientToServer`, `ServerToClient`, etc.). +3. **Surface State Containers:** Track mutable, long-lived rendering states via `SurfaceModel`, `ComponentModel`, and `DataModel`. +4. **Message Processor:** Parse inbound message sequences to mutate local state containers via `MessageProcessor`. +5. **JSON Pointer Scope:** Standardize relative pointer evaluation and reactivity via scoped context managers (`ComponentContext`, `DataContext`). +6. **Validation:** Throw strict schema and reference-resolution errors. -- **Relaxed Boundaries**: The strict separation between Core and Framework libraries can be relaxed. The generic `ComponentContext` and the framework-specific adapter logic are often tightly integrated. -- **Why Keep the Binder Layer?**: Even in a combined library, defining the intermediate Binder Layer remains highly recommended. It standardizes how A2UI data resolves into reactive state. This allows developers adopting the library to easily write alternative implementations of well-known components without having to rewrite the complex, boilerplate-heavy A2UI data subscription logic. +--- -## 2. The Core Interfaces +## 2. The Core SDK Interfaces -At the heart of the A2UI architecture are five key interfaces that connect the data to the screen. +At the heart of the A2UI architecture are key interfaces that connect the data to the screen. ### `ComponentApi` @@ -62,57 +40,16 @@ interface ComponentApi { } ``` -### `ComponentImplementation` - -The framework-specific logic for rendering a component. It extends `ComponentApi` to include a `build` or `render` method. - -How this looks depends on the target framework's paradigm: - -**Functional / Reactive Frameworks (e.g., Flutter, SwiftUI, React)** - -```typescript -interface ComponentImplementation extends ComponentApi { - /** - * @param ctx The component's context containing its data and state. - * @param buildChild A closure provided by the surface to recursively build children. - */ - build( - ctx: ComponentContext, - buildChild: (id: string) => NativeWidget, - ): NativeWidget; -} -``` - -**Stateful / Imperative Frameworks (e.g., Vanilla DOM, Android Views)** -Because the catalog only holds a single "blueprint" of each `ComponentImplementation`, stateful frameworks need a way to instantiate individual objects for each component rendered on screen. - -```typescript -interface ComponentInstance { - mount(container: NativeElement): void; - update(ctx: ComponentContext): void; - unmount(): void; -} - -interface ComponentImplementation extends ComponentApi { - /** Creates a new stateful instance of this component type. */ - createInstance(ctx: ComponentContext): ComponentInstance; -} -``` - -### `Surface` - -The entrypoint widget/view for a specific framework. It is instantiated with a `SurfaceModel`. It listens to the model for lifecycle events and dynamically builds the UI tree, initiating the recursive rendering loop at the component with ID `root`. - ### `SurfaceModel` & `ComponentContext` -The state containers. +The core state containers. - **`SurfaceModel`**: Represents the entire state of a single UI surface, holding the `DataModel` and a flat list of component configurations. - **`ComponentContext`**: A transient object created by the `Surface` and passed into a `ComponentImplementation` during rendering. It pairs the component's specific configuration with a scoped window into the data model (`DataContext`). --- -## THE FRAMEWORK-AGNOSTIC LAYER +## THE FRAMEWORK-AGNOSTIC DATA LAYER ## 3. The Core Data Layer (Detailed Specifications) @@ -319,7 +256,9 @@ class ComponentContext { _Escape Hatch_: Component implementations can use `ctx.surfaceComponents` to inspect the metadata of other components in the same surface (e.g. a `Row` checking if children have a `weight` property). This is discouraged but necessary for some layout engines. -### The Processing Layer (`MessageProcessor`) +--- + +## 4. The Processing Layer (`MessageProcessor`) The "Controller" that accepts the raw stream of A2UI messages, parses them, and mutates the Models. It also handles the aggregation of client state for synchronization. @@ -344,7 +283,7 @@ class MessageProcessor { } ``` -#### Client Data Model Synchronization +### Client Data Model Synchronization When a surface is created with `sendDataModel: true`, the client is responsible for sending the current state of that surface's data model back to the server whenever a client-to-server message (like an `action`) is sent. @@ -358,7 +297,7 @@ When a surface is created with `sendDataModel: true`, the client is responsible - **Surface Lifecycle**: It is an error to receive a `createSurface` message for a `surfaceId` that is already active. The processor MUST throw an error or report a validation failure if this occurs. - **Component Lifecycle**: If an `updateComponents` message provides an existing `id` but a _different_ `type`, the processor MUST remove the old component and create a fresh one to ensure framework renderers correctly reset their internal state. -#### Generating Client Capabilities and Schema Types +### Generating Client Capabilities and Schema Types To dynamically generate the `a2uiClientCapabilities` payload (specifically `inlineCatalogs`), the processor must convert internal component schemas into valid JSON Schemas. @@ -373,7 +312,9 @@ When `getClientCapabilities()` converts internal schemas to generate `inlineCata 3. **Theme**: Convert the catalog's theme schema into a JSON Schema representation. 4. **Reference Processing**: For all generated schemas (components, functions, and themes), traverse the tree looking for descriptions starting with `REF:`. Strip the tag and replace the node with a valid JSON Schema `$ref` object. -## 4. The Catalog API & Functions +--- + +## 5. The Catalog API & Functions A catalog groups component definitions and function definitions together, along with an optional theme schema. @@ -440,38 +381,11 @@ myCustomCatalog = Catalog( --- -## THE FRAMEWORK-SPECIFIC LAYER - -## 5. Component Implementation Strategies - -While the `ComponentImplementation` API dictates that a component must be able to `build()` or `mount()`, _how_ a developer connects that view to the reactive data model inside `ComponentContext` varies by language capabilities. - -### Strategy 1: Direct / Binderless Implementation - -The most straightforward approach. The developer implements the `ComponentImplementation` and manually manages A2UI reactivity directly within the `build` method using the framework's native reactive tools (e.g., `StreamBuilder` in Flutter, or manual `useEffect` in React). - -_Example: Flutter Direct Implementation_ - -```dart -Widget build(ComponentContext context, ChildBuilderCallback buildChild) { - return StreamBuilder( - // Manually observe the dynamic value stream - stream: context.dataContext.observeDynamicValue(context.componentModel.properties['label']), - builder: (context, snapshot) { - return ElevatedButton( - onPressed: () => context.dispatchAction(context.componentModel.properties['action']), - child: Text(snapshot.data?.toString() ?? ''), - ); - } - ); -} -``` - -### Strategy 2: The Binder Layer Pattern +## 6. The Binder Layer Pattern (State Bridge) For complex applications, scattering manual A2UI subscription logic across all view components becomes repetitive and error-prone. -The **Binder Layer** is an intermediate abstraction. It takes raw component properties and transforms the reactive A2UI bindings into a single, cohesive stream of strongly-typed `ResolvedProps`. The view component simply listens to this generic stream. +The **Binder Layer** is a framework-agnostic intermediate abstraction inside the Core SDK. It takes raw component configurations and transforms the reactive A2UI bindings into a single, cohesive stream of strongly-typed `ResolvedProps`. The native UI components (described in the [Framework Adapter Specification](framework_adapter_spec.md)) simply listen to this generic stream. ```typescript export interface ComponentBinding { @@ -485,7 +399,7 @@ export interface ComponentBinder { } ``` -### Strategy 3: Generic Binders for Dynamic Languages +### Generic Binders for Dynamic Languages In languages with powerful runtime reflection (like TypeScript/Zod), the Binder Layer can be entirely automated. You can write a generic factory that inspects a component's schema and automatically creates all necessary data model subscriptions, inferring strict types. @@ -498,137 +412,15 @@ This provides the ultimate "happy path" developer experience. The developer writ // Conceptually, the inferred type looks like this: interface ButtonResolvedProps { - label?: string; // Resolved from DynamicString - action: () => void; // Resolved from Action - child?: string; // Resolved structural ComponentId + label?: string; // Resolved from DynamicString + action: () => void; // Resolved from Action + child?: string; // Resolved structural ComponentId } - -// 2. The developer writes a simple, stateless UI component. -// The `props` argument is strictly inferred from the ButtonSchema. -const ReactButton = createReactComponent(ButtonBinder, ({ props, buildChild }) => { - return ( - - ); -}); ``` -Because of the generic types flowing through the adapter, if the developer typos `props.action` as `props.onClick`, or treats `props.label` as an object instead of a string, the compiler will immediately flag a type error. - -### Example: Framework-Specific Adapters - -The adapter acts as a wrapper that instantiates the binder, binds its output stream to the framework's state mechanism, injects structural rendering helpers (`buildChild`), and hooks into the native destruction lifecycle to call `dispose()`. - -#### React Pseudo-Adapter - -```typescript -// Pseudo-code concept for a React adapter -function createReactComponent(binder, RenderComponent) { - return function ReactWrapper({ context, buildChild }) { - // Hook into component mount - const [props, setProps] = useState(binder.initialProps); - - useEffect(() => { - // Create binding on mount - const binding = binder.bind(context); - - // Subscribe to updates - const sub = binding.propsStream.subscribe(newProps => setProps(newProps)); - - // Cleanup on unmount - return () => { - sub.unsubscribe(); - binding.dispose(); - }; - }, [context]); - - return ; - } -} -``` - -#### Angular Pseudo-Adapter - -```typescript -// Pseudo-code concept for an Angular adapter -@Component({ - selector: 'app-angular-wrapper', - imports: [MatButtonModule], - template: ` - @if (props(); as props) { - - } - `, -}) -export class AngularWrapper { - private binder = inject(BinderService); - private context = inject(ComponentContext); - - private bindingResource = resource({ - loader: async () => { - const binding = this.binder.bind(this.context); - - return { - instance: binding, - props: toSignal(binding.propsStream), // Convert Observable to Signal - }; - }, - }); - - props = computed(() => this.bindingResource.value()?.props() ?? null); - - constructor() { - inject(DestroyRef).onDestroy(() => { - this.bindingResource.value()?.instance.dispose(); - }); - } -} -``` - -## 6. Framework Binding Lifecycles & Traits - -Regardless of the implementation strategy chosen, the framework adapter or `ComponentImplementation` MUST strictly manage subscriptions to ensure performance and prevent memory leaks. - -### Contract of Ownership - -A crucial part of A2UI's architecture is understanding who "owns" the data layers. - -- **The Data Layer (Message Processor) owns the `ComponentModel`**. It creates, updates, and destroys the component's raw data state based on the incoming JSON stream. -- **The Framework Adapter owns the `ComponentContext` and `ComponentBinding`**. When the native framework decides to mount a component onto the screen (e.g., React runs `render`), the Framework Adapter creates the `ComponentContext` and passes it to the Binder. When the native framework unmounts the component, the Framework Adapter MUST call `binding.dispose()`. - -### Data Props vs. Structural Props - -It's important to distinguish between Data Props (like `label` or `value`) and Structural Props (like `child` or `children`). - -- **Data Props:** Handled entirely by the Binder. The adapter receives a stream of fully resolved values (e.g., `"Submit"` instead of a `DynamicString` path). Whenever a data value updates, the binder should emit a _new reference_ (e.g. a shallow copy of the props object) to ensure declarative frameworks that rely on strict equality (like React) correctly detect the change and trigger a re-render. -- **Structural Props:** The Binder does not attempt to resolve component IDs into actual UI trees. Instead, it outputs metadata for the children that need to be rendered. - - For a simple `ComponentId` (e.g., `Card.child`), it emits an object like `{ id: string, basePath: string }`. - - For a `ChildList` (e.g., `Column.children`), it evaluates the array. If the array is driven by a dynamic template bound to the data model, the binder must iterate over the array, using `context.dataContext.nested()` to generate a specific context for each index, and output a list of `ChildNode` streams. -- The framework adapter is then responsible for taking these node definitions and calling a framework-native `buildChild(id, basePath)` method recursively. - -> **Implementation Tip: Context Propagation** -> When implementing the recursive `buildChild` helper, ensure that it correctly inherits the _current_ component's data context path by default. If a nested component (like a Text field inside a List template) uses a relative path, it must resolve against the scoped path provided by its immediate structural parent (e.g., `/restaurants/0`), not the root path. Failing to propagate this context is a common cause of "empty" data in nested components. - -### Component Subscription Lifecycle Rules - -1. **Lazy Subscription**: Only bind and subscribe to data paths or property updates when the component is actually mounted/attached to the UI. -2. **Path Stability**: If a component's property changes via an `updateComponents` message, you MUST unsubscribe from the old path before subscribing to the new one. -3. **Destruction / Cleanup**: When a component is removed from the UI (e.g., via a `deleteSurface` message), the implementation MUST hook into its native lifecycle to dispose of all data model subscriptions. - -### Reactive Validation (`Checkable`) - -Interactive components that support the `checks` property should implement the `Checkable` trait. - -- **Aggregate Error Stream**: The component should subscribe to all `CheckRule` conditions defined in its properties. -- **UI Feedback**: It should reactively display the `message` of the first failing check as a validation error hint. -- **Action Blocking**: Actions (like `Button` clicks) should be reactively disabled or blocked if any validation check fails. - --- -## STANDARDS & TOOLING - -## 7. The Basic Catalog Standard +## 7. The Basic Catalog Standard (Core APIs) The standard A2UI Basic Catalog specifies a set of core components (Button, Text, Row, Column) and functions. @@ -657,20 +449,6 @@ class BasicCatalogImplementations( val row: RowApi // ... ) - -// The Framework Adapter implements the native views extending the base APIs -class ComposeButton : ButtonApi() { - // Framework specific render logic -} - -// The compiler forces all required components to be provided -val implementations = BasicCatalogImplementations( - button = ComposeButton(), - text = ComposeText(), - row = ComposeRow() -) - -val catalog = Catalog("id", listOf(implementations.button, implementations.text, implementations.row)) ``` #### Dynamic Languages (e.g. TypeScript) @@ -685,13 +463,6 @@ type BasicCatalogImplementations = { Row: ComponentImplementation & {name: 'Row'; schema: Schema}; // ... }; - -// If a developer forgets 'Row' or spells it wrong, the compiler throws an error. -const catalog = new Catalog('id', [ - implementations.Button, - implementations.Text, - implementations.Row, -]); ``` ### Expression Resolution Logic (`formatString`) @@ -705,60 +476,35 @@ The Basic Catalog requires a `formatString` function capable of interpreting `${ 3. **Escaping**: Literal `${` sequences must be handled (typically escaping as `\${`). 4. **Reactive Coercion**: Results are transformed into strings using the standard Type Coercion rules. -## 8. The Gallery App - -The Gallery App is a comprehensive development and debugging tool that serves as the reference environment for an A2UI renderer. It allows developers to visualize components, inspect the live data model, step through progressive rendering, and verify interaction logic. - -### UX Architecture - -The Gallery App must implement a three-column layout: - -1. **Left Column (Sample Navigation)**: A list of available A2UI samples. -2. **Center Column (Rendering & Messages)**: - - **Surface Preview**: Renders the active A2UI `Surface`. - - **JSON Message Stream**: Displays the list of A2UI JSON messages. - - **Interactive Stepper**: An "Advance" button allows processing messages one by one to verify progressive rendering. -3. **Right Column (Live Inspection)**: - - **Data Model Pane**: A live-updating view of the full Data Model. - - **Action Logs Pane**: A log of triggered actions and their context. - -### Integration Testing Requirements - -Every renderer implementation must include a suite of automated integration tests that utilize the Gallery App's logic to verify: - -- **Static Rendering**: Opening "Simple Text" renders correctly. -- **Layout Integrity**: "Row Layout" places elements correctly. -- **Two-Way Binding**: Typing in a TextField updates both the UI and the Data Model viewer simultaneously. -- **Reactive Logic**: Changes in one component dynamically update dependent components. -- **Action Context Scoping**: Actions emitted from nested templates (like Lists) contain correctly resolved data scopes. +--- -## 9. Agent Implementation Guide +## 8. Agent Implementation Guide: Core SDK Phases -If you are an AI Agent tasked with building a new renderer for A2UI, you MUST follow this strict, phased sequence of operations. +If you are an AI Agent tasked with building a new Core SDK for A2UI, you MUST follow this strict, phased sequence of operations. -### 1. Context to Ingest +### Phase 1: Context to Ingest Thoroughly review: +- `specification/v0_9_1/docs/sdks_spec.md` (unified topologies and layer context) - `specification/v0_9/docs/a2ui_protocol.md` (protocol rules) - `specification/v0_9/json/common_types.json` (dynamic binding types) - `specification/v0_9/json/server_to_client.json` (message envelopes) - `specification/v0_9/json/catalogs/minimal/minimal_catalog.json` (your initial target) -- `specification/v0_9/docs/basic_catalog_implementation_guide.md` (for rendering and spacing rules for when you get to the basic catalog) +- `specification/v0_9/docs/basic_catalog_implementation_guide.md` (for functional specs and spacing rules for when you get to the basic catalog) -### 2. Key Architecture Decisions (Write a Plan Document) +### Phase 2: Key Architecture Decisions (Write a Plan Document) Create a comprehensive design document detailing: - **Dependencies**: Which Schema Library and Observable/Reactive Library will you use? _Note: Ensure your reactive library supports both discrete event subscription (EventEmitter style) and stateful, signal-like data streams (BehaviorSubject/Signal style)._ -- **Component Architecture**: How will you define the `ComponentImplementation` API for this language and framework? -- **Surface Architecture**: How will the `Surface` framework entry point function to recursively build children? -- **Binding Strategy**: Will you use an intermediate Generic Binder Layer, or a direct binderless implementation? +- **Component Architecture**: How will you define the `ComponentApi` structure for this language? +- **Binding Strategy**: Detail your plans for the intermediate Core Binder Layer and dynamic/static types. - **STOP HERE. Ask the user for approval on this design document before proceeding.** -### 3. Core Model Layer +### Phase 3: Core Model Layer -Implement the framework-agnostic Data Layer (Section 3). +Implement the framework-agnostic Data Layer (Section 3 & 4). - Implement event streams and stateful signals. - Implement strict Protocol Models (`A2uiMessage`, `A2uiClientCapabilities`, etc.) with JSON serialization/deserialization and schema validation logic. @@ -768,38 +514,10 @@ Implement the framework-agnostic Data Layer (Section 3). - Implement `MessageProcessor` and ClientCapabilities generation. - **Action**: Write unit tests for JSON validation, the `DataModel` (especially pointer resolution/cascade logic), and `MessageProcessor`. Ensure they pass before continuing. -### 4. Framework-Specific Layer - -Implement the bridge between models and native UI (Section 5 & 6). - -- Define the concrete `ComponentImplementation` base class/interface. -- Implement the `Surface` view/widget that recurses through components. -- Implement subscription lifecycle management (lazy mounting, unmounting disposal). - -### 5. Minimal Catalog Support - -Target the `minimal_catalog.json` first. - -- Implement the pure API schemas for `Text`, `Row`, `Column`, `Button`, `TextField`. -- Implement the specific native UI rendering components for these. -- Implement the `capitalize` function. -- Bundle these into a `Catalog`. -- **Action**: Write unit tests verifying that properties update reactively when data changes. - -### 6. Gallery Application (Milestone) - -Build the Gallery App following the requirements in **Section 8**. - -- Load JSON samples from `specification/v0_9/json/catalogs/minimal/examples/`. -- Verify progressive rendering and reactivity. -- **STOP HERE. Ask the user for approval of the architecture and gallery application before proceeding to step 7.** - -### 7. Basic Catalog Support +### Phase 4: Basic Catalog Core Support Once the minimal architecture is proven robust, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md) and: - **Core Library**: Implement the full suite of basic functions. It is crucial to note that string interpolation and expression parsing should ONLY happen within the `formatString` function. Do not attempt to add global string interpolation to all strings. - **Core Library**: Create definitions/binders for the remaining Basic Catalog components. -- **Framework Library**: Implement all remaining UI widgets. -- **Tests**: Look at existing reference implementations (e.g., `web_core`) to formulate and run comprehensive unit and integration test cases for data coercion and function logic. -- Update the Gallery App to load samples from `specification/v0_9/json/catalogs/basic/examples/`. +- **Tests**: Look at existing reference implementations (e.g., `web_core`) to formulate and run comprehensive unit test cases for data coercion and function logic. diff --git a/specification/v0_9_1/docs/framework_adapter_spec.md b/specification/v0_9_1/docs/framework_adapter_spec.md new file mode 100644 index 0000000000..2958ceb76a --- /dev/null +++ b/specification/v0_9_1/docs/framework_adapter_spec.md @@ -0,0 +1,330 @@ +# A2UI Framework Adapter Specification + +This document describes the specification and architecture of an A2UI Framework-Specific Adapter (the View/Rendering Layer). The design defines how a framework-agnostic A2UI Core SDK (documented in the [A2UI Core SDK Specification](core_sdk_spec.md)) connects to native UI frameworks to paint the pixels. + +Both the core data structures and the rendering components interact with **Catalogs**. Within a catalog, the implementation follows a structured split: from the pure **Component Schema** (defined in the Core SDK) down to the **Framework-Specific Adapter** that renders native components (React, Angular, Flutter, SwiftUI, Jetpack Compose, iOS Views, Android Views, Vanilla DOM). + +--- + +## 1. Framework Adapter Overview + +The A2UI client architecture has a well-defined data flow that bridges language-agnostic data structures with native UI frameworks. + +1. **A2UI Messages** arrive from the server (JSON). +2. The **`MessageProcessor`** (part of Core SDK) parses these and updates the **`SurfaceModel`** (Agnostic State). +3. The **`Surface`** (Framework Entry View) listens to the `SurfaceModel` and begins rendering. +4. The `Surface` instantiates and renders individual **`ComponentImplementation`** nodes to build the UI tree. + +This establishes a fundamental split: + +- **The Framework-Agnostic Layer (Data Layer / Core SDK)**: Handles JSON parsing, state management, JSON pointers, and schemas. This logic is identical across all UI frameworks within a given language. +- **The Framework-Specific Layer (View Layer / Framework Adapter)**: Handles turning the structured state into actual pixels (React Nodes, Flutter Widgets, iOS Views). + +--- + +## 2. The View-Layer Interfaces + +At the heart of the A2UI framework-specific architecture are the interfaces that render components and manage the native UI lifecycle. + +### `ComponentImplementation` + +The framework-specific logic for rendering a component. It extends `ComponentApi` (defined in [Core SDK Specification](core_sdk_spec.md)) to include a `build` or `render` method. + +How this looks depends on the target framework's paradigm: + +#### Functional / Reactive Frameworks (e.g., Flutter, SwiftUI, React) + +```typescript +interface ComponentImplementation extends ComponentApi { + /** + * @param ctx The component's context containing its data and state. + * @param buildChild A closure provided by the surface to recursively build children. + */ + build( + ctx: ComponentContext, + buildChild: (id: string) => NativeWidget, + ): NativeWidget; +} +``` + +#### Stateful / Imperative Frameworks (e.g., Vanilla DOM, Android Views) + +Because the catalog only holds a single "blueprint" of each `ComponentImplementation`, stateful frameworks need a way to instantiate individual objects for each component rendered on screen. + +```typescript +interface ComponentInstance { + mount(container: NativeElement): void; + update(ctx: ComponentContext): void; + unmount(): void; +} + +interface ComponentImplementation extends ComponentApi { + /** Creates a new stateful instance of this component type. */ + createInstance(ctx: ComponentContext): ComponentInstance; +} +``` + +### `Surface` + +The entrypoint widget/view for a specific framework. It is instantiated with a `SurfaceModel` (from the Core SDK). It listens to the model for lifecycle events and dynamically builds the UI tree, initiating the recursive rendering loop at the component with ID `root`. + +--- + +## 3. Component Implementation Strategies + +While the `ComponentImplementation` API dictates that a component must be able to `build()` or `mount()`, _how_ a developer connects that view to the reactive data model inside `ComponentContext` varies by language and framework capabilities. + +### Strategy 1: Direct / Binderless Implementation + +The most straightforward approach. The developer implements the `ComponentImplementation` and manually manages A2UI reactivity directly within the `build` method using the framework's native reactive tools (e.g., `StreamBuilder` in Flutter, or manual `useEffect` in React). + +_Example: Flutter Direct Implementation_ + +```dart +Widget build(ComponentContext context, ChildBuilderCallback buildChild) { + return StreamBuilder( + // Manually observe the dynamic value stream + stream: context.dataContext.observeDynamicValue(context.componentModel.properties['label']), + builder: (context, snapshot) { + return ElevatedButton( + onPressed: () => context.dispatchAction(context.componentModel.properties['action']), + child: Text(snapshot.data?.toString() ?? ''), + ); + } + ); +} +``` + +### Strategy 2: Binder-Based UI Components + +For complex applications, scattering manual A2UI subscription logic across all view components becomes repetitive and error-prone. The **Binder Layer** in the Core SDK abstractly resolves and evaluates reactive inputs into a standard stream of `ResolvedProps`. + +The framework-specific UI component simply subscribes to this generic stream and updates the rendering. + +### Strategy 3: Generic Binders for Dynamic Languages + +In highly dynamic ecosystems like TypeScript/JavaScript, we can completely automate the creation of wrappers that automatically bind binders to UI components, offering compile-time type-safety: + +```typescript +// Concept: The developer writes a simple, stateless UI component. +// The `props` argument is strictly inferred from the ButtonSchema binder. +const ReactButton = createReactComponent(ButtonBinder, ({ props, buildChild }) => { + return ( + + ); +}); +``` + +Because of the generic types flowing through the adapter, if the developer typos `props.action` as `props.onClick`, or treats `props.label` as an object instead of a string, the compiler will immediately flag a type error. + +--- + +## 4. Example: Framework-Specific Adapters + +The adapter acts as a wrapper that instantiates the binder, binds its output stream to the framework's state mechanism, injects structural rendering helpers (`buildChild`), and hooks into the native destruction lifecycle to call `dispose()`. + +### React Pseudo-Adapter + +```typescript +// Pseudo-code concept for a React adapter +function createReactComponent(binder, RenderComponent) { + return function ReactWrapper({ context, buildChild }) { + // Hook into component mount + const [props, setProps] = useState(binder.initialProps); + + useEffect(() => { + // Create binding on mount + const binding = binder.bind(context); + + // Subscribe to updates + const sub = binding.propsStream.subscribe(newProps => setProps(newProps)); + + // Cleanup on unmount + return () => { + sub.unsubscribe(); + binding.dispose(); + }; + }, [context]); + + return ; + } +} +``` + +### Angular Pseudo-Adapter + +```typescript +// Pseudo-code concept for an Angular adapter +@Component({ + selector: 'app-angular-wrapper', + imports: [MatButtonModule], + template: ` + @if (props(); as props) { + + } + `, +}) +export class AngularWrapper { + private binder = inject(BinderService); + private context = inject(ComponentContext); + + private bindingResource = resource({ + loader: async () => { + const binding = this.binder.bind(this.context); + + return { + instance: binding, + props: toSignal(binding.propsStream), // Convert Observable to Signal + }; + }, + }); + + props = computed(() => this.bindingResource.value()?.props() ?? null); + + constructor() { + inject(DestroyRef).onDestroy(() => { + this.bindingResource.value()?.instance.dispose(); + }); + } +} +``` + +--- + +## 5. Framework Binding Lifecycles & Traits + +Regardless of the implementation strategy chosen, the framework adapter or `ComponentImplementation` MUST strictly manage subscriptions to ensure performance and prevent memory leaks. + +### Contract of Ownership + +A crucial part of A2UI's architecture is understanding who "owns" the data layers. + +- **The Data Layer (Message Processor) owns the `ComponentModel`**. It creates, updates, and destroys the component's raw data state based on the incoming JSON stream. +- **The Framework Adapter owns the `ComponentContext` and `ComponentBinding`**. When the native framework decides to mount a component onto the screen (e.g., React runs `render`), the Framework Adapter creates the `ComponentContext` and passes it to the Binder. When the native framework unmounts the component, the Framework Adapter MUST call `binding.dispose()`. + +### Data Props vs. Structural Props + +It's important to distinguish between Data Props (like `label` or `value`) and Structural Props (like `child` or `children`). + +- **Data Props:** Handled entirely by the Binder. The adapter receives a stream of fully resolved values (e.g., `"Submit"` instead of a `DynamicString` path). Whenever a data value updates, the binder should emit a _new reference_ (e.g. a shallow copy of the props object) to ensure declarative frameworks that rely on strict equality (like React) correctly detect the change and trigger a re-render. +- **Structural Props:** The Binder does not attempt to resolve component IDs into actual UI trees. Instead, it outputs metadata for the children that need to be rendered. + - For a simple `ComponentId` (e.g., `Card.child`), it emits an object like `{ id: string, basePath: string }`. + - For a `ChildList` (e.g., `Column.children`), it evaluates the array. If the array is driven by a dynamic template bound to the data model, the binder must iterate over the array, using `context.dataContext.nested()` to generate a specific context for each index, and output a list of `ChildNode` streams. +- The framework adapter is then responsible for taking these node definitions and calling a framework-native `buildChild(id, basePath)` method recursively. + +> **Implementation Tip: Context Propagation** +> When implementing the recursive `buildChild` helper, ensure that it correctly inherits the _current_ component's data context path by default. If a nested component (like a Text field inside a List template) uses a relative path, it must resolve against the scoped path provided by its immediate structural parent (e.g., `/restaurants/0`), not the root path. Failing to propagate this context is a common cause of "empty" data in nested components. + +### Component Subscription Lifecycle Rules + +1. **Lazy Subscription**: Only bind and subscribe to data paths or property updates when the component is actually mounted/attached to the UI. +2. **Path Stability**: If a component's property changes via an `updateComponents` message, you MUST unsubscribe from the old path before subscribing to the new one. +3. **Destruction / Cleanup**: When a component is removed from the UI (e.g., via a `deleteSurface` message), the implementation MUST hook into its native lifecycle to dispose of all data model subscriptions. + +### Reactive Validation (`Checkable`) + +Interactive components that support the `checks` property should implement the `Checkable` trait. + +- **Aggregate Error Stream**: The component should subscribe to all `CheckRule` conditions defined in its properties. +- **UI Feedback**: It should reactively display the `message` of the first failing check as a validation error hint. +- **Action Blocking**: Actions (like `Button` clicks) should be reactively disabled or blocked if any validation check fails. + +--- + +## 6. Standard & Custom Component Overrides + +The standard A2UI Basic Catalog specifies a set of core components (Button, Text, Row, Column) and functions. + +### Strict API / Implementation Separation + +When building libraries that provide the Basic Catalog, separating the pure API from visual renderers is vital. + +- **Multi-Framework Code Reuse**: Allows core binders to be reused across different UI framework adapter libraries. +- **Developer Overrides**: By exposing the standard API definitions, developers adopting A2UI can easily swap in custom UI implementations (e.g., replacing the default `Button` with their company's internal Design System `Button`) without having to rewrite the complex A2UI validation, data binding, and capability generation logic. + +For a detailed walkthrough on how to visually and functionally implement each basic component and function, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md). + +--- + +## 7. The Gallery App Specification + +The Gallery App is a comprehensive development and debugging tool that serves as the reference environment for an A2UI renderer. It allows developers to visualize components, inspect the live data model, step through progressive rendering, and verify interaction logic. + +### UX Architecture + +The Gallery App must implement a three-column layout: + +1. **Left Column (Sample Navigation)**: A list of available A2UI samples. +2. **Center Column (Rendering & Messages)**: + - **Surface Preview**: Renders the active A2UI `Surface`. + - **JSON Message Stream**: Displays the list of A2UI JSON messages. + - **Interactive Stepper**: An "Advance" button allows processing messages one by one to verify progressive rendering. +3. **Right Column (Live Inspection)**: + - **Data Model Pane**: A live-updating view of the full Data Model. + - **Action Logs Pane**: A log of triggered actions and their context. + +### Integration Testing Requirements + +Every renderer implementation must include a suite of automated integration tests that utilize the Gallery App's logic to verify: + +- **Static Rendering**: Opening "Simple Text" renders correctly. +- **Layout Integrity**: "Row Layout" places elements correctly. +- **Two-Way Binding**: Typing in a TextField updates both the UI and the Data Model viewer simultaneously. +- **Reactive Logic**: Changes in one component dynamically update dependent components. +- **Action Context Scoping**: Actions emitted from nested templates (like Lists) contain correctly resolved data scopes. + +--- + +## 8. Agent Implementation Guide: Framework & UI Phases + +If you are an AI Agent tasked with building a new Renderer/Framework Adapter for A2UI, you MUST follow this strict, phased sequence of operations. + +### Phase 1: Context & Core Ingest + +Thoroughly review: + +- [A2UI Core SDK Specification](core_sdk_spec.md) (for state and message structures). +- `specification/v0_9/docs/basic_catalog_implementation_guide.md` (for rendering, typography, alignment, and spacing rules). + +### Phase 2: Key Architecture Decisions (Write a Plan Document) + +Create a comprehensive design document detailing: + +- **Component Architecture**: How will you define the `ComponentImplementation` base class or interface in this framework? +- **Surface Architecture**: How will the `Surface` framework entry point function to recursively build children? +- **Binding Strategy**: Will you use Strategy 1 (Direct), Strategy 2 (Predefined binders), or Strategy 3 (Automated TypeScript/Zod wrappers)? +- **STOP HERE. Ask the user for approval on this design document before proceeding.** + +### Phase 3: Framework-Specific Layer + +Implement the bridge between Core SDK models and native UI. + +- Define the concrete `ComponentImplementation` base class/interface. +- Implement the `Surface` view/widget that recurses through components. +- Implement subscription lifecycle management (lazy mounting, unmounting disposal, path stability). + +### Phase 4: Minimal Catalog Support + +Target the `minimal_catalog.json` first. + +- Implement the specific native UI rendering components for `Text`, `Row`, `Column`, `Button`, and `TextField`. +- Bundle these visual widgets into a catalog adapter. +- **Action**: Write unit tests verifying that properties update reactively in the UI when underlying data model changes. + +### Phase 5: Gallery Application (Milestone) + +Build the Gallery App following the requirements in **Section 7**. + +- Load JSON samples from `specification/v0_9/json/catalogs/minimal/examples/`. +- Verify progressive rendering, stepping, and visual reactivity. +- **STOP HERE. Ask the user for approval of the architecture and gallery application before proceeding.** + +### Phase 6: Basic Catalog Support + +Once the minimal architecture is proven robust, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md) and: + +- **Framework Library**: Implement all remaining UI widgets and traits (such as `Checkable`). +- **Tests**: Formulate and run comprehensive unit and integration test cases to verify static rendering, layout alignment, two-way bindings, and scoped actions. +- Update the Gallery App to load samples from `specification/v0_9/json/catalogs/basic/examples/`. diff --git a/specification/v0_9_1/docs/sdks_spec.md b/specification/v0_9_1/docs/sdks_spec.md new file mode 100644 index 0000000000..fa6308f831 --- /dev/null +++ b/specification/v0_9_1/docs/sdks_spec.md @@ -0,0 +1,62 @@ +# A2UI Unified SDK Architecture + +This document describes the unified architecture of the A2UI SDK ecosystem. To support modern, multi-paradigm generative UI applications, A2UI separates concerns into a modular, three-tiered structure: the **A2UI Core SDK**, the **Inference SDK**, and **Framework Adapters**. + +This architecture enables high code reuse, strict conformance, and unified behavior across all languages. Ensuring that we use a consistent codebase structure and terminology across languages also makes it easier to maintain the codebases together and add new features across languages using spec-driven development. + +## 1. Ecosystem Dependency Structure + +Historically, A2UI maintained separate, isolated codebases for server-side agent SDKs (which generate UI messages) and client-side rendering SDKs (which paint pixels). We are now building a unified, shared-code structure across each language: + +``` ++-------------------------------------------------------------+ +| FRAMEWORK ADAPTER | +| (Renders surface state to pixels using a native UI) | ++------------------------------+------------------------------+ + | + | (Depends on) + v ++-------------------------------------------------------------+ +| A2UI CORE SDK | +| (Maintains surface state, message processing, validation) | ++------------------------------+------------------------------+ + ^ + | (Depends on) + | ++-------------------------------------------------------------+ +| INFERENCE SDK | +| (Generates serverToClient messages using LLM engines) | ++-------------------------------------------------------------+ +``` + +## 2. Library Responsibilities + +### A2UI Core SDK (`a2ui_core` / `@a2ui/core`) + +**Availability:** Every target language (TS, Dart, Python, Kotlin, Swift, C++). +**Responsibility:** Provides the foundational, language-native representations of the official A2UI specifications. + +- **Protocol Models:** Strongly-typed classes representing Catalog declarations (`Catalog`, `ComponentApi`, `FunctionApi`) and A2UI message types (`ClientToServer`, `ServerToClient`, etc.). +- **Surface State Management:** Mutable models representing active UI surfaces (`SurfaceModel`, `ComponentModel`, `DataModel`). +- **Processing Layer (`MessageProcessor`):** Evaluates incoming JSON message arrays, resolves relative JSON pointers, and updates surface state models. +- **Validation:** Performs strict validation of message schema structures and reference checks. +- **Incremental Snapshotting:** Supports tracking and collapsing incremental updates into flat snapshots (e.g., `ComponentNode` trees). + +### Inference SDK (`a2ui_inference` / `@a2ui/inference`) + +**Availability:** All supported agent languages and select client-side languages (for local inference). +**Responsibility:** Guides Large Language Models (LLMs) to generate valid A2UI message payloads based on a defined component Catalog and active application context. + +- **Inference Strategies:** Standard structured generation schemes. +- **Prompt Construction:** Building prompt buffers and feeding component capability schemas dynamically to LLM contexts. +- **Message Parsing:** Safely extracting and repairing raw JSON envelopes from LLM completion streams. +- **Error Repair & Retries:** Programmatic fixing of malformed or invalid schemas. + +### Framework Adapters (`react_renderer`, `compose_renderer`, etc.) + +**Availability:** Every supported UI framework across client platforms. +**Responsibility:** Paints the state of a Core `SurfaceModel` onto physical screen pixels using native UI hierarchies. + +- **Framework Entry View (`Surface`):** Observes Core state and boots the recursive layout loop. +- **Component Renderers (`ComponentImplementation`):** Individual native views implementing visual specs (e.g., standard layout, text, or inputs from the Basic Catalog). +- **Lifecycle Bindings:** Standardizes lazy-mounting, reactive value propagation, and memory leak prevention (`dispose`). diff --git a/specification/v1_0/docs/renderer_guide.md b/specification/v1_0/docs/core_sdk_spec.md similarity index 55% rename from specification/v1_0/docs/renderer_guide.md rename to specification/v1_0/docs/core_sdk_spec.md index 9b0234aeb4..ca1dc09632 100644 --- a/specification/v1_0/docs/renderer_guide.md +++ b/specification/v1_0/docs/core_sdk_spec.md @@ -1,53 +1,31 @@ -# Unified Architecture & Implementation Guide +# A2UI Core SDK Specification -This document describes the architecture of an A2UI client implementation. The design separates concerns into distinct layers to maximize code reuse, ensure memory safety, and provide a streamlined developer experience when adding custom components. +This document describes the detailed programmatic specification and architecture of the A2UI Core SDK. The Core SDK serves as the foundational data, state, and processing layer of A2UI. -Both the core data structures and the rendering components interact with **Catalogs**. Within a catalog, the implementation follows a structured split: from the pure **Component Schema** down to the **Framework-Specific Adapter** that paints the pixels. +This layer handles JSON parsing, state models, JSON pointers, catalogs, and schemas. This logic remains completely framework-agnostic, allowing it to be implemented identically across all target environments (including server-side or headless languages where there is no renderer). -## 1. Unified Architecture Overview +For a high-level overview of the entire A2UI ecosystem (including the Inference SDK and Framework Adapter structure), see the [A2UI Unified SDK Architecture](sdks_spec.md). For UI framework integration and rendering details, see the [A2UI Framework Adapter Specification](framework_adapter_spec.md). -The A2UI client architecture has a well-defined data flow that bridges language-agnostic data structures with native UI frameworks. - -1. **A2UI Messages** arrive from the server (JSON). -2. The **`MessageProcessor`** parses these and updates the **`SurfaceModel`** (Agnostic State). -3. The **`Surface`** (Framework Entry View) listens to the `SurfaceModel` and begins rendering. -4. The `Surface` instantiates and renders individual **`ComponentImplementation`** nodes to build the UI tree. - -This establishes a fundamental split: - -- **The Framework-Agnostic Layer (Data Layer)**: Handles JSON parsing, state management, JSON pointers, and schemas. This logic is identical across all UI frameworks within a given language. -- **The Framework-Specific Layer (View Layer)**: Handles turning the structured state into actual pixels (React Nodes, Flutter Widgets, iOS Views). - -### Implementation Topologies - -Because A2UI spans multiple languages and UI paradigms, the strictness and location of these architectural boundaries will vary depending on the target ecosystem. - -#### Dynamic Languages (e.g., TypeScript / JavaScript) - -In highly dynamic ecosystems like the web, the architecture is typically split across multiple packages to maximize code reuse across diverse UI frameworks (React, Angular, Vue, Lit). - -- **Core Library (`web_core`)**: Implements the Core Data Layer, Component Schemas, and a Generic Binder Layer. Because TS/JS has powerful runtime reflection, the core library can provide a generic binder that automatically handles all data binding without framework-specific code. -- **Framework Library (`react_renderer`, `angular_renderer`)**: Implements the Framework-Specific Adapters and the actual view implementations (the React `Button`, `Text`, etc.). - -#### Static Languages (e.g., Kotlin, Swift, Dart) +--- -In statically typed languages (and AOT-compiled languages like Dart), runtime reflection is often limited or discouraged for performance reasons. +## 1. Core SDK Role & Architecture -- **Core Library (e.g., `kotlin_core`)**: Implements the Core Data Layer and Component Schemas. The core library typically provides a manually implemented **Binder Layer** for the standard Basic Catalog components. This ensures that even in static environments, basic components have a standardized, framework-agnostic reactive state definition. -- **Code Generation (Future/Optional)**: While the core library starts with manual binders, it may eventually offer Code Generation (e.g., KSP, Swift Macros) to automate the creation of Binders for custom components. -- **Custom Components**: In the absence of code generation, developers implementing new, ad-hoc components typically utilize a **"Binderless" Implementation** flow, which allows for direct binding to the data model without intermediate boilerplate. -- **Framework Library (e.g., `compose_renderer`)**: Uses the predefined Binders to connect to native UI state and implements the actual visual components. +The A2UI Core SDK acts as the central state coordinator. It is designed to represent core concepts and behaviors described in the A2UI specification, without any UI rendering logic. -#### Combined Core + Framework Libraries (e.g., Swift + SwiftUI) +Its core responsibilities include: -In ecosystems dominated by a single UI framework (like iOS with SwiftUI), developers often build a single, unified library rather than splitting Core and Framework into separate packages. +1. **Catalog Representation:** Define `Catalog` structures and pure technical component metadata/schemas (`ComponentApi`, `FunctionApi`). +2. **Protocol Definitions:** Model strongly-typed inbound and outbound message structures (e.g., `ClientToServer`, `ServerToClient`, etc.). +3. **Surface State Containers:** Track mutable, long-lived rendering states via `SurfaceModel`, `ComponentModel`, and `DataModel`. +4. **Message Processor:** Parse inbound message sequences to mutate local state containers via `MessageProcessor`. +5. **JSON Pointer Scope:** Standardize relative pointer evaluation and reactivity via scoped context managers (`ComponentContext`, `DataContext`). +6. **Validation:** Throw strict schema and reference-resolution errors. -- **Relaxed Boundaries**: The strict separation between Core and Framework libraries can be relaxed. The generic `ComponentContext` and the framework-specific adapter logic are often tightly integrated. -- **Why Keep the Binder Layer?**: Even in a combined library, defining the intermediate Binder Layer remains highly recommended. It standardizes how A2UI data resolves into reactive state. This allows developers adopting the library to easily write alternative implementations of well-known components without having to rewrite the complex, boilerplate-heavy A2UI data subscription logic. +--- -## 2. The Core Interfaces +## 2. The Core SDK Interfaces -At the heart of the A2UI architecture are five key interfaces that connect the data to the screen. +At the heart of the A2UI architecture are key interfaces that connect the data to the screen. ### `ComponentApi` @@ -62,57 +40,16 @@ interface ComponentApi { } ``` -### `ComponentImplementation` - -The framework-specific logic for rendering a component. It extends `ComponentApi` to include a `build` or `render` method. - -How this looks depends on the target framework's paradigm: - -**Functional / Reactive Frameworks (e.g., Flutter, SwiftUI, React)** - -```typescript -interface ComponentImplementation extends ComponentApi { - /** - * @param ctx The component's context containing its data and state. - * @param buildChild A closure provided by the surface to recursively build children. - */ - build( - ctx: ComponentContext, - buildChild: (id: string, basePath?: string) => NativeWidget, - ): NativeWidget; -} -``` - -**Stateful / Imperative Frameworks (e.g., Vanilla DOM, Android Views)** -Because the catalog only holds a single "blueprint" of each `ComponentImplementation`, stateful frameworks need a way to instantiate individual objects for each component rendered on screen. - -```typescript -interface ComponentInstance { - mount(container: NativeElement): void; - update(ctx: ComponentContext): void; - unmount(): void; -} - -interface ComponentImplementation extends ComponentApi { - /** Creates a new stateful instance of this component type. */ - createInstance(ctx: ComponentContext): ComponentInstance; -} -``` - -### `Surface` - -The entrypoint widget/view for a specific framework. It is instantiated with a `SurfaceModel`. It listens to the model for lifecycle events and dynamically builds the UI tree, initiating the recursive rendering loop at the component with ID `root`. - ### `SurfaceModel` & `ComponentContext` -The state containers. +The core state containers. - **`SurfaceModel`**: Represents the entire state of a single UI surface, holding the `DataModel` and a flat list of component configurations. - **`ComponentContext`**: A transient object created by the `Surface` and passed into a `ComponentImplementation` during rendering. It pairs the component's specific configuration with a scoped window into the data model (`DataContext`). --- -## THE FRAMEWORK-AGNOSTIC LAYER +## THE FRAMEWORK-AGNOSTIC DATA LAYER ## 3. The Core Data Layer (Detailed Specifications) @@ -319,7 +256,9 @@ class ComponentContext { _Escape Hatch_: Component implementations can use `ctx.surfaceComponents` to inspect the metadata of other components in the same surface (e.g. a `Row` checking if children have a `weight` property). This is discouraged but necessary for some layout engines. -### The Processing Layer (`MessageProcessor`) +--- + +## 4. The Processing Layer (`MessageProcessor`) The "Controller" that accepts the raw stream of A2UI messages, parses them, and mutates the Models. It also handles the aggregation of client state for synchronization. @@ -344,7 +283,7 @@ class MessageProcessor { } ``` -#### Client Data Model Synchronization +### Client Data Model Synchronization When a surface is created with `sendDataModel: true`, the client is responsible for sending the current state of that surface's data model back to the server whenever a client-to-server message (like an `action`) is sent. @@ -358,7 +297,7 @@ When a surface is created with `sendDataModel: true`, the client is responsible - **Surface Lifecycle**: It is an error to receive a `createSurface` message for a `surfaceId` that is already active; `surfaceId` must be globally unique per client session. The processor MUST throw an error or report a validation failure if this occurs. - **Component Lifecycle**: If an `updateComponents` message provides an existing `id` but a _different_ `type`, the processor MUST remove the old component and create a fresh one to ensure framework renderers correctly reset their internal state. -#### Generating Client Capabilities and Schema Types +### Generating Client Capabilities and Schema Types To dynamically generate the `a2uiClientCapabilities` payload (specifically `inlineCatalogs`), the processor must convert internal component schemas into valid JSON Schemas. @@ -373,7 +312,9 @@ When `getClientCapabilities()` converts internal schemas to generate `inlineCata 3. **Theme**: Convert the catalog's theme schema into a JSON Schema representation. 4. **Reference Processing**: For all generated schemas (components, functions, and themes), traverse the tree looking for descriptions starting with `REF:`. Strip the tag and replace the node with a valid JSON Schema `$ref` object. -## 4. The Catalog API & Functions +--- + +## 5. The Catalog API & Functions A catalog groups component definitions and function definitions together, along with an optional theme schema. @@ -440,38 +381,11 @@ myCustomCatalog = Catalog( --- -## THE FRAMEWORK-SPECIFIC LAYER - -## 5. Component Implementation Strategies - -While the `ComponentImplementation` API dictates that a component must be able to `build()` or `mount()`, _how_ a developer connects that view to the reactive data model inside `ComponentContext` varies by language capabilities. - -### Strategy 1: Direct / Binderless Implementation - -The most straightforward approach. The developer implements the `ComponentImplementation` and manually manages A2UI reactivity directly within the `build` method using the framework's native reactive tools (e.g., `StreamBuilder` in Flutter, or manual `useEffect` in React). - -_Example: Flutter Direct Implementation_ - -```dart -Widget build(ComponentContext context, ChildBuilderCallback buildChild) { - return StreamBuilder( - // Manually observe the dynamic value stream - stream: context.dataContext.observeDynamicValue(context.componentModel.properties['label']), - builder: (context, snapshot) { - return ElevatedButton( - onPressed: () => context.dispatchAction(context.componentModel.properties['action']), - child: Text(snapshot.data?.toString() ?? ''), - ); - } - ); -} -``` - -### Strategy 2: The Binder Layer Pattern +## 6. The Binder Layer Pattern (State Bridge) For complex applications, scattering manual A2UI subscription logic across all view components becomes repetitive and error-prone. -The **Binder Layer** is an intermediate abstraction. It takes raw component properties and transforms the reactive A2UI bindings into a single, cohesive stream of strongly-typed `ResolvedProps`. The view component simply listens to this generic stream. +The **Binder Layer** is a framework-agnostic intermediate abstraction inside the Core SDK. It takes raw component configurations and transforms the reactive A2UI bindings into a single, cohesive stream of strongly-typed `ResolvedProps`. The native UI components (described in the [Framework Adapter Specification](framework_adapter_spec.md)) simply listen to this generic stream. ```typescript export interface ComponentBinding { @@ -485,7 +399,7 @@ export interface ComponentBinder { } ``` -### Strategy 3: Generic Binders for Dynamic Languages +### Generic Binders for Dynamic Languages In languages with powerful runtime reflection (like TypeScript/Zod), the Binder Layer can be entirely automated. You can write a generic factory that inspects a component's schema and automatically creates all necessary data model subscriptions, inferring strict types. @@ -498,137 +412,15 @@ This provides the ultimate "happy path" developer experience. The developer writ // Conceptually, the inferred type looks like this: interface ButtonResolvedProps { - label?: string; // Resolved from DynamicString - action: () => void; // Resolved from Action - child?: { id: string; basePath: string }; // Resolved structural ComponentId + label?: string; // Resolved from DynamicString + action: () => void; // Resolved from Action + child?: {id: string; basePath: string}; // Resolved structural ComponentId } - -// 2. The developer writes a simple, stateless UI component. -// The `props` argument is strictly inferred from the ButtonSchema. -const ReactButton = createReactComponent(ButtonBinder, ({ props, buildChild }) => { - return ( - - ); -}); ``` -Because of the generic types flowing through the adapter, if the developer typos `props.action` as `props.onClick`, or treats `props.label` as an object instead of a string, the compiler will immediately flag a type error. - -### Example: Framework-Specific Adapters - -The adapter acts as a wrapper that instantiates the binder, binds its output stream to the framework's state mechanism, injects structural rendering helpers (`buildChild`), and hooks into the native destruction lifecycle to call `dispose()`. - -#### React Pseudo-Adapter - -```typescript -// Pseudo-code concept for a React adapter -function createReactComponent(binder, RenderComponent) { - return function ReactWrapper({ context, buildChild }) { - // Hook into component mount - const [props, setProps] = useState(binder.initialProps); - - useEffect(() => { - // Create binding on mount - const binding = binder.bind(context); - - // Subscribe to updates - const sub = binding.propsStream.subscribe(newProps => setProps(newProps)); - - // Cleanup on unmount - return () => { - sub.unsubscribe(); - binding.dispose(); - }; - }, [context]); - - return ; - } -} -``` - -#### Angular Pseudo-Adapter - -```typescript -// Pseudo-code concept for an Angular adapter -@Component({ - selector: 'app-angular-wrapper', - imports: [MatButtonModule], - template: ` - @if (props(); as props) { - - } - `, -}) -export class AngularWrapper { - private binder = inject(BinderService); - private context = inject(ComponentContext); - - private bindingResource = resource({ - loader: async () => { - const binding = this.binder.bind(this.context); - - return { - instance: binding, - props: toSignal(binding.propsStream), // Convert Observable to Signal - }; - }, - }); - - props = computed(() => this.bindingResource.value()?.props() ?? null); - - constructor() { - inject(DestroyRef).onDestroy(() => { - this.bindingResource.value()?.instance.dispose(); - }); - } -} -``` - -## 6. Framework Binding Lifecycles & Traits - -Regardless of the implementation strategy chosen, the framework adapter or `ComponentImplementation` MUST strictly manage subscriptions to ensure performance and prevent memory leaks. - -### Contract of Ownership - -A crucial part of A2UI's architecture is understanding who "owns" the data layers. - -- **The Data Layer (Message Processor) owns the `ComponentModel`**. It creates, updates, and destroys the component's raw data state based on the incoming JSON stream. -- **The Framework Adapter owns the `ComponentContext` and `ComponentBinding`**. When the native framework decides to mount a component onto the screen (e.g., React runs `render`), the Framework Adapter creates the `ComponentContext` and passes it to the Binder. When the native framework unmounts the component, the Framework Adapter MUST call `binding.dispose()`. - -### Data Props vs. Structural Props - -It's important to distinguish between Data Props (like `label` or `value`) and Structural Props (like `child` or `children`). - -- **Data Props:** Handled entirely by the Binder. The adapter receives a stream of fully resolved values (e.g., `"Submit"` instead of a `DynamicString` path). Whenever a data value updates, the binder should emit a _new reference_ (e.g. a shallow copy of the props object) to ensure declarative frameworks that rely on strict equality (like React) correctly detect the change and trigger a re-render. -- **Structural Props:** The Binder does not attempt to resolve component IDs into actual UI trees. Instead, it outputs metadata for the children that need to be rendered. - - For a simple `ComponentId` (e.g., `Card.child`), it emits an object like `{ id: string, basePath: string }`. - - For a `ChildList` (e.g., `Column.children`), it evaluates the array. If the array is driven by a dynamic template bound to the data model, the binder must iterate over the array, using `context.dataContext.nested()` to generate a specific context for each index, and output a list of `ChildNode` streams. -- The framework adapter is then responsible for taking these node definitions and calling a framework-native `buildChild(id, basePath)` method recursively. - -> **Implementation Tip: Context Propagation** -> When implementing the recursive `buildChild` helper, ensure that it correctly inherits the _current_ component's data context path by default. If a nested component (like a Text field inside a List template) uses a relative path, it must resolve against the scoped path provided by its immediate structural parent (e.g., `/restaurants/0`), not the root path. Failing to propagate this context is a common cause of "empty" data in nested components. - -### Component Subscription Lifecycle Rules - -1. **Lazy Subscription**: Only bind and subscribe to data paths or property updates when the component is actually mounted/attached to the UI. -2. **Path Stability**: If a component's property changes via an `updateComponents` message, you MUST unsubscribe from the old path before subscribing to the new one. -3. **Destruction / Cleanup**: When a component is removed from the UI (e.g., via a `deleteSurface` message), the implementation MUST hook into its native lifecycle to dispose of all data model subscriptions. - -### Reactive Validation (`Checkable`) - -Interactive components that support the `checks` property should implement the `Checkable` trait. - -- **Aggregate Error Stream**: The component should subscribe to all `CheckRule` conditions defined in its properties. -- **UI Feedback**: It should reactively display the `message` of the first failing check as a validation error hint. -- **Action Blocking**: Actions (like `Button` clicks) should be reactively disabled or blocked if any validation check fails. - --- -## STANDARDS & TOOLING - -## 7. The Basic Catalog Standard +## 7. The Basic Catalog Standard (Core APIs) The standard A2UI Basic Catalog specifies a set of core components (Button, Text, Row, Column) and functions. @@ -657,20 +449,6 @@ class BasicCatalogImplementations( val row: RowApi // ... ) - -// The Framework Adapter implements the native views extending the base APIs -class ComposeButton : ButtonApi() { - // Framework specific render logic -} - -// The compiler forces all required components to be provided -val implementations = BasicCatalogImplementations( - button = ComposeButton(), - text = ComposeText(), - row = ComposeRow() -) - -val catalog = Catalog("id", listOf(implementations.button, implementations.text, implementations.row)) ``` #### Dynamic Languages (e.g. TypeScript) @@ -685,13 +463,6 @@ type BasicCatalogImplementations = { Row: ComponentImplementation & {name: 'Row'; schema: Schema}; // ... }; - -// If a developer forgets 'Row' or spells it wrong, the compiler throws an error. -const catalog = new Catalog('id', [ - implementations.Button, - implementations.Text, - implementations.Row, -]); ``` ### Expression Resolution Logic (`formatString`) @@ -705,60 +476,35 @@ The Basic Catalog requires a `formatString` function capable of interpreting `${ 3. **Escaping**: Literal `${` sequences must be handled (typically escaping as `\${`). 4. **Reactive Coercion**: Results are transformed into strings using the standard Type Coercion rules. -## 8. The Gallery App - -The Gallery App is a comprehensive development and debugging tool that serves as the reference environment for an A2UI renderer. It allows developers to visualize components, inspect the live data model, step through progressive rendering, and verify interaction logic. - -### UX Architecture - -The Gallery App must implement a three-column layout: - -1. **Left Column (Sample Navigation)**: A list of available A2UI samples. -2. **Center Column (Rendering & Messages)**: - - **Surface Preview**: Renders the active A2UI `Surface`. - - **JSON Message Stream**: Displays the list of A2UI JSON messages. - - **Interactive Stepper**: An "Advance" button allows processing messages one by one to verify progressive rendering. -3. **Right Column (Live Inspection)**: - - **Data Model Pane**: A live-updating view of the full Data Model. - - **Action Logs Pane**: A log of triggered actions and their context. - -### Integration Testing Requirements - -Every renderer implementation must include a suite of automated integration tests that utilize the Gallery App's logic to verify: - -- **Static Rendering**: Opening "Simple Text" renders correctly. -- **Layout Integrity**: "Row Layout" places elements correctly. -- **Two-Way Binding**: Typing in a TextField updates both the UI and the Data Model viewer simultaneously. -- **Reactive Logic**: Changes in one component dynamically update dependent components. -- **Action Context Scoping**: Actions emitted from nested templates (like Lists) contain correctly resolved data scopes. +--- -## 9. Agent Implementation Guide +## 8. Agent Implementation Guide: Core SDK Phases -If you are an AI Agent tasked with building a new renderer for A2UI, you MUST follow this strict, phased sequence of operations. +If you are an AI Agent tasked with building a new Core SDK for A2UI, you MUST follow this strict, phased sequence of operations. -### 1. Context to Ingest +### Phase 1: Context to Ingest Thoroughly review: +- `specification/v1_0/docs/sdks_spec.md` (unified topologies and layer context) - `specification/v1_0/docs/a2ui_protocol.md` (protocol rules) - `specification/v1_0/json/common_types.json` (dynamic binding types) - `specification/v1_0/json/server_to_client.json` (message envelopes) - `specification/v1_0/json/catalogs/minimal/minimal_catalog.json` (your initial target) -- `specification/v1_0/docs/basic_catalog_implementation_guide.md` (for rendering and spacing rules for when you get to the basic catalog) +- `specification/v1_0/docs/basic_catalog_implementation_guide.md` (for functional specs and spacing rules for when you get to the basic catalog) -### 2. Key Architecture Decisions (Write a Plan Document) +### Phase 2: Key Architecture Decisions (Write a Plan Document) Create a comprehensive design document detailing: - **Dependencies**: Which Schema Library and Observable/Reactive Library will you use? _Note: Ensure your reactive library supports both discrete event subscription (EventEmitter style) and stateful, signal-like data streams (BehaviorSubject/Signal style)._ -- **Component Architecture**: How will you define the `ComponentImplementation` API for this language and framework? -- **Surface Architecture**: How will the `Surface` framework entry point function to recursively build children? -- **Binding Strategy**: Will you use an intermediate Generic Binder Layer, or a direct binderless implementation? +- **Component Architecture**: How will you define the `ComponentApi` structure for this language? +- **Binding Strategy**: Detail your plans for the intermediate Core Binder Layer and dynamic/static types. - **STOP HERE. Ask the user for approval on this design document before proceeding.** -### 3. Core Model Layer +### Phase 3: Core Model Layer -Implement the framework-agnostic Data Layer (Section 3). +Implement the framework-agnostic Data Layer (Section 3 & 4). - Implement event streams and stateful signals. - Implement strict Protocol Models (`A2uiMessage`, `A2uiClientCapabilities`, etc.) with JSON serialization/deserialization and schema validation logic. @@ -768,38 +514,10 @@ Implement the framework-agnostic Data Layer (Section 3). - Implement `MessageProcessor` and ClientCapabilities generation. - **Action**: Write unit tests for JSON validation, the `DataModel` (especially pointer resolution/cascade logic), and `MessageProcessor`. Ensure they pass before continuing. -### 4. Framework-Specific Layer - -Implement the bridge between models and native UI (Section 5 & 6). - -- Define the concrete `ComponentImplementation` base class/interface. -- Implement the `Surface` view/widget that recurses through components. -- Implement subscription lifecycle management (lazy mounting, unmounting disposal). - -### 5. Minimal Catalog Support - -Target the `minimal_catalog.json` first. - -- Implement the pure API schemas for `Text`, `Row`, `Column`, `Button`, `TextField`. -- Implement the specific native UI rendering components for these. -- Implement the `capitalize` function. -- Bundle these into a `Catalog`. -- **Action**: Write unit tests verifying that properties update reactively when data changes. - -### 6. Gallery Application (Milestone) - -Build the Gallery App following the requirements in **Section 8**. - -- Load JSON samples from `specification/v1_0/json/catalogs/minimal/examples/`. -- Verify progressive rendering and reactivity. -- **STOP HERE. Ask the user for approval of the architecture and gallery application before proceeding to step 7.** - -### 7. Basic Catalog Support +### Phase 4: Basic Catalog Core Support Once the minimal architecture is proven robust, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md) and: - **Core Library**: Implement the full suite of basic functions. It is crucial to note that string interpolation and expression parsing should ONLY happen within the `formatString` function. Do not attempt to add global string interpolation to all strings. - **Core Library**: Create definitions/binders for the remaining Basic Catalog components. -- **Framework Library**: Implement all remaining UI widgets. -- **Tests**: Look at existing reference implementations (e.g., `web_core`) to formulate and run comprehensive unit and integration test cases for data coercion and function logic. -- Update the Gallery App to load samples from `specification/v1_0/json/catalogs/basic/examples/`. +- **Tests**: Look at existing reference implementations (e.g., `web_core`) to formulate and run comprehensive unit test cases for data coercion and function logic. diff --git a/specification/v1_0/docs/framework_adapter_spec.md b/specification/v1_0/docs/framework_adapter_spec.md new file mode 100644 index 0000000000..39eb3e8d20 --- /dev/null +++ b/specification/v1_0/docs/framework_adapter_spec.md @@ -0,0 +1,330 @@ +# A2UI Framework Adapter Specification + +This document describes the specification and architecture of an A2UI Framework-Specific Adapter (the View/Rendering Layer). The design defines how a framework-agnostic A2UI Core SDK (documented in the [A2UI Core SDK Specification](core_sdk_spec.md)) connects to native UI frameworks to paint the pixels. + +Both the core data structures and the rendering components interact with **Catalogs**. Within a catalog, the implementation follows a structured split: from the pure **Component Schema** (defined in the Core SDK) down to the **Framework-Specific Adapter** that renders native components (React, Angular, Flutter, SwiftUI, Jetpack Compose, iOS Views, Android Views, Vanilla DOM). + +--- + +## 1. Framework Adapter Overview + +The A2UI client architecture has a well-defined data flow that bridges language-agnostic data structures with native UI frameworks. + +1. **A2UI Messages** arrive from the server (JSON). +2. The **`MessageProcessor`** (part of Core SDK) parses these and updates the **`SurfaceModel`** (Agnostic State). +3. The **`Surface`** (Framework Entry View) listens to the `SurfaceModel` and begins rendering. +4. The `Surface` instantiates and renders individual **`ComponentImplementation`** nodes to build the UI tree. + +This establishes a fundamental split: + +- **The Framework-Agnostic Layer (Data Layer / Core SDK)**: Handles JSON parsing, state management, JSON pointers, and schemas. This logic is identical across all UI frameworks within a given language. +- **The Framework-Specific Layer (View Layer / Framework Adapter)**: Handles turning the structured state into actual pixels (React Nodes, Flutter Widgets, iOS Views). + +--- + +## 2. The View-Layer Interfaces + +At the heart of the A2UI framework-specific architecture are the interfaces that render components and manage the native UI lifecycle. + +### `ComponentImplementation` + +The framework-specific logic for rendering a component. It extends `ComponentApi` (defined in [Core SDK Specification](core_sdk_spec.md)) to include a `build` or `render` method. + +How this looks depends on the target framework's paradigm: + +#### Functional / Reactive Frameworks (e.g., Flutter, SwiftUI, React) + +```typescript +interface ComponentImplementation extends ComponentApi { + /** + * @param ctx The component's context containing its data and state. + * @param buildChild A closure provided by the surface to recursively build children. + */ + build( + ctx: ComponentContext, + buildChild: (id: string, basePath?: string) => NativeWidget, + ): NativeWidget; +} +``` + +#### Stateful / Imperative Frameworks (e.g., Vanilla DOM, Android Views) + +Because the catalog only holds a single "blueprint" of each `ComponentImplementation`, stateful frameworks need a way to instantiate individual objects for each component rendered on screen. + +```typescript +interface ComponentInstance { + mount(container: NativeElement): void; + update(ctx: ComponentContext): void; + unmount(): void; +} + +interface ComponentImplementation extends ComponentApi { + /** Creates a new stateful instance of this component type. */ + createInstance(ctx: ComponentContext): ComponentInstance; +} +``` + +### `Surface` + +The entrypoint widget/view for a specific framework. It is instantiated with a `SurfaceModel` (from the Core SDK). It listens to the model for lifecycle events and dynamically builds the UI tree, initiating the recursive rendering loop at the component with ID `root`. + +--- + +## 3. Component Implementation Strategies + +While the `ComponentImplementation` API dictates that a component must be able to `build()` or `mount()`, _how_ a developer connects that view to the reactive data model inside `ComponentContext` varies by language and framework capabilities. + +### Strategy 1: Direct / Binderless Implementation + +The most straightforward approach. The developer implements the `ComponentImplementation` and manually manages A2UI reactivity directly within the `build` method using the framework's native reactive tools (e.g., `StreamBuilder` in Flutter, or manual `useEffect` in React). + +_Example: Flutter Direct Implementation_ + +```dart +Widget build(ComponentContext context, ChildBuilderCallback buildChild) { + return StreamBuilder( + // Manually observe the dynamic value stream + stream: context.dataContext.observeDynamicValue(context.componentModel.properties['label']), + builder: (context, snapshot) { + return ElevatedButton( + onPressed: () => context.dispatchAction(context.componentModel.properties['action']), + child: Text(snapshot.data?.toString() ?? ''), + ); + } + ); +} +``` + +### Strategy 2: Binder-Based UI Components + +For complex applications, scattering manual A2UI subscription logic across all view components becomes repetitive and error-prone. The **Binder Layer** in the Core SDK abstractly resolves and evaluates reactive inputs into a standard stream of `ResolvedProps`. + +The framework-specific UI component simply subscribes to this generic stream and updates the rendering. + +### Strategy 3: Generic Binders for Dynamic Languages + +In highly dynamic ecosystems like TypeScript/JavaScript, we can completely automate the creation of wrappers that automatically bind binders to UI components, offering compile-time type-safety: + +```typescript +// Concept: The developer writes a simple, stateless UI component. +// The `props` argument is strictly inferred from the ButtonSchema binder. +const ReactButton = createReactComponent(ButtonBinder, ({ props, buildChild }) => { + return ( + + ); +}); +``` + +Because of the generic types flowing through the adapter, if the developer typos `props.action` as `props.onClick`, or treats `props.label` as an object instead of a string, the compiler will immediately flag a type error. + +--- + +## 4. Example: Framework-Specific Adapters + +The adapter acts as a wrapper that instantiates the binder, binds its output stream to the framework's state mechanism, injects structural rendering helpers (`buildChild`), and hooks into the native destruction lifecycle to call `dispose()`. + +### React Pseudo-Adapter + +```typescript +// Pseudo-code concept for a React adapter +function createReactComponent(binder, RenderComponent) { + return function ReactWrapper({ context, buildChild }) { + // Hook into component mount + const [props, setProps] = useState(binder.initialProps); + + useEffect(() => { + // Create binding on mount + const binding = binder.bind(context); + + // Subscribe to updates + const sub = binding.propsStream.subscribe(newProps => setProps(newProps)); + + // Cleanup on unmount + return () => { + sub.unsubscribe(); + binding.dispose(); + }; + }, [context]); + + return ; + } +} +``` + +### Angular Pseudo-Adapter + +```typescript +// Pseudo-code concept for an Angular adapter +@Component({ + selector: 'app-angular-wrapper', + imports: [MatButtonModule], + template: ` + @if (props(); as props) { + + } + `, +}) +export class AngularWrapper { + private binder = inject(BinderService); + private context = inject(ComponentContext); + + private bindingResource = resource({ + loader: async () => { + const binding = this.binder.bind(this.context); + + return { + instance: binding, + props: toSignal(binding.propsStream), // Convert Observable to Signal + }; + }, + }); + + props = computed(() => this.bindingResource.value()?.props() ?? null); + + constructor() { + inject(DestroyRef).onDestroy(() => { + this.bindingResource.value()?.instance.dispose(); + }); + } +} +``` + +--- + +## 5. Framework Binding Lifecycles & Traits + +Regardless of the implementation strategy chosen, the framework adapter or `ComponentImplementation` MUST strictly manage subscriptions to ensure performance and prevent memory leaks. + +### Contract of Ownership + +A crucial part of A2UI's architecture is understanding who "owns" the data layers. + +- **The Data Layer (Message Processor) owns the `ComponentModel`**. It creates, updates, and destroys the component's raw data state based on the incoming JSON stream. +- **The Framework Adapter owns the `ComponentContext` and `ComponentBinding`**. When the native framework decides to mount a component onto the screen (e.g., React runs `render`), the Framework Adapter creates the `ComponentContext` and passes it to the Binder. When the native framework unmounts the component, the Framework Adapter MUST call `binding.dispose()`. + +### Data Props vs. Structural Props + +It's important to distinguish between Data Props (like `label` or `value`) and Structural Props (like `child` or `children`). + +- **Data Props:** Handled entirely by the Binder. The adapter receives a stream of fully resolved values (e.g., `"Submit"` instead of a `DynamicString` path). Whenever a data value updates, the binder should emit a _new reference_ (e.g. a shallow copy of the props object) to ensure declarative frameworks that rely on strict equality (like React) correctly detect the change and trigger a re-render. +- **Structural Props:** The Binder does not attempt to resolve component IDs into actual UI trees. Instead, it outputs metadata for the children that need to be rendered. + - For a simple `ComponentId` (e.g., `Card.child`), it emits an object like `{ id: string, basePath: string }`. + - For a `ChildList` (e.g., `Column.children`), it evaluates the array. If the array is driven by a dynamic template bound to the data model, the binder must iterate over the array, using `context.dataContext.nested()` to generate a specific context for each index, and output a list of `ChildNode` streams. +- The framework adapter is then responsible for taking these node definitions and calling a framework-native `buildChild(id, basePath)` method recursively. + +> **Implementation Tip: Context Propagation** +> When implementing the recursive `buildChild` helper, ensure that it correctly inherits the _current_ component's data context path by default. If a nested component (like a Text field inside a List template) uses a relative path, it must resolve against the scoped path provided by its immediate structural parent (e.g., `/restaurants/0`), not the root path. Failing to propagate this context is a common cause of "empty" data in nested components. + +### Component Subscription Lifecycle Rules + +1. **Lazy Subscription**: Only bind and subscribe to data paths or property updates when the component is actually mounted/attached to the UI. +2. **Path Stability**: If a component's property changes via an `updateComponents` message, you MUST unsubscribe from the old path before subscribing to the new one. +3. **Destruction / Cleanup**: When a component is removed from the UI (e.g., via a `deleteSurface` message), the implementation MUST hook into its native lifecycle to dispose of all data model subscriptions. + +### Reactive Validation (`Checkable`) + +Interactive components that support the `checks` property should implement the `Checkable` trait. + +- **Aggregate Error Stream**: The component should subscribe to all `CheckRule` conditions defined in its properties. +- **UI Feedback**: It should reactively display the `message` of the first failing check as a validation error hint. +- **Action Blocking**: Actions (like `Button` clicks) should be reactively disabled or blocked if any validation check fails. + +--- + +## 6. Standard & Custom Component Overrides + +The standard A2UI Basic Catalog specifies a set of core components (Button, Text, Row, Column) and functions. + +### Strict API / Implementation Separation + +When building libraries that provide the Basic Catalog, separating the pure API from visual renderers is vital. + +- **Multi-Framework Code Reuse**: Allows core binders to be reused across different UI framework adapter libraries. +- **Developer Overrides**: By exposing the standard API definitions, developers adopting A2UI can easily swap in custom UI implementations (e.g., replacing the default `Button` with their company's internal Design System `Button`) without having to rewrite the complex A2UI validation, data binding, and capability generation logic. + +For a detailed walkthrough on how to visually and functionally implement each basic component and function, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md). + +--- + +## 7. The Gallery App Specification + +The Gallery App is a comprehensive development and debugging tool that serves as the reference environment for an A2UI renderer. It allows developers to visualize components, inspect the live data model, step through progressive rendering, and verify interaction logic. + +### UX Architecture + +The Gallery App must implement a three-column layout: + +1. **Left Column (Sample Navigation)**: A list of available A2UI samples. +2. **Center Column (Rendering & Messages)**: + - **Surface Preview**: Renders the active A2UI `Surface`. + - **JSON Message Stream**: Displays the list of A2UI JSON messages. + - **Interactive Stepper**: An "Advance" button allows processing messages one by one to verify progressive rendering. +3. **Right Column (Live Inspection)**: + - **Data Model Pane**: A live-updating view of the full Data Model. + - **Action Logs Pane**: A log of triggered actions and their context. + +### Integration Testing Requirements + +Every renderer implementation must include a suite of automated integration tests that utilize the Gallery App's logic to verify: + +- **Static Rendering**: Opening "Simple Text" renders correctly. +- **Layout Integrity**: "Row Layout" places elements correctly. +- **Two-Way Binding**: Typing in a TextField updates both the UI and the Data Model viewer simultaneously. +- **Reactive Logic**: Changes in one component dynamically update dependent components. +- **Action Context Scoping**: Actions emitted from nested templates (like Lists) contain correctly resolved data scopes. + +--- + +## 8. Agent Implementation Guide: Framework & UI Phases + +If you are an AI Agent tasked with building a new Renderer/Framework Adapter for A2UI, you MUST follow this strict, phased sequence of operations. + +### Phase 1: Context & Core Ingest + +Thoroughly review: + +- [A2UI Core SDK Specification](core_sdk_spec.md) (for state and message structures). +- `specification/v1_0/docs/basic_catalog_implementation_guide.md` (for rendering, typography, alignment, and spacing rules). + +### Phase 2: Key Architecture Decisions (Write a Plan Document) + +Create a comprehensive design document detailing: + +- **Component Architecture**: How will you define the `ComponentImplementation` base class or interface in this framework? +- **Surface Architecture**: How will the `Surface` framework entry point function to recursively build children? +- **Binding Strategy**: Will you use Strategy 1 (Direct), Strategy 2 (Predefined binders), or Strategy 3 (Automated TypeScript/Zod wrappers)? +- **STOP HERE. Ask the user for approval on this design document before proceeding.** + +### Phase 3: Framework-Specific Layer + +Implement the bridge between Core SDK models and native UI. + +- Define the concrete `ComponentImplementation` base class/interface. +- Implement the `Surface` view/widget that recurses through components. +- Implement subscription lifecycle management (lazy mounting, unmounting disposal, path stability). + +### Phase 4: Minimal Catalog Support + +Target the `minimal_catalog.json` first. + +- Implement the specific native UI rendering components for `Text`, `Row`, `Column`, `Button`, and `TextField`. +- Bundle these visual widgets into a catalog adapter. +- **Action**: Write unit tests verifying that properties update reactively in the UI when underlying data model changes. + +### Phase 5: Gallery Application (Milestone) + +Build the Gallery App following the requirements in **Section 7**. + +- Load JSON samples from `specification/v1_0/json/catalogs/minimal/examples/`. +- Verify progressive rendering, stepping, and visual reactivity. +- **STOP HERE. Ask the user for approval of the architecture and gallery application before proceeding.** + +### Phase 6: Basic Catalog Support + +Once the minimal architecture is proven robust, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md) and: + +- **Framework Library**: Implement all remaining UI widgets and traits (such as `Checkable`). +- **Tests**: Formulate and run comprehensive unit and integration test cases to verify static rendering, layout alignment, two-way bindings, and scoped actions. +- Update the Gallery App to load samples from `specification/v1_0/json/catalogs/basic/examples/`. diff --git a/specification/v1_0/docs/sdks_spec.md b/specification/v1_0/docs/sdks_spec.md new file mode 100644 index 0000000000..fa6308f831 --- /dev/null +++ b/specification/v1_0/docs/sdks_spec.md @@ -0,0 +1,62 @@ +# A2UI Unified SDK Architecture + +This document describes the unified architecture of the A2UI SDK ecosystem. To support modern, multi-paradigm generative UI applications, A2UI separates concerns into a modular, three-tiered structure: the **A2UI Core SDK**, the **Inference SDK**, and **Framework Adapters**. + +This architecture enables high code reuse, strict conformance, and unified behavior across all languages. Ensuring that we use a consistent codebase structure and terminology across languages also makes it easier to maintain the codebases together and add new features across languages using spec-driven development. + +## 1. Ecosystem Dependency Structure + +Historically, A2UI maintained separate, isolated codebases for server-side agent SDKs (which generate UI messages) and client-side rendering SDKs (which paint pixels). We are now building a unified, shared-code structure across each language: + +``` ++-------------------------------------------------------------+ +| FRAMEWORK ADAPTER | +| (Renders surface state to pixels using a native UI) | ++------------------------------+------------------------------+ + | + | (Depends on) + v ++-------------------------------------------------------------+ +| A2UI CORE SDK | +| (Maintains surface state, message processing, validation) | ++------------------------------+------------------------------+ + ^ + | (Depends on) + | ++-------------------------------------------------------------+ +| INFERENCE SDK | +| (Generates serverToClient messages using LLM engines) | ++-------------------------------------------------------------+ +``` + +## 2. Library Responsibilities + +### A2UI Core SDK (`a2ui_core` / `@a2ui/core`) + +**Availability:** Every target language (TS, Dart, Python, Kotlin, Swift, C++). +**Responsibility:** Provides the foundational, language-native representations of the official A2UI specifications. + +- **Protocol Models:** Strongly-typed classes representing Catalog declarations (`Catalog`, `ComponentApi`, `FunctionApi`) and A2UI message types (`ClientToServer`, `ServerToClient`, etc.). +- **Surface State Management:** Mutable models representing active UI surfaces (`SurfaceModel`, `ComponentModel`, `DataModel`). +- **Processing Layer (`MessageProcessor`):** Evaluates incoming JSON message arrays, resolves relative JSON pointers, and updates surface state models. +- **Validation:** Performs strict validation of message schema structures and reference checks. +- **Incremental Snapshotting:** Supports tracking and collapsing incremental updates into flat snapshots (e.g., `ComponentNode` trees). + +### Inference SDK (`a2ui_inference` / `@a2ui/inference`) + +**Availability:** All supported agent languages and select client-side languages (for local inference). +**Responsibility:** Guides Large Language Models (LLMs) to generate valid A2UI message payloads based on a defined component Catalog and active application context. + +- **Inference Strategies:** Standard structured generation schemes. +- **Prompt Construction:** Building prompt buffers and feeding component capability schemas dynamically to LLM contexts. +- **Message Parsing:** Safely extracting and repairing raw JSON envelopes from LLM completion streams. +- **Error Repair & Retries:** Programmatic fixing of malformed or invalid schemas. + +### Framework Adapters (`react_renderer`, `compose_renderer`, etc.) + +**Availability:** Every supported UI framework across client platforms. +**Responsibility:** Paints the state of a Core `SurfaceModel` onto physical screen pixels using native UI hierarchies. + +- **Framework Entry View (`Surface`):** Observes Core state and boots the recursive layout loop. +- **Component Renderers (`ComponentImplementation`):** Individual native views implementing visual specs (e.g., standard layout, text, or inputs from the Basic Catalog). +- **Lifecycle Bindings:** Standardizes lazy-mounting, reactive value propagation, and memory leak prevention (`dispose`).