From c89dba18d27e14d17d4955a792105a277335b75a Mon Sep 17 00:00:00 2001 From: Jacob Simionato Date: Tue, 28 Apr 2026 11:20:00 +0930 Subject: [PATCH 1/6] docs(v0.9): split renderer_guide.md into core SDK and UI adapter guides --- .../docs/core_sdk_implementation_guide.md | 250 ++++++ specification/v0_9/docs/renderer_guide.md | 722 ------------------ ..._framework_adapter_implementation_guide.md | 145 ++++ 3 files changed, 395 insertions(+), 722 deletions(-) create mode 100644 specification/v0_9/docs/core_sdk_implementation_guide.md delete mode 100644 specification/v0_9/docs/renderer_guide.md create mode 100644 specification/v0_9/docs/ui_framework_adapter_implementation_guide.md diff --git a/specification/v0_9/docs/core_sdk_implementation_guide.md b/specification/v0_9/docs/core_sdk_implementation_guide.md new file mode 100644 index 000000000..566876443 --- /dev/null +++ b/specification/v0_9/docs/core_sdk_implementation_guide.md @@ -0,0 +1,250 @@ +# A2UI Core SDK Implementation Guide + +This document describes the architecture and implementation requirements for an A2UI Core SDK. The Core SDK is a framework-agnostic library responsible for state management, protocol parsing, and logic evaluation. It is designed to be implemented in any programming language (client or server) to provide a consistent foundation for A2UI-powered applications. + +## 1. Role of the Core SDK + +The Core SDK handles the "brain" of the A2UI system. It manages language-agnostic data structures and bridges the raw JSON protocol with reactive state models. + +Its primary responsibilities include: +* **Message Processing**: Parsing and validating incoming A2UI JSON messages. +* **State Accumulation**: Maintaining the "Single Source of Truth" for UI surfaces, components, and data. +* **Data Binding**: Resolving JSON Pointer paths and managing reactive subscriptions to the Data Model. +* **Expression Evaluation**: Parsing and executing A2UI expressions (like `formatString`). +* **Logic Execution**: Providing a standard execution environment for catalog functions. +* **Capability Generation**: Translating internal schemas into standard JSON Schemas for AI agents. + +## 2. Framework-Agnostic Architecture + +The architecture emphasizes a clean separation between construct (the model) and visualization (the renderer). This layer follows the exact same design in all programming languages and does not require design work when porting to a new ecosystem. + +### Foundational Prerequisites + +The very first step in implementing a Core SDK is choosing two critical libraries that will dictate the ergonomics and performance of your implementation. + +#### 1. Choice of Schema Library +To represent and validate component and function APIs, the SDK requires a **Schema Library** (like **Zod** in TypeScript, **Pydantic** in Python, or **kotlinx.serialization** in Kotlin). +* **Requirement**: It MUST allow for programmatic definition of schemas. +* **Requirement**: It SHOULD support exporting these definitions to standard JSON Schema (required for generating client capabilities). +* **Fallback**: If no suitable library exists, you may use raw JSON Schema strings or native language structs with a manual validation layer. + +#### 2. Choice of Observable Library +A2UI is inherently reactive. The SDK needs an **Observable or Reactive Library** (like **Rx** variants, **Preact Signals**, or **Combine**) to handle state propagation. +* **Event Streams**: Needed for discrete, one-off events (e.g., `onSurfaceCreated`, `onAction`). These typically follow a standard `EventEmitter` pattern. +* **Stateful Streams (Signals)**: Needed for data paths and function results. These MUST hold a "current value" that can be read synchronously upon subscription and notify listeners of future changes. +* **Memory Management**: The chosen library MUST provide a clear mechanism to **unsubscribe** or dispose of listeners to prevent memory leaks in long-running sessions. + + +## 3. Protocol Models & Serialization + +The Core SDK defines strict, native type representations of the A2UI JSON schemas. It acts as a safe boundary between the raw network stream and internal state. + +### Required Data Structures +* **Server-to-Client Messages:** `A2uiMessage` (a union/protocol type), `CreateSurfaceMessage`, `UpdateComponentsMessage`, `UpdateDataModelMessage`, `DeleteSurfaceMessage`. +* **Client-to-Server Events:** `ActionMessage`, `ErrorMessage`. +* **Client Metadata:** `A2uiClientCapabilities`, `InlineCatalog`, `FunctionDefinition`, `ClientDataModel`. + +### JSON Serialization & Validation +* **Inbound (Parsing)**: The SDK must deserialize raw JSON into strongly-typed messages. If a payload violates the schema, it MUST throw an `A2uiValidationError` before reaching the state models. +* **Outbound (Stringifying)**: The SDK must serialize client events and capabilities from strict native types back into valid JSON. + +## 4. The State Model Layer + +The State Layer maintains a long-lived, mutable state object designed for high-performance updates. + +### Design Principles +1. **The "Add" Pattern**: Construction is separated from composition. Parent containers do not act as factories; they receive models to manage. +2. **Granular Reactivity**: Updates are isolated. + * **Structure Changes**: `SurfaceComponentsModel` notifies when items are added/removed. + * **Property Changes**: `ComponentModel` notifies when its specific configuration changes. + * **Data Changes**: `DataModel` notifies only subscribers to the specific path that changed. + +### State Models + +#### `SurfaceGroupModel` & `SurfaceModel` +The root containers for active surfaces and their catalogs, data, and components. + +```typescript +class SurfaceGroupModel { + addSurface(surface: SurfaceModel): void; + deleteSurface(id: string): void; + getSurface(id: string): SurfaceModel | undefined; + + readonly onSurfaceCreated: EventSource>; + readonly onSurfaceDeleted: EventSource; + readonly onAction: EventSource; +} + +class SurfaceModel { + readonly id: string; + readonly catalog: Catalog; + readonly dataModel: DataModel; + readonly componentsModel: SurfaceComponentsModel; + readonly theme?: Record; + readonly sendDataModel: boolean; + + readonly onAction: EventSource; + dispatchAction(payload: Record, sourceComponentId: string): Promise; +} +``` + +#### `SurfaceComponentsModel` & `ComponentModel` +Manages the adjacency list of component configurations. + +```typescript +class SurfaceComponentsModel { + get(id: string): ComponentModel | undefined; + addComponent(component: ComponentModel): void; + readonly onCreated: EventSource; + readonly onDeleted: EventSource; +} + +class ComponentModel { + readonly id: string; + readonly type: string; + get properties(): Record; + set properties(newProps: Record); + readonly onUpdated: EventSource; +} +``` + +#### `DataModel` +A dedicated store for application data supporting JSON Pointer ([RFC 6901]). + +**Implementation Rules**: +1. **Relative Paths**: A2UI extends JSON Pointer to support paths that do not start with `/` (e.g., `name`), resolving relative to the current scope. +2. **Auto-vivification**: When setting a path like `/a/b/0/c`, create intermediate segments. If a segment is numeric, initialize as an Array `[]`, otherwise an Object `{}`. +3. **Notification Strategy (Bubble & Cascade)**: Notify exact matches, bubble up to all parent paths, and cascade down to all nested descendant paths. +4. **Undefined Handling**: Setting an object key to `undefined` removes it. Setting an array index to `undefined` preserves length but empties the index. + +## 5. The Context & Evaluation Layer + +Transient objects created on-demand during rendering to handle evaluation scope and binding resolution. + +### `DataContext` +The primary interface for resolving `DynamicValue`s (literals, paths, function calls). + +```typescript +class DataContext { + readonly path: string; // Evaluation scope + resolveDynamicValue(v: DynamicValue): V; + subscribeDynamicValue(v: DynamicValue, onChange: (v: V | undefined) => void): Subscription; + nested(relativePath: string): DataContext; // Creates a child scope for templates +} +``` + +### `ComponentContext` +Pairs a component's specific configuration with its scoped `DataContext`. + +```typescript +class ComponentContext { + readonly componentModel: ComponentModel; + readonly dataContext: DataContext; + readonly surfaceComponents: SurfaceComponentsModel; // For cross-component inspection + dispatchAction(action: Record): Promise; +} +``` + +## 6. Message Processing (`MessageProcessor`) + +The "Controller" that accepts the raw stream, parses messages, and mutates models. + +### Client Data Model Synchronization +When `sendDataModel: true`, the SDK aggregates the full state of enabled surfaces. The **Transport Layer** calls `getClientDataModel()` before sending any message to the server to populate metadata (e.g., `a2uiClientDataModel`). + +### Capability Generation +To generate `a2uiClientCapabilities` (specifically `inlineCatalogs`): +1. **Translation**: Convert internal component, function, and theme schemas into raw JSON Schema. +2. **Envelope**: Wrap component schemas in the A2UI envelope (`allOf` containing `ComponentCommon`). +3. **REF: Tagging**: Shared types (like `DynamicString`) are "tagged" in their description (e.g., `REF:common_types.json#/$defs/DynamicString`). The processor must traverse generated schemas, strip the tag, and replace the node with a valid JSON Schema `$ref`. + +## 7. The Catalog & Function API + +A catalog defines the set of available components and functions. + +### `ComponentApi` +The framework-agnostic definition of a component. +```typescript +interface ComponentApi { + readonly name: string; + readonly schema: Schema; // Zod/Pydantic/etc. +} +``` + +### Functions +Functions accept statically resolved values as input and can return a static value or a reactive stream (Signal). +1. **Pure Logic**: Synchronous (e.g., `add`). +2. **External State**: Reactive streams (e.g., `clock()`). +3. **Effect Functions**: Side-effect handlers (e.g., `openUrl`) triggered by actions. + +### Expression Resolution (`formatString`) +Required logic for interpreting `${expression}` syntax. +* **Recursion**: Must use `DataContext.resolveDynamicValue()` to evaluate nested expressions. +* **Tokenization**: Distinguish between DataPaths (`${/path}`) and FunctionCalls (`${now()}`). + +## 8. Standards & Type Coercion + +### Implementation Separation +It is **crucial** to separate the pure API (the Schemas and `ComponentApi`) from the UI implementations. This allows a shared Core SDK to define the API once, while diverse UI Framework Adapters provide native view implementations. + +### Type Coercion Table +| Input Type | Target Type | Result | +| :------------------------- | :---------- | :---------------------------------------------------------------------- | +| `String` ("true", "false") | `Boolean` | `true` or `false` (case-insensitive). Any other string maps to `false`. | +| `Number` (non-zero) | `Boolean` | `true` | +| `Number` (0) | `Boolean` | `false` | +| `Any` | `String` | Locale-neutral string representation | +| `null` / `undefined` | `String` | `""` (empty string) | +| `null` / `undefined` | `Number` | `0` | +| `String` (numeric) | `Number` | Parsed numeric value or `0` | + +## 9. Phased Implementation Workflow + +Building a Core SDK requires a rigorous, test-driven approach. Since the SDK is framework-agnostic, you can build and test it entirely in isolation before touching any UI code. + +### Phase 1: Context Ingestion & Architecture +Review the protocol specifications (`a2ui_protocol.md`, `common_types.json`, `server_to_client.json`). Decide on your Schema Library and Observable/Reactive Library. Set up your unit testing framework. + +### Phase 2: Protocol Models & Serialization +Implement strict native types for all A2UI messages and metadata. Write the deserialization and validation logic. +* **Unit Tests (Exhaustive)**: + * Provide valid JSON strings for all message types and assert correct object instantiation. + * Provide invalid JSON (missing required fields, wrong types) and assert `A2uiValidationError` is thrown. + * Test client-to-server serialization (e.g., ensuring `A2uiClientAction` formats timestamps correctly). + +### Phase 3: The Data Model +Implement the `DataModel` class. This is the most algorithmically complex layer. +* **Unit Tests (Exhaustive)**: + * **Path Resolution**: Test setting/getting absolute paths (`/user/name`). + * **Auto-vivification**: Test setting deep paths (`/a/b/0/c`). Assert that `0` becomes an array and `b` becomes an object. + * **Deletion**: Test setting a path to `undefined`/`null`. Assert keys are removed from objects and indices are emptied in arrays. + * **Subscriptions**: Test that updating `/user/name` triggers subscribers for `/user/name` (exact), `/user` (bubble), and `/` (bubble), but not `/user/age` (sibling). + +### Phase 4: Component & Surface State Models +Implement `ComponentModel`, `SurfaceComponentsModel`, `SurfaceModel`, and `SurfaceGroupModel`. +* **Unit Tests (Exhaustive)**: + * Assert `SurfaceComponentsModel` properly adds, updates, and deletes components, emitting events for each. + * Test that replacing a component with a different `type` but the same `id` disposes the old model and creates a new one. + * Test `SurfaceGroupModel` lifecycle (adding/deleting surfaces and cascading disposal). + +### Phase 5: The Context & Evaluation Layer +Implement `DataContext` and `ComponentContext` to handle path scoping and dynamic value resolution. +* **Unit Tests (Exhaustive)**: + * **Scoping**: Test that `nested("child").path` resolves correctly against parent paths (handling trailing slashes). + * **Dynamic Resolution**: Test `resolveDynamicValue` with literals, DataBindings (`path`), and FunctionCalls (`call`). + * **Reactivity**: Test `subscribeDynamicValue` returning a stateful stream that updates when the underlying `DataModel` path is mutated. + +### Phase 6: Message Processing +Implement the `MessageProcessor` to act as the central controller. +* **Unit Tests (Exhaustive)**: + * Pass a sequence of `createSurface`, `updateComponents`, and `updateDataModel` messages. Assert the final state of the `SurfaceGroupModel`. + * Test that invalid message sequences (e.g., updating a surface before creating it) throw `A2uiStateError`. + * Test `getClientDataModel()` correctly aggregates data only for surfaces with `sendDataModel: true`. + +### Phase 7: Capabilities & Catalog Functions +Implement the schema translation logic for `a2uiClientCapabilities` and standard function execution (e.g., `formatString`). +* **Unit Tests (Exhaustive)**: + * **Capabilities**: Define a mock ComponentApi, generate capabilities, and assert the resulting JSON Schema is valid and that `REF:` tags are properly stripped and converted to `$ref` objects. + * **Functions**: Test `formatString` with mixed static text, absolute paths, relative paths, and nested function calls. Ensure missing values fail gracefully or coerce to empty strings. + +[RFC 6901]: https://datatracker.ietf.org/doc/html/rfc6901 diff --git a/specification/v0_9/docs/renderer_guide.md b/specification/v0_9/docs/renderer_guide.md deleted file mode 100644 index 219b0f23a..000000000 --- a/specification/v0_9/docs/renderer_guide.md +++ /dev/null @@ -1,722 +0,0 @@ -# Unified Architecture & Implementation Guide - -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. - -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. - -## 1. Unified Architecture 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`** 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. -* **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. - -#### Combined Core + Framework Libraries (e.g., Swift + SwiftUI) -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. -* **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 - -At the heart of the A2UI architecture are five key interfaces that connect the data to the screen. - -### `ComponentApi` -The framework-agnostic definition of a component. It defines the name and the exact JSON schema footprint of the component, without any rendering logic. It acts as the single source of truth for the component's contract. - -```typescript -interface ComponentApi { - /** The name of the component as it appears in the A2UI JSON (e.g., 'Button'). */ - readonly name: string; - /** The technical definition used for validation and generating client capabilities. */ - readonly schema: Schema; -} -``` - -### `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. -* **`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 - -## 3. The Core Data Layer (Detailed Specifications) - -The Data Layer maintains a long-lived, mutable state object. This layer follows the exact same design in all programming languages and **does not require design work when porting to a new framework**. - -### Prerequisites - -To implement the Data Layer effectively, your target environment needs two foundational utilities: - -#### 1. Schema Library -To represent and validate component and function APIs, the Data Layer requires a **Schema Library** (like **Zod** in TypeScript or **Pydantic** in Python) that allows for programmatic definition of schemas and the ability to export them to standard JSON Schema. If no suitable library exists, raw JSON Schema strings or `Codable` structs can be used. - -#### 2. Observable Library -A2UI relies on standard observer patterns. The Data Layer needs two types of reactivity: -* **Event Streams**: Simple publish/subscribe mechanisms for discrete events (e.g., `onSurfaceCreated`, `onAction`). -* **Stateful Streams (Signals)**: Reactive variables that hold an initial value synchronously upon subscription, and notify listeners of future changes (e.g., DataModel paths, function results). Crucially, the subscription must provide a clear mechanism to **unsubscribe** (e.g., a `dispose()` method) to prevent memory leaks. - -### Design Principles - -#### 1. The "Add" Pattern for Composition -We strictly separate **construction** from **composition**. Parent containers do not act as factories for their children. -```typescript -const child = new ChildModel(config); -parent.addChild(child); -``` - -#### 2. Standard Observer Pattern -Models must provide a mechanism for the rendering layer to observe changes. -1. **Low Dependency**: Prefer "lowest common denominator" mechanisms. -2. **Multi-Cast**: Support multiple listeners registered simultaneously. -3. **Unsubscribe Pattern**: There MUST be a clear way to stop listening. -4. **Payload Support**: Communicate specific data updates and lifecycle events. -5. **Consistency**: Used uniformly across `SurfaceGroupModel` (lifecycle), `SurfaceModel` (actions), `SurfaceComponentsModel` (lifecycle), `ComponentModel` (updates), and `DataModel` (data changes). - -#### 3. Granular Reactivity -The model is designed to support high-performance rendering through granular updates. -* **Structure Changes**: The `SurfaceComponentsModel` notifies when items are added/removed. -* **Property Changes**: The `ComponentModel` notifies when its specific configuration changes. -* **Data Changes**: The `DataModel` notifies only subscribers to the specific path that changed. - -### Protocol Models & Serialization - -The framework-agnostic layer is responsible for defining strict, native type representations of the A2UI JSON schemas. Renderers should not pass raw generic dictionaries (like `Map` or `Record`) directly into the state layer. - -Developers must create data classes, structs, or interfaces (e.g., `data class` in Kotlin, `Codable struct` in Swift, or Zod-validated `interface` in TypeScript) that perfectly mirror the official JSON specifications. This creates a safe boundary between the raw network stream and the internal state models. - -**Required Data Structures:** -* **Server-to-Client Messages:** `A2uiMessage` (a union/protocol type), `CreateSurfaceMessage`, `UpdateComponentsMessage`, `UpdateDataModelMessage`, `DeleteSurfaceMessage`. -* **Client-to-Server Events:** `ClientEvent` (a union/protocol type), `ActionMessage`, `ErrorMessage`. -* **Client Metadata:** `A2uiClientCapabilities`, `InlineCatalog`, `FunctionDefinition`, `ClientDataModel`. - -**JSON Serialization & Validation:** -* **Inbound (Parsing)**: The core library must provide a mechanism to deserialize a raw JSON string into a strongly-typed `A2uiMessage`. If the payload violates the A2UI JSON schema, this layer must throw an `A2uiValidationError` *before* the message reaches the state models. -* **Outbound (Stringifying)**: The core library must serialize client-to-server events and capabilities from their strict native types back into valid JSON strings to hand off to the transport layer. - -### The State Models - -#### SurfaceGroupModel & SurfaceModel -The root containers for active surfaces and their catalogs, data, and components. - -```typescript -interface SurfaceLifecycleListener { - onSurfaceCreated?: (s: SurfaceModel) => void; - onSurfaceDeleted?: (id: string) => void; -} - -class SurfaceGroupModel { - addSurface(surface: SurfaceModel): void; - deleteSurface(id: string): void; - getSurface(id: string): SurfaceModel | undefined; - - readonly onSurfaceCreated: EventSource>; - readonly onSurfaceDeleted: EventSource; - readonly onAction: EventSource; -} - -/** - * Matches 'action' in specification/v0_9/json/client_to_server.json. - */ -interface A2uiClientAction { - name: string; - surfaceId: string; - sourceComponentId: string; - timestamp: string; // ISO 8601 - context: Record; -} - -type ActionListener = (action: A2uiClientAction) => void | Promise; - -class SurfaceModel { - readonly id: string; -... - readonly catalog: Catalog; - readonly dataModel: DataModel; - readonly componentsModel: SurfaceComponentsModel; - readonly theme?: Record; - /** If true, the client should send the full data model with actions. */ - readonly sendDataModel: boolean; - - readonly onAction: EventSource; - /** - * Dispatches an action from this surface. - * @param payload The raw action event from the component. - * @param sourceComponentId The ID of the component that triggered the action. - */ - dispatchAction(payload: Record, sourceComponentId: string): Promise; -} -``` - -#### `SurfaceComponentsModel` & `ComponentModel` -Manages the raw JSON configuration of components in a flat map. - -```typescript -class SurfaceComponentsModel { - get(id: string): ComponentModel | undefined; - addComponent(component: ComponentModel): void; - - readonly onCreated: EventSource; - readonly onDeleted: EventSource; -} - -class ComponentModel { - readonly id: string; - readonly type: string; // Component name (e.g. 'Button') - - get properties(): Record; - set properties(newProps: Record); - - readonly onUpdated: EventSource; -} -``` - -#### `DataModel` -A dedicated store for application data. - -```typescript -interface Subscription { - readonly value: T | undefined; // Latest evaluated value - unsubscribe(): void; -} - -class DataModel { - get(path: string): any; // Resolve JSON Pointer to value - set(path: string, value: any): void; // Atomic update at path - subscribe(path: string, onChange: (v: T | undefined) => void): Subscription; // Reactive path monitoring - dispose(): void; -} -``` - -**JSON Pointer Implementation Rules**: -1. **A2UI Extension**: A2UI extends JSON Pointer to support **Relative Paths** that do not start with a forward slash `/` (e.g., `name` vs `/name`). These resolve relative to the current evaluation scope. -2. **Auto-typing (Auto-vivification)**: When setting a value at a nested path (e.g., `/a/b/0/c`), create intermediate segments. If the next segment is numeric (`0`), initialize as an Array `[]`, otherwise an Object `{}`. -3. **Notification Strategy (Bubble & Cascade)**: Notify exact matches, bubble up to all parent paths, and cascade down to all nested descendant paths. -4. **Undefined Handling**: Setting an object key to `undefined` removes the key. Setting an array index to `undefined` preserves length but empties the index (sparse array). - -**Type Coercion Standards**: -| Input Type | Target Type | Result | -| :------------------------- | :---------- | :---------------------------------------------------------------------- | -| `String` ("true", "false") | `Boolean` | `true` or `false` (case-insensitive). Any other string maps to `false`. | -| `Number` (non-zero) | `Boolean` | `true` | -| `Number` (0) | `Boolean` | `false` | -| `Any` | `String` | Locale-neutral string representation | -| `null` / `undefined` | `String` | `""` (empty string) | -| `null` / `undefined` | `Number` | `0` | -| `String` (numeric) | `Number` | Parsed numeric value or `0` | - -#### The Context Layer -Transient objects created on-demand during rendering to solve "scope" and binding resolution. - -```typescript -class DataContext { - constructor(dataModel: DataModel, path: string); - readonly path: string; - set(path: string, value: unknown): void; - resolveDynamicValue(v: DynamicValue): V; - subscribeDynamicValue(v: DynamicValue, onChange: (v: V | undefined) => void): Subscription; - nested(relativePath: string): DataContext; -} - -class ComponentContext { - constructor(surface: SurfaceModel, componentId: string, basePath?: string); - readonly componentModel: ComponentModel; - readonly dataContext: DataContext; - readonly surfaceComponents: SurfaceComponentsModel; // The escape hatch - dispatchAction(action: Record): Promise; -} -``` - -*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`) -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. - -```typescript -class MessageProcessor { - readonly model: SurfaceGroupModel; - - constructor(catalogs: Catalog[], actionHandler: ActionListener); - - // Accepts validated, strongly-typed message objects, not raw JSON - processMessages(messages: A2uiMessage[]): void; - addLifecycleListener(l: SurfaceLifecycleListener): () => void; - - // Returns a strictly typed capabilities object ready for JSON serialization - getClientCapabilities(options?: CapabilitiesOptions): A2uiClientCapabilities; - - /** - * Returns the aggregated data model for all surfaces that have 'sendDataModel' enabled. - * This should be used by the transport layer to populate metadata (e.g., 'a2uiClientDataModel'). - */ - getClientDataModel(): A2uiClientDataModel | undefined; -} -``` - -#### 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. - -**Implementation Flow:** -1. The `MessageProcessor` tracks the `sendDataModel` flag for each surface. -2. The `getClientDataModel()` method iterates over all active surfaces and returns a map of data models for those where the flag is enabled. -3. The **Transport Layer** (e.g., A2A, MCP) calls `getClientDataModel()` before sending any message to the server. -4. If a non-empty data model map is returned, it is included in the transport's metadata field (e.g., `a2uiClientDataModel` in A2A metadata). - -* **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 -To dynamically generate the `a2uiClientCapabilities` payload (specifically `inlineCatalogs`), the processor must convert internal component schemas into valid JSON Schemas. - -**Schema Types Location**: Foundational schema types *should* be defined in a dedicated directory like `schema`. You can see the `renderers/web_core/src/v0_9/schema/common-types.ts` file in the reference web implementation as an example. - -**Detectable Common Types**: Shared definitions (like `DynamicString`) must emit external JSON Schema `$ref` pointers. This is achieved by "tagging" the schemas using their `description` property (e.g., `REF:common_types.json#/$defs/DynamicString`). - -When `getClientCapabilities()` converts internal schemas to generate `inlineCatalogs`: -1. **Components**: Translate each component schema into a raw JSON Schema. Wrap it in the standard A2UI component envelope (`allOf` containing `ComponentCommon`). -2. **Functions**: Map each function in the catalog to a `FunctionDefinition` object, converting its argument schema to JSON Schema. -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 - -A catalog groups component definitions and function definitions together, along with an optional theme schema. - -```typescript -interface FunctionApi { - readonly name: string; - readonly returnType: 'string' | 'number' | 'boolean' | 'array' | 'object' | 'any' | 'void'; - readonly schema: Schema; // The expected arguments -} - -/** - * A function implementation. Splitting API from Implementation is less critical than - * for components because functions are framework-agnostic, but it allows for - * re-using API definitions across different implementation providers. - */ -interface FunctionImplementation extends FunctionApi { - // Executes the function logic. Accepts static inputs, returns a value or a reactive stream. - execute(args: Record, context: DataContext): unknown | Observable; -} - -class Catalog { - readonly id: string; // Unique catalog URI - readonly components: ReadonlyMap; - readonly functions?: ReadonlyMap; - readonly themeSchema?: Schema; - - constructor(id: string, components: T[], functions?: FunctionImplementation[], themeSchema?: Schema) { - // Initializes the properties - } -} -``` - -**Function Implementation Details**: -Functions in A2UI accept statically resolved values as input arguments (not observable streams). However, they can return an observable stream (or Signal) to provide reactive updates to the UI, or they can simply return a static value synchronously. - -Functions generally fall into a few common patterns: -1. **Pure Logic (Synchronous)**: Functions like `add` or `concat`. Their logic is immediate and depends only on their inputs. They typically return a static value. -2. **External State (Reactive)**: Functions like `clock()` or `networkStatus()`. These return long-lived streams that push updates to the UI independently of data model changes. -3. **Effect Functions**: Side-effect handlers (e.g., `openUrl`, `closeModal`) that return `void`. These are triggered by user actions rather than interpolation. - -If a function returns a reactive stream, it MUST use an idiomatic listening mechanism that supports standard unsubscription. To properly support an AI agent, functions SHOULD include a schema to generate accurate client capabilities. - -### Composing Your Own Catalog -You can define your own catalog by composing components and functions that reflect your design system. While you can build a catalog entirely from scratch, you can also import or combine definitions with the Basic Catalog to save time. - -*Example of composing a catalog:* -```python -# Pseudocode -myCustomCatalog = Catalog( - id="https://mycompany.com/catalogs/custom_catalog.json", - functions=basicCatalog.functions, - components=basicCatalog.components + [MyCompanyLogoComponent()], - themeSchema=basicCatalog.themeSchema # Inherit theme schema -) -``` - ---- - -## 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 -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. - -```typescript -export interface ComponentBinding { - readonly propsStream: StatefulStream; // e.g. BehaviorSubject - dispose(): void; // Cleans up all underlying data model subscriptions -} - -export interface ComponentBinder { - readonly schema: Schema; - bind(context: ComponentContext): ComponentBinding; -} -``` - -### Strategy 3: 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. - -This provides the ultimate "happy path" developer experience. The developer writes a simple, stateless UI component that receives native types, completely abstracted from A2UI's internals. - -```typescript -// 1. The framework adapter infers the prop types from the Binder's Schema. -// The raw `DynamicString` label and `Action` object have been automatically -// resolved into a static `string` and a callable `() => void` function. - -// Conceptually, the inferred type looks like this: -interface ButtonResolvedProps { - 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 - -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, it is **crucial** to separate the pure API (the Schemas and `ComponentApi`/`FunctionApi` definitions) from the actual UI implementations. - -* **Multi-Framework Code Reuse**: In ecosystems like the Web, this allows a shared `web_core` library to define the Basic Catalog API and Binders once, while separate packages (`react_renderer`, `angular_renderer`) provide the native view implementations. -* **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). - -### Strongly-Typed Catalog Implementations -To ensure all components are properly implemented and match the exact API signature, platforms with strong type systems should utilize their advanced typing features. This ensures that a provided renderer not only exists, but its `name` and `schema` strictly match the official Catalog Definition, catching mismatches at compile time rather than runtime. - -#### Statically Typed Languages (e.g. Kotlin/Swift) -In languages like Kotlin, you can define a strict interface or class that demands concrete instances of the specific component APIs defined by the Core Library. - -```kotlin -// The Core Library defines the exact shape of the catalog -class BasicCatalogImplementations( - val button: ButtonApi, // Must be an instance of the ButtonApi class - val text: TextApi, - 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) -In TypeScript, we can use intersection types to force the framework renderer to intersect with the exact definition. - -```typescript -// Concept: Forcing implementations to match the spec -type BasicCatalogImplementations = { - Button: ComponentImplementation & { name: "Button", schema: Schema }, - Text: ComponentImplementation & { name: "Text", schema: Schema }, - 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`) -The Basic Catalog requires a `formatString` function capable of interpreting `${expression}` syntax within string properties. - -**Implementation Requirements**: -1. **Recursion**: The implementation MUST use `DataContext.resolveDynamicValue()` or `DataContext.subscribeDynamicValue()` to recursively evaluate nested expressions or function calls (e.g., `${formatDate(value:${/date})}`). -2. **Tokenization**: Distinguish between DataPaths (e.g., `${/user/name}`) and FunctionCalls (e.g., `${now()}`). -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 - -If you are an AI Agent tasked with building a new renderer for A2UI, you MUST follow this strict, phased sequence of operations. - -### 1. Context to Ingest - -Thoroughly review: -* `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) - - -### 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? -* **STOP HERE. Ask the user for approval on this design document before proceeding.** - -### 3. Core Model Layer -Implement the framework-agnostic Data Layer (Section 3). -* Implement event streams and stateful signals. -* Implement strict Protocol Models (`A2uiMessage`, `A2uiClientCapabilities`, etc.) with JSON serialization/deserialization and schema validation logic. -* Implement `DataModel`, ensuring correct JSON pointer resolution and the cascade/bubble notification strategy. -* Implement `ComponentModel`, `SurfaceComponentsModel`, `SurfaceModel`, and `SurfaceGroupModel`. -* Implement `DataContext` and `ComponentContext`. -* 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 -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/`. diff --git a/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md b/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md new file mode 100644 index 000000000..a9c7867c6 --- /dev/null +++ b/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md @@ -0,0 +1,145 @@ +# A2UI UI Framework Adapter Implementation Guide + +This document describes how to implement an A2UI renderer for a specific UI framework (e.g., React, Flutter, Angular, SwiftUI). The Framework Adapter bridges the framework-agnostic **Core SDK** with native UI components to paint pixels on the screen. + +## 1. Role of the Framework Adapter + +The Framework Adapter handles the "Body" of the A2UI system. It consumes the reactive state provided by the Core SDK and transforms it into native widget trees. + +Its primary responsibilities include: +* **Rendering Loop**: Managing the recursive construction of the UI tree. +* **Lifecycle Management**: Mounting and unmounting components and cleaning up subscriptions. +* **Action Handling**: Connecting native UI events (clicks, input) to A2UI actions. +* **Component Mapping**: Dispatching component types to their native implementations. + +## 2. The Rendering Architecture + +The rendering flow follows a well-defined path: +1. **The `Surface` Entry Point**: A native widget/view is instantiated with a `SurfaceModel`. +2. **Observation**: The `Surface` listens to the `SurfaceModel` for structure changes. +3. **Recursion**: The `Surface` initiates rendering at the component with ID `root`. +4. **Component Rendering**: Each component uses its `ComponentContext` to resolve data and recursively call `buildChild(id)` for its children. + +### The recursive `buildChild` helper +When implementing the recursive renderer, ensure it correctly propagates the data context path. 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 parent (e.g., `/restaurants/0`), not the root path. + +## 3. Component Implementation + +Each A2UI component is defined by its implementation interface. + +### Functional / Reactive Frameworks (e.g., Flutter, React) +```typescript +interface ComponentImplementation extends ComponentApi { + /** + * @param ctx The component's context containing its data and state. + * @param buildChild A closure to recursively build children. + */ + build(ctx: ComponentContext, buildChild: (id: string, basePath?: string) => NativeWidget): NativeWidget; +} +``` + +### Stateful / Imperative Frameworks (e.g., Vanilla DOM) +Stateful frameworks need a way to instantiate individual objects for each rendered component. +```typescript +interface ComponentInstance { + mount(container: NativeElement): void; + update(ctx: ComponentContext): void; + unmount(): void; +} + +interface ComponentImplementation extends ComponentApi { + createInstance(ctx: ComponentContext): ComponentInstance; +} +``` + +## 4. Implementation Strategies + +### Strategy 1: Direct / Binderless Implementation +The developer manually manages A2UI reactivity within the `build` method using native reactive tools (e.g., `useEffect` in React or `StreamBuilder` in Flutter). + +### Strategy 2: The Binder Layer Pattern +To avoid repetitive subscription logic, use an intermediate **Binder Layer**. It transforms raw A2UI properties into a single stream of strongly-typed `ResolvedProps`. + +```typescript +export interface ComponentBinding { + readonly propsStream: StatefulStream; + dispose(): void; // Cleans up data model subscriptions +} +``` + +### Strategy 3: Generic Binders (Dynamic Languages) +In languages with powerful reflection (like TypeScript), the Binder Layer can be automated. A generic factory inspects a component's schema and automatically creates data model subscriptions, allowing developers to write simple, stateless UI components. + +## 5. Reactivity & Lifecycle Rules + +Regardless of strategy, implementations MUST strictly manage subscriptions: +1. **Lazy Subscription**: Only bind and subscribe to data when the component is actually mounted/attached to the UI. +2. **Ownership**: + * The **Core SDK** owns the `ComponentModel` (raw data state). + * The **Framework Adapter** owns the `ComponentContext` and `ComponentBinding`. +3. **Path Stability**: If a property changes via an `updateComponents` message, you MUST unsubscribe from the old path before subscribing to the new one. +4. **Destruction**: When a component or surface is removed, the adapter MUST dispose of all data model subscriptions to prevent memory leaks. + +## 6. Advanced Framework Traits + +### Data Props vs. Structural Props +* **Data Props (e.g., `label`)**: Handled by the Binder/Adapter. The view receives fully resolved values. Whenever data updates, the binder should emit a *new reference* (shallow copy) to trigger declarative re-renders. +* **Structural Props (e.g., `children`)**: The binder outputs metadata (`{ id, basePath }`). The adapter takes these and calls the framework-native `buildChild` method recursively. + +### Reactive Validation (`Checkable`) +Components supporting the `checks` property should implement the `Checkable` trait: +* **UI Feedback**: Reactively display the `message` of the first failing check. +* **Action Blocking**: Actions (like Button clicks) should be disabled if validation fails. + +## 7. Strongly-Typed Catalogs + +Platforms with strong type systems should utilize their features to ensure an adapter renderer strictly matches the official `ComponentApi` (name and schema). This catches spelling or schema mismatches at compile time. + +## 8. Development Tools: The Gallery App + +The Gallery App is the reference environment for an A2UI renderer. + +### UX Architecture +* **Left**: Sample Navigation. +* **Center**: Surface Preview, JSON Message Stream, and an "Advance" stepper for progressive rendering verification. +* **Right**: Live Data Model inspector and Action Logs. + +### Integration Testing Requirements +Every renderer must verify: +* **Layout Integrity**: Correct placement of Row/Column elements. +* **Two-Way Binding**: TextField updates reflected in the Data Model viewer. +* **Context Scoping**: Correct data resolution in nested templates (like Lists). + +## 9. Phased Implementation Workflow + +If you are building a new A2UI renderer, follow this strict, phased sequence of operations. + +### Phase 1: Implement or Adopt a Core SDK +The framework adapter relies entirely on a stable, framework-agnostic data layer. +* If a Core SDK already exists for your language (e.g., `@a2ui/web_core` for JS/TS), add it as a dependency. +* If one does not exist, you **must** build it first. Follow the strict, test-driven phases outlined in the [Core SDK Implementation Guide](core_sdk_implementation_guide.md) before writing any UI code. + +### Phase 2: Key Architecture Decisions +Before writing UI code, create a design document detailing: +* **Component Architecture**: Define the `ComponentImplementation` API for your specific framework. +* **Surface Architecture**: Design how the `Surface` entry point will recursively build children and propagate context. +* **Binding Strategy**: Decide between a Generic Binder Layer (recommended for dynamic languages) or a Direct/Binderless implementation. + +### Phase 3: Framework-Specific Layer +Implement the bridge between your models and the native UI. +* Implement the `Surface` widget/view. +* Establish lazy subscription mounting and disposal lifecycles to prevent memory leaks. + +### Phase 4: Minimal Catalog Support +Implement native UI support for a minimal set of components to verify the architecture: +* **Components**: `Text`, `Row`, `Column`, `Button`, `TextField`. +* **Validation**: Verify that native properties update reactively when the underlying Core SDK data changes. + +### Phase 5: Development Tools (Gallery App) +Build the **Gallery App** as described in Section 8. This tool is essential for debugging progressive rendering and two-way interaction logic before adding more complex components. + +### Phase 6: Basic Catalog Support +Once the core architecture is stable, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md) to: +* Implement the full suite of Basic Catalog widgets. +* Bind all standard functions (arithmetic, logical, formatting) to the native UI components. + From 5abb29d954d2afcc017711513d338af4f87cb29a Mon Sep 17 00:00:00 2001 From: Jacob Simionato Date: Tue, 28 Apr 2026 11:27:09 +0930 Subject: [PATCH 2/6] docs(v0.9): restore detailed code snippets lost during document split --- .../docs/core_sdk_implementation_guide.md | 86 +++++++++++- ..._framework_adapter_implementation_guide.md | 130 ++++++++++++++++++ 2 files changed, 209 insertions(+), 7 deletions(-) diff --git a/specification/v0_9/docs/core_sdk_implementation_guide.md b/specification/v0_9/docs/core_sdk_implementation_guide.md index 566876443..5dcba42af 100644 --- a/specification/v0_9/docs/core_sdk_implementation_guide.md +++ b/specification/v0_9/docs/core_sdk_implementation_guide.md @@ -18,6 +18,22 @@ Its primary responsibilities include: The architecture emphasizes a clean separation between construct (the model) and visualization (the renderer). This layer follows the exact same design in all programming languages and does not require design work when porting to a new ecosystem. +### 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. + +#### Static Languages (e.g., Kotlin, Swift, Dart) +In statically typed languages, runtime reflection is often limited or discouraged for performance reasons. +* **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. +* **Framework Library (e.g., `compose_renderer`)**: Uses the predefined Binders to connect to native UI state and implements the actual visual components. + +#### Combined Core + Framework Libraries (e.g., Swift + SwiftUI) +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. The generic `ComponentContext` and the framework-specific adapter logic are often tightly integrated. + ### Foundational Prerequisites The very first step in implementing a Core SDK is choosing two critical libraries that will dictate the ergonomics and performance of your implementation. @@ -54,7 +70,13 @@ The State Layer maintains a long-lived, mutable state object designed for high-p ### Design Principles 1. **The "Add" Pattern**: Construction is separated from composition. Parent containers do not act as factories; they receive models to manage. -2. **Granular Reactivity**: Updates are isolated. +2. **Standard Observer Pattern**: Models must provide a mechanism for the rendering layer to observe changes. + 1. **Low Dependency**: Prefer "lowest common denominator" mechanisms. + 2. **Multi-Cast**: Support multiple listeners registered simultaneously. + 3. **Unsubscribe Pattern**: There MUST be a clear way to stop listening. + 4. **Payload Support**: Communicate specific data updates and lifecycle events. + 5. **Consistency**: Used uniformly across `SurfaceGroupModel` (lifecycle), `SurfaceModel` (actions), `SurfaceComponentsModel` (lifecycle), `ComponentModel` (updates), and `DataModel` (data changes). +3. **Granular Reactivity**: Updates are isolated. * **Structure Changes**: `SurfaceComponentsModel` notifies when items are added/removed. * **Property Changes**: `ComponentModel` notifies when its specific configuration changes. * **Data Changes**: `DataModel` notifies only subscribers to the specific path that changed. @@ -65,6 +87,22 @@ The State Layer maintains a long-lived, mutable state object designed for high-p The root containers for active surfaces and their catalogs, data, and components. ```typescript +interface SurfaceLifecycleListener { + onSurfaceCreated?: (s: SurfaceModel) => void; + onSurfaceDeleted?: (id: string) => void; +} + +/** + * Matches 'action' in specification/v0_9/json/client_to_server.json. + */ +interface A2uiClientAction { + name: string; + surfaceId: string; + sourceComponentId: string; + timestamp: string; // ISO 8601 + context: Record; +} + class SurfaceGroupModel { addSurface(surface: SurfaceModel): void; deleteSurface(id: string): void; @@ -111,6 +149,20 @@ class ComponentModel { #### `DataModel` A dedicated store for application data supporting JSON Pointer ([RFC 6901]). +```typescript +interface Subscription { + readonly value: T | undefined; // Latest evaluated value + unsubscribe(): void; +} + +class DataModel { + get(path: string): any; // Resolve JSON Pointer to value + set(path: string, value: any): void; // Atomic update at path + subscribe(path: string, onChange: (v: T | undefined) => void): Subscription; // Reactive path monitoring + dispose(): void; +} +``` + **Implementation Rules**: 1. **Relative Paths**: A2UI extends JSON Pointer to support paths that do not start with `/` (e.g., `name`), resolving relative to the current scope. 2. **Auto-vivification**: When setting a path like `/a/b/0/c`, create intermediate segments. If a segment is numeric, initialize as an Array `[]`, otherwise an Object `{}`. @@ -140,11 +192,13 @@ Pairs a component's specific configuration with its scoped `DataContext`. class ComponentContext { readonly componentModel: ComponentModel; readonly dataContext: DataContext; - readonly surfaceComponents: SurfaceComponentsModel; // For cross-component inspection + readonly surfaceComponents: SurfaceComponentsModel; // The escape hatch dispatchAction(action: Record): Promise; } ``` +*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. + ## 6. Message Processing (`MessageProcessor`) The "Controller" that accepts the raw stream, parses messages, and mutates models. @@ -160,7 +214,7 @@ To generate `a2uiClientCapabilities` (specifically `inlineCatalogs`): ## 7. The Catalog & Function API -A catalog defines the set of available components and functions. +A catalog groups component definitions and function definitions together, along with an optional theme schema. ### `ComponentApi` The framework-agnostic definition of a component. @@ -172,10 +226,28 @@ interface ComponentApi { ``` ### Functions -Functions accept statically resolved values as input and can return a static value or a reactive stream (Signal). -1. **Pure Logic**: Synchronous (e.g., `add`). -2. **External State**: Reactive streams (e.g., `clock()`). -3. **Effect Functions**: Side-effect handlers (e.g., `openUrl`) triggered by actions. +Functions accept statically resolved values as input arguments (not observable streams). However, they can return an observable stream (or Signal) to provide reactive updates to the UI, or they can simply return a static value synchronously. + +Functions generally fall into a few common patterns: +1. **Pure Logic (Synchronous)**: Functions like `add` or `concat`. Their logic is immediate and depends only on their inputs. They typically return a static value. +2. **External State (Reactive)**: Functions like `clock()` or `networkStatus()`. These return long-lived streams that push updates to the UI independently of data model changes. +3. **Effect Functions**: Side-effect handlers (e.g., `openUrl`, `closeModal`) that return `void`. These are triggered by user actions rather than interpolation. + +If a function returns a reactive stream, it MUST use an idiomatic listening mechanism that supports standard unsubscription. To properly support an AI agent, functions SHOULD include a schema to generate accurate client capabilities. + +### Composing Your Own Catalog +You can define your own catalog by composing components and functions that reflect your design system. While you can build a catalog entirely from scratch, you can also import or combine definitions with the Basic Catalog to save time. + +*Example of composing a catalog:* +```python +# Pseudocode +myCustomCatalog = Catalog( + id="https://mycompany.com/catalogs/custom_catalog.json", + functions=basicCatalog.functions, + components=basicCatalog.components + [MyCompanyLogoComponent()], + themeSchema=basicCatalog.themeSchema # Inherit theme schema +) +``` ### Expression Resolution (`formatString`) Required logic for interpreting `${expression}` syntax. diff --git a/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md b/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md index a9c7867c6..2cd81752e 100644 --- a/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md +++ b/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md @@ -57,6 +57,22 @@ interface ComponentImplementation extends ComponentApi { ### Strategy 1: Direct / Binderless Implementation The developer manually manages A2UI reactivity within the `build` method using native reactive tools (e.g., `useEffect` in React or `StreamBuilder` in Flutter). +*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 To avoid repetitive subscription logic, use an intermediate **Binder Layer**. It transforms raw A2UI properties into a single stream of strongly-typed `ResolvedProps`. @@ -70,6 +86,73 @@ export interface ComponentBinding { ### Strategy 3: Generic Binders (Dynamic Languages) In languages with powerful reflection (like TypeScript), the Binder Layer can be automated. A generic factory inspects a component's schema and automatically creates data model subscriptions, allowing developers to write simple, stateless UI components. +### 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. Reactivity & Lifecycle Rules Regardless of strategy, implementations MUST strictly manage subscriptions: @@ -95,6 +178,53 @@ Components supporting the `checks` property should implement the `Checkable` tra Platforms with strong type systems should utilize their features to ensure an adapter renderer strictly matches the official `ComponentApi` (name and schema). This catches spelling or schema mismatches at compile time. +#### Statically Typed Languages (e.g. Kotlin/Swift) +In languages like Kotlin, you can define a strict interface or class that demands concrete instances of the specific component APIs defined by the Core Library. + +```kotlin +// The Core Library defines the exact shape of the catalog +class BasicCatalogImplementations( + val button: ButtonApi, // Must be an instance of the ButtonApi class + val text: TextApi, + 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) +In TypeScript, we can use intersection types to force the framework renderer to intersect with the exact definition. + +```typescript +// Concept: Forcing implementations to match the spec +type BasicCatalogImplementations = { + Button: ComponentImplementation & { name: "Button", schema: Schema }, + Text: ComponentImplementation & { name: "Text", schema: Schema }, + 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 +]); +``` + ## 8. Development Tools: The Gallery App The Gallery App is the reference environment for an A2UI renderer. From a6499eab6259bee106ab3cf27d8364ae31ba63d1 Mon Sep 17 00:00:00 2001 From: Jacob Simionato Date: Tue, 28 Apr 2026 11:39:41 +0930 Subject: [PATCH 3/6] docs(v0.9): move implementation topologies to adapter guide and adjust flow --- .../docs/core_sdk_implementation_guide.md | 16 --------- ..._framework_adapter_implementation_guide.md | 33 ++++++++++++++----- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/specification/v0_9/docs/core_sdk_implementation_guide.md b/specification/v0_9/docs/core_sdk_implementation_guide.md index 5dcba42af..419307e6b 100644 --- a/specification/v0_9/docs/core_sdk_implementation_guide.md +++ b/specification/v0_9/docs/core_sdk_implementation_guide.md @@ -18,22 +18,6 @@ Its primary responsibilities include: The architecture emphasizes a clean separation between construct (the model) and visualization (the renderer). This layer follows the exact same design in all programming languages and does not require design work when porting to a new ecosystem. -### 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. - -#### Static Languages (e.g., Kotlin, Swift, Dart) -In statically typed languages, runtime reflection is often limited or discouraged for performance reasons. -* **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. -* **Framework Library (e.g., `compose_renderer`)**: Uses the predefined Binders to connect to native UI state and implements the actual visual components. - -#### Combined Core + Framework Libraries (e.g., Swift + SwiftUI) -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. The generic `ComponentContext` and the framework-specific adapter logic are often tightly integrated. - ### Foundational Prerequisites The very first step in implementing a Core SDK is choosing two critical libraries that will dictate the ergonomics and performance of your implementation. diff --git a/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md b/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md index 2cd81752e..a6900d63e 100644 --- a/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md +++ b/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md @@ -12,7 +12,24 @@ Its primary responsibilities include: * **Action Handling**: Connecting native UI events (clicks, input) to A2UI actions. * **Component Mapping**: Dispatching component types to their native implementations. -## 2. The Rendering Architecture +## 2. Implementation Topologies + +Because A2UI spans multiple languages and UI paradigms, the strictness and location of the architectural boundaries between the Core SDK and the Framework Adapter 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 (e.g., `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 Adapter (e.g., `react_renderer`, `angular_renderer`)**: Implements the Framework-Specific Adapters and the actual view implementations. + +#### Static Languages (e.g., Kotlin, Swift, Dart) +In statically typed languages, runtime reflection is often limited or discouraged for performance reasons. +* **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. +* **Framework Adapter (e.g., `compose_renderer`)**: Uses the predefined Binders from the Core SDK to connect to native UI state and implements the actual visual components. + +#### Combined Core + Framework Libraries (e.g., Swift + SwiftUI) +In ecosystems dominated by a single UI framework (like iOS with SwiftUI), developers often build a single, unified library rather than splitting the Core SDK and Framework Adapter into separate packages. In these cases, the generic `ComponentContext` and the framework-specific adapter logic are often tightly integrated. + +## 3. The Rendering Architecture The rendering flow follows a well-defined path: 1. **The `Surface` Entry Point**: A native widget/view is instantiated with a `SurfaceModel`. @@ -23,7 +40,7 @@ The rendering flow follows a well-defined path: ### The recursive `buildChild` helper When implementing the recursive renderer, ensure it correctly propagates the data context path. 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 parent (e.g., `/restaurants/0`), not the root path. -## 3. Component Implementation +## 4. Component Implementation Each A2UI component is defined by its implementation interface. @@ -52,7 +69,7 @@ interface ComponentImplementation extends ComponentApi { } ``` -## 4. Implementation Strategies +## 5. Implementation Strategies ### Strategy 1: Direct / Binderless Implementation The developer manually manages A2UI reactivity within the `build` method using native reactive tools (e.g., `useEffect` in React or `StreamBuilder` in Flutter). @@ -153,7 +170,7 @@ export class AngularWrapper { } ``` -## 5. Reactivity & Lifecycle Rules +## 6. Reactivity & Lifecycle Rules Regardless of strategy, implementations MUST strictly manage subscriptions: 1. **Lazy Subscription**: Only bind and subscribe to data when the component is actually mounted/attached to the UI. @@ -163,7 +180,7 @@ Regardless of strategy, implementations MUST strictly manage subscriptions: 3. **Path Stability**: If a property changes via an `updateComponents` message, you MUST unsubscribe from the old path before subscribing to the new one. 4. **Destruction**: When a component or surface is removed, the adapter MUST dispose of all data model subscriptions to prevent memory leaks. -## 6. Advanced Framework Traits +## 7. Advanced Framework Traits ### Data Props vs. Structural Props * **Data Props (e.g., `label`)**: Handled by the Binder/Adapter. The view receives fully resolved values. Whenever data updates, the binder should emit a *new reference* (shallow copy) to trigger declarative re-renders. @@ -174,7 +191,7 @@ Components supporting the `checks` property should implement the `Checkable` tra * **UI Feedback**: Reactively display the `message` of the first failing check. * **Action Blocking**: Actions (like Button clicks) should be disabled if validation fails. -## 7. Strongly-Typed Catalogs +## 8. Strongly-Typed Catalogs Platforms with strong type systems should utilize their features to ensure an adapter renderer strictly matches the official `ComponentApi` (name and schema). This catches spelling or schema mismatches at compile time. @@ -225,7 +242,7 @@ const catalog = new Catalog("id", [ ]); ``` -## 8. Development Tools: The Gallery App +## 9. Development Tools: The Gallery App The Gallery App is the reference environment for an A2UI renderer. @@ -240,7 +257,7 @@ Every renderer must verify: * **Two-Way Binding**: TextField updates reflected in the Data Model viewer. * **Context Scoping**: Correct data resolution in nested templates (like Lists). -## 9. Phased Implementation Workflow +## 10. Phased Implementation Workflow If you are building a new A2UI renderer, follow this strict, phased sequence of operations. From c95a41c5fdf47c171f8908943765720d96c55589 Mon Sep 17 00:00:00 2001 From: Jacob Simionato Date: Tue, 28 Apr 2026 11:48:13 +0930 Subject: [PATCH 4/6] docs(v0.9): restore all missing agent implementation guide milestones and constraints --- .../docs/core_sdk_implementation_guide.md | 72 +++++++++---------- ..._framework_adapter_implementation_guide.md | 29 ++++---- 2 files changed, 53 insertions(+), 48 deletions(-) diff --git a/specification/v0_9/docs/core_sdk_implementation_guide.md b/specification/v0_9/docs/core_sdk_implementation_guide.md index 419307e6b..dbfa4bcba 100644 --- a/specification/v0_9/docs/core_sdk_implementation_guide.md +++ b/specification/v0_9/docs/core_sdk_implementation_guide.md @@ -254,53 +254,53 @@ It is **crucial** to separate the pure API (the Schemas and `ComponentApi`) from | `null` / `undefined` | `Number` | `0` | | `String` (numeric) | `Number` | Parsed numeric value or `0` | -## 9. Phased Implementation Workflow +## 9. Agent Implementation Guide -Building a Core SDK requires a rigorous, test-driven approach. Since the SDK is framework-agnostic, you can build and test it entirely in isolation before touching any UI code. +If you are an AI Agent tasked with building a new Core SDK for A2UI, you MUST follow this strict, phased sequence of operations. Building a Core SDK requires a rigorous, test-driven approach. You can build and test it entirely in isolation before touching any UI code. -### Phase 1: Context Ingestion & Architecture -Review the protocol specifications (`a2ui_protocol.md`, `common_types.json`, `server_to_client.json`). Decide on your Schema Library and Observable/Reactive Library. Set up your unit testing framework. +### Phase 1: Context to Ingest +Thoroughly review: +* `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) -### Phase 2: Protocol Models & Serialization +### 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).* +* **STOP HERE. Ask the user for approval on this design document before proceeding.** + +### Phase 3: Protocol Models & Serialization Implement strict native types for all A2UI messages and metadata. Write the deserialization and validation logic. -* **Unit Tests (Exhaustive)**: - * Provide valid JSON strings for all message types and assert correct object instantiation. - * Provide invalid JSON (missing required fields, wrong types) and assert `A2uiValidationError` is thrown. - * Test client-to-server serialization (e.g., ensuring `A2uiClientAction` formats timestamps correctly). +* **Action**: Write unit tests for JSON validation. Provide valid JSON strings and assert correct instantiation. Provide invalid JSON and assert `A2uiValidationError` is thrown. -### Phase 3: The Data Model +### Phase 4: The Data Model Implement the `DataModel` class. This is the most algorithmically complex layer. -* **Unit Tests (Exhaustive)**: - * **Path Resolution**: Test setting/getting absolute paths (`/user/name`). - * **Auto-vivification**: Test setting deep paths (`/a/b/0/c`). Assert that `0` becomes an array and `b` becomes an object. - * **Deletion**: Test setting a path to `undefined`/`null`. Assert keys are removed from objects and indices are emptied in arrays. - * **Subscriptions**: Test that updating `/user/name` triggers subscribers for `/user/name` (exact), `/user` (bubble), and `/` (bubble), but not `/user/age` (sibling). +* **Action**: Write exhaustive unit tests for `DataModel`, especially JSON pointer resolution, auto-vivification (e.g. `/a/b/0/c`), and the cascade/bubble notification strategy. Ensure they pass before continuing. -### Phase 4: Component & Surface State Models +### Phase 5: Component & Surface State Models Implement `ComponentModel`, `SurfaceComponentsModel`, `SurfaceModel`, and `SurfaceGroupModel`. -* **Unit Tests (Exhaustive)**: - * Assert `SurfaceComponentsModel` properly adds, updates, and deletes components, emitting events for each. - * Test that replacing a component with a different `type` but the same `id` disposes the old model and creates a new one. - * Test `SurfaceGroupModel` lifecycle (adding/deleting surfaces and cascading disposal). +* **Action**: Write unit tests verifying that `SurfaceComponentsModel` properly adds, updates, and deletes components, emitting events for each. Test `SurfaceGroupModel` lifecycle management. -### Phase 5: The Context & Evaluation Layer +### Phase 6: The Context & Evaluation Layer Implement `DataContext` and `ComponentContext` to handle path scoping and dynamic value resolution. -* **Unit Tests (Exhaustive)**: - * **Scoping**: Test that `nested("child").path` resolves correctly against parent paths (handling trailing slashes). - * **Dynamic Resolution**: Test `resolveDynamicValue` with literals, DataBindings (`path`), and FunctionCalls (`call`). - * **Reactivity**: Test `subscribeDynamicValue` returning a stateful stream that updates when the underlying `DataModel` path is mutated. +* **Action**: Write unit tests for scoping (e.g. `nested("child").path`) and dynamic resolution with literals, DataBindings (`path`), and FunctionCalls (`call`). -### Phase 6: Message Processing +### Phase 7: Message Processing Implement the `MessageProcessor` to act as the central controller. -* **Unit Tests (Exhaustive)**: - * Pass a sequence of `createSurface`, `updateComponents`, and `updateDataModel` messages. Assert the final state of the `SurfaceGroupModel`. - * Test that invalid message sequences (e.g., updating a surface before creating it) throw `A2uiStateError`. - * Test `getClientDataModel()` correctly aggregates data only for surfaces with `sendDataModel: true`. - -### Phase 7: Capabilities & Catalog Functions -Implement the schema translation logic for `a2uiClientCapabilities` and standard function execution (e.g., `formatString`). -* **Unit Tests (Exhaustive)**: - * **Capabilities**: Define a mock ComponentApi, generate capabilities, and assert the resulting JSON Schema is valid and that `REF:` tags are properly stripped and converted to `$ref` objects. - * **Functions**: Test `formatString` with mixed static text, absolute paths, relative paths, and nested function calls. Ensure missing values fail gracefully or coerce to empty strings. +* **Action**: Write unit tests passing a sequence of `createSurface`, `updateComponents`, and `updateDataModel` messages. Assert the final state of the models and verify `getClientDataModel()` correctly aggregates data for `sendDataModel: true`. + +### Phase 8: Capabilities & Minimal Catalog Functions +Target the `minimal_catalog.json` first. +* Implement the schema translation logic for `a2uiClientCapabilities` (stripping `REF:` tags and converting to `$ref`). +* Implement the pure API schemas for the minimal catalog components (`Text`, `Row`, `Column`, `Button`, `TextField`). +* Implement the `capitalize` function. +* **Action**: Write unit tests verifying that standard function execution and capability generation work correctly. + +### Phase 9: Basic Catalog Support +Once the minimal architecture is proven robust, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md) and: +* 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. +* Create definitions for the remaining Basic Catalog components. +* **Action**: Look at existing reference implementations (e.g., `web_core`) to formulate and run comprehensive unit test cases for data coercion and function logic. [RFC 6901]: https://datatracker.ietf.org/doc/html/rfc6901 diff --git a/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md b/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md index a6900d63e..4a340972c 100644 --- a/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md +++ b/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md @@ -257,20 +257,21 @@ Every renderer must verify: * **Two-Way Binding**: TextField updates reflected in the Data Model viewer. * **Context Scoping**: Correct data resolution in nested templates (like Lists). -## 10. Phased Implementation Workflow +## 10. Agent Implementation Guide -If you are building a new A2UI renderer, follow this strict, phased sequence of operations. +If you are an AI Agent tasked with building a new A2UI renderer, you MUST follow this strict, phased sequence of operations. ### Phase 1: Implement or Adopt a Core SDK The framework adapter relies entirely on a stable, framework-agnostic data layer. * If a Core SDK already exists for your language (e.g., `@a2ui/web_core` for JS/TS), add it as a dependency. * If one does not exist, you **must** build it first. Follow the strict, test-driven phases outlined in the [Core SDK Implementation Guide](core_sdk_implementation_guide.md) before writing any UI code. -### Phase 2: Key Architecture Decisions -Before writing UI code, create a design document detailing: -* **Component Architecture**: Define the `ComponentImplementation` API for your specific framework. -* **Surface Architecture**: Design how the `Surface` entry point will recursively build children and propagate context. -* **Binding Strategy**: Decide between a Generic Binder Layer (recommended for dynamic languages) or a Direct/Binderless implementation. +### Phase 2: Key Architecture Decisions (Write a Plan Document) +Before writing UI code, create a comprehensive design document detailing: +* **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 (recommended for dynamic languages) or a Direct/Binderless implementation? +* **STOP HERE. Ask the user for approval on this design document before proceeding.** ### Phase 3: Framework-Specific Layer Implement the bridge between your models and the native UI. @@ -278,15 +279,19 @@ Implement the bridge between your models and the native UI. * Establish lazy subscription mounting and disposal lifecycles to prevent memory leaks. ### Phase 4: Minimal Catalog Support -Implement native UI support for a minimal set of components to verify the architecture: -* **Components**: `Text`, `Row`, `Column`, `Button`, `TextField`. -* **Validation**: Verify that native properties update reactively when the underlying Core SDK data changes. +Target the `minimal_catalog.json` first. +* Implement native UI support for a minimal set of components to verify the architecture: `Text`, `Row`, `Column`, `Button`, `TextField`. +* **Action**: Verify that native properties update reactively when the underlying Core SDK data changes. -### Phase 5: Development Tools (Gallery App) -Build the **Gallery App** as described in Section 8. This tool is essential for debugging progressive rendering and two-way interaction logic before adding more complex components. +### Phase 5: Gallery Application (Milestone) +Build the **Gallery App** as described in Section 9. This tool is essential for debugging progressive rendering and two-way interaction logic before adding more complex components. +* 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 Phase 6.** ### Phase 6: Basic Catalog Support Once the core architecture is stable, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md) to: * Implement the full suite of Basic Catalog widgets. * Bind all standard functions (arithmetic, logical, formatting) to the native UI components. +* Update the Gallery App to load samples from `specification/v0_9/json/catalogs/basic/examples/`. From dff5a2ecd7c7a4a38d69b317a35e672d3e395f6e Mon Sep 17 00:00:00 2001 From: Jacob Simionato Date: Tue, 28 Apr 2026 12:02:43 +0930 Subject: [PATCH 5/6] docs(v0.9): restore all dropped details from previous renderer guide --- .../docs/core_sdk_implementation_guide.md | 36 ++++++++++++++++- ..._framework_adapter_implementation_guide.md | 39 ++++++++++++++++--- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/specification/v0_9/docs/core_sdk_implementation_guide.md b/specification/v0_9/docs/core_sdk_implementation_guide.md index dbfa4bcba..99a3fa4ea 100644 --- a/specification/v0_9/docs/core_sdk_implementation_guide.md +++ b/specification/v0_9/docs/core_sdk_implementation_guide.md @@ -2,7 +2,20 @@ This document describes the architecture and implementation requirements for an A2UI Core SDK. The Core SDK is a framework-agnostic library responsible for state management, protocol parsing, and logic evaluation. It is designed to be implemented in any programming language (client or server) to provide a consistent foundation for A2UI-powered applications. -## 1. Role of the Core SDK +## 1. Unified Architecture 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`** 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). + +## 2. Role of the Core SDK The Core SDK handles the "brain" of the A2UI system. It manages language-agnostic data structures and bridges the raw JSON protocol with reactive state models. @@ -187,6 +200,27 @@ class ComponentContext { The "Controller" that accepts the raw stream, parses messages, and mutates models. +```typescript +class MessageProcessor { + readonly model: SurfaceGroupModel; + + constructor(catalogs: Catalog[], actionHandler: ActionListener); + + // Accepts validated, strongly-typed message objects, not raw JSON + processMessages(messages: A2uiMessage[]): void; + addLifecycleListener(l: SurfaceLifecycleListener): () => void; + + // Returns a strictly typed capabilities object ready for JSON serialization + getClientCapabilities(options?: CapabilitiesOptions): A2uiClientCapabilities; + + /** + * Returns the aggregated data model for all surfaces that have 'sendDataModel' enabled. + * This should be used by the transport layer to populate metadata (e.g., 'a2uiClientDataModel'). + */ + getClientDataModel(): A2uiClientDataModel | undefined; +} +``` + ### Client Data Model Synchronization When `sendDataModel: true`, the SDK aggregates the full state of enabled surfaces. The **Transport Layer** calls `getClientDataModel()` before sending any message to the server to populate metadata (e.g., `a2uiClientDataModel`). diff --git a/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md b/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md index 4a340972c..cc9a0febf 100644 --- a/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md +++ b/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md @@ -182,9 +182,21 @@ Regardless of strategy, implementations MUST strictly manage subscriptions: ## 7. Advanced Framework Traits +### 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 -* **Data Props (e.g., `label`)**: Handled by the Binder/Adapter. The view receives fully resolved values. Whenever data updates, the binder should emit a *new reference* (shallow copy) to trigger declarative re-renders. -* **Structural Props (e.g., `children`)**: The binder outputs metadata (`{ id, basePath }`). The adapter takes these and calls the framework-native `buildChild` method recursively. +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. ### Reactive Validation (`Checkable`) Components supporting the `checks` property should implement the `Checkable` trait: @@ -193,6 +205,17 @@ Components supporting the `checks` property should implement the `Checkable` tra ## 8. Strongly-Typed Catalogs +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, it is **crucial** to separate the pure API (the Schemas and `ComponentApi`/`FunctionApi` definitions) from the actual UI implementations. + +* **Multi-Framework Code Reuse**: In ecosystems like the Web, this allows a shared `web_core` library to define the Basic Catalog API and Binders once, while separate packages (`react_renderer`, `angular_renderer`) provide the native view implementations. +* **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). + +### Strictly Matching APIs Platforms with strong type systems should utilize their features to ensure an adapter renderer strictly matches the official `ComponentApi` (name and schema). This catches spelling or schema mismatches at compile time. #### Statically Typed Languages (e.g. Kotlin/Swift) @@ -247,9 +270,15 @@ const catalog = new Catalog("id", [ The Gallery App is the reference environment for an A2UI renderer. ### UX Architecture -* **Left**: Sample Navigation. -* **Center**: Surface Preview, JSON Message Stream, and an "Advance" stepper for progressive rendering verification. -* **Right**: Live Data Model inspector and Action Logs. +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 must verify: From bc2b43bd841193b7f31d5022f17566f0aab3027c Mon Sep 17 00:00:00 2001 From: Jacob Simionato Date: Tue, 28 Apr 2026 12:09:17 +0930 Subject: [PATCH 6/6] docs(v0.9): clarify distinction between minimal and basic catalogs --- specification/v0_9/docs/core_sdk_implementation_guide.md | 4 ++-- .../v0_9/docs/ui_framework_adapter_implementation_guide.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/specification/v0_9/docs/core_sdk_implementation_guide.md b/specification/v0_9/docs/core_sdk_implementation_guide.md index 99a3fa4ea..cd20e41f0 100644 --- a/specification/v0_9/docs/core_sdk_implementation_guide.md +++ b/specification/v0_9/docs/core_sdk_implementation_guide.md @@ -325,14 +325,14 @@ Implement the `MessageProcessor` to act as the central controller. * **Action**: Write unit tests passing a sequence of `createSurface`, `updateComponents`, and `updateDataModel` messages. Assert the final state of the models and verify `getClientDataModel()` correctly aggregates data for `sendDataModel: true`. ### Phase 8: Capabilities & Minimal Catalog Functions -Target the `minimal_catalog.json` first. +The Minimal Catalog (`@specification/v0_9/json/catalogs/minimal/minimal_catalog.json`) is designed for rapid bootstrapping and testing. Target it first. * Implement the schema translation logic for `a2uiClientCapabilities` (stripping `REF:` tags and converting to `$ref`). * Implement the pure API schemas for the minimal catalog components (`Text`, `Row`, `Column`, `Button`, `TextField`). * Implement the `capitalize` function. * **Action**: Write unit tests verifying that standard function execution and capability generation work correctly. ### Phase 9: Basic Catalog Support -Once the minimal architecture is proven robust, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md) and: +The Basic Catalog (`@specification/v0_9/json/basic_catalog.json`) is the ultimate standard that must be implemented for production renderers. Once the minimal architecture is proven robust, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md) and: * 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. * Create definitions for the remaining Basic Catalog components. * **Action**: 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/docs/ui_framework_adapter_implementation_guide.md b/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md index cc9a0febf..8a3d1e9d2 100644 --- a/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md +++ b/specification/v0_9/docs/ui_framework_adapter_implementation_guide.md @@ -308,7 +308,7 @@ Implement the bridge between your models and the native UI. * Establish lazy subscription mounting and disposal lifecycles to prevent memory leaks. ### Phase 4: Minimal Catalog Support -Target the `minimal_catalog.json` first. +The Minimal Catalog (`@specification/v0_9/json/catalogs/minimal/minimal_catalog.json`) is designed for rapid bootstrapping and testing. Target it first. * Implement native UI support for a minimal set of components to verify the architecture: `Text`, `Row`, `Column`, `Button`, `TextField`. * **Action**: Verify that native properties update reactively when the underlying Core SDK data changes. @@ -319,7 +319,7 @@ Build the **Gallery App** as described in Section 9. This tool is essential for * **STOP HERE. Ask the user for approval of the architecture and gallery application before proceeding to Phase 6.** ### Phase 6: Basic Catalog Support -Once the core architecture is stable, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md) to: +The Basic Catalog (`@specification/v0_9/json/basic_catalog.json`) is the ultimate standard that must be implemented for production renderers. Once the core architecture is stable, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md) to: * Implement the full suite of Basic Catalog widgets. * Bind all standard functions (arithmetic, logical, formatting) to the native UI components. * Update the Gallery App to load samples from `specification/v0_9/json/catalogs/basic/examples/`.