From 0cd25b74f07246197d7360746ab4ac3c74d36a6f Mon Sep 17 00:00:00 2001 From: Seth Date: Wed, 8 Jul 2026 00:34:19 -0600 Subject: [PATCH 1/4] feat(a2ui_core): migrate to A2UI protocol v1.0 - Bump protocol version to v1.0 in message envelopes and capabilities - Rename createSurface 'theme' to 'surfaceProperties'; support inline 'components' and 'dataModel' for single-message UI instantiation - Add CallFunctionMessage/ActionResponseMessage (server-to-client) and A2uiFunctionResponse (client-to-server) with actionId correlation - Add callableFrom execution boundaries with INVALID_FUNCTION_CALL rejection for remote calls to clientOnly/unregistered functions - Serialize catalog functions as a map keyed by name with static returnType/callableFrom metadata; move surfaceProperties under $defs - Remove wire-level returnType from FunctionCall payloads - Serialize explicit null in updateDataModel for key deletion - Add @index system function (with offset) scoped to template loops - Enforce UAX #31 identifier rules for catalog entity names - Add optional catalog 'instructions' (replaces external rules text) Co-Authored-By: Claude Fable 5 --- packages/a2ui_core/lib/a2ui_core.dart | 1 + packages/a2ui_core/lib/src/core/catalog.dart | 54 +++- packages/a2ui_core/lib/src/core/common.dart | 22 +- packages/a2ui_core/lib/src/core/contexts.dart | 75 ++++- packages/a2ui_core/lib/src/core/messages.dart | 225 ++++++++++++++- .../lib/src/core/minimal_catalog.dart | 15 +- .../a2ui_core/lib/src/core/surface_model.dart | 56 +++- .../lib/src/primitives/identifiers.dart | 22 ++ .../lib/src/processing/expressions.dart | 2 +- .../lib/src/processing/processor.dart | 176 ++++++++++-- .../a2ui_core/lib/src/rendering/binder.dart | 14 +- packages/a2ui_core/test/binder_test.dart | 17 ++ packages/a2ui_core/test/contexts_test.dart | 79 ++++++ packages/a2ui_core/test/expressions_test.dart | 1 - packages/a2ui_core/test/messages_test.dart | 257 ++++++++++++++++-- packages/a2ui_core/test/processor_test.dart | 205 +++++++++++++- 16 files changed, 1133 insertions(+), 88 deletions(-) create mode 100644 packages/a2ui_core/lib/src/primitives/identifiers.dart create mode 100644 packages/a2ui_core/test/contexts_test.dart diff --git a/packages/a2ui_core/lib/a2ui_core.dart b/packages/a2ui_core/lib/a2ui_core.dart index 4ca0acd27..26c42cff9 100644 --- a/packages/a2ui_core/lib/a2ui_core.dart +++ b/packages/a2ui_core/lib/a2ui_core.dart @@ -23,6 +23,7 @@ export 'src/primitives/data_path.dart'; export 'src/primitives/errors.dart'; // Event notifications for discrete lifecycle events. export 'src/primitives/event_notifier.dart'; +export 'src/primitives/identifiers.dart'; // Reactivity (re-exports preact_signals primitives). export 'src/primitives/reactivity.dart'; export 'src/processing/basic_functions.dart'; diff --git a/packages/a2ui_core/lib/src/core/catalog.dart b/packages/a2ui_core/lib/src/core/catalog.dart index 3485ab504..2593375fc 100644 --- a/packages/a2ui_core/lib/src/core/catalog.dart +++ b/packages/a2ui_core/lib/src/core/catalog.dart @@ -4,6 +4,8 @@ import 'package:json_schema_builder/json_schema_builder.dart'; import '../primitives/cancellation.dart'; +import '../primitives/errors.dart'; +import '../primitives/identifiers.dart'; import '../primitives/reactivity.dart'; import 'contexts.dart'; @@ -33,11 +35,40 @@ enum A2uiReturnType { } } +/// Where a catalog function may be invoked from. +enum A2uiCallableFrom { + /// Only callable locally on the client (e.g. in data bindings and + /// component actions). + clientOnly, + + /// Only callable by the server via `callFunction` messages. + remoteOnly, + + /// Callable both locally and via `callFunction` messages. + clientOrRemote; + + /// The JSON value used in the A2UI protocol. + String get jsonValue => name; + + /// Parses from the JSON string representation. + static A2uiCallableFrom fromJson(String value) => values.byName(value); + + /// Whether the function may be invoked locally on the client. + bool get isClientCallable => this != remoteOnly; + + /// Whether the function may be invoked by the server via `callFunction`. + bool get isRemoteCallable => this != clientOnly; +} + /// A definition of a UI function's API. abstract class FunctionApi { String get name; A2uiReturnType get returnType; Schema get argumentSchema; + + /// Where this function may be invoked from. Defaults to + /// [A2uiCallableFrom.clientOnly]. + A2uiCallableFrom get callableFrom => A2uiCallableFrom.clientOnly; } /// A function implementation that can be registered with a catalog. @@ -55,13 +86,30 @@ class Catalog { final String id; final Map components; final Map functions; - final Schema? themeSchema; + final Schema? surfacePropertiesSchema; + + /// Optional Markdown design guidelines and component usage rules embedded + /// directly in the catalog. + final String? instructions; Catalog({ required this.id, required List components, List functions = const [], - this.themeSchema, + this.surfacePropertiesSchema, + this.instructions, }) : components = {for (var c in components) c.name: c}, - functions = {for (var f in functions) f.name: f}; + functions = {for (var f in functions) f.name: f} { + for (final String name in [ + ...this.components.keys, + ...this.functions.keys, + ]) { + if (!isValidA2uiIdentifier(name)) { + throw A2uiValidationError( + "Catalog entity name '$name' does not conform to UAX #31 " + 'identifier rules.', + ); + } + } + } } diff --git a/packages/a2ui_core/lib/src/core/common.dart b/packages/a2ui_core/lib/src/core/common.dart index ef208fea3..a29dcdf97 100644 --- a/packages/a2ui_core/lib/src/core/common.dart +++ b/packages/a2ui_core/lib/src/core/common.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'catalog.dart'; - /// A JSON Pointer path to a value in the data model. class DataBinding { final String path; @@ -17,32 +15,24 @@ class DataBinding { } /// Invokes a named function on the client. +/// +/// Runtime execution boundaries (`callableFrom`) and return types are +/// defined statically in catalog function definitions rather than on the +/// wire. class FunctionCall { final String call; final Map args; - final A2uiReturnType returnType; - FunctionCall({ - required this.call, - required this.args, - this.returnType = A2uiReturnType.boolean, - }); + FunctionCall({required this.call, required this.args}); factory FunctionCall.fromJson(Map json) { return FunctionCall( call: json['call'] as String, args: json['args'] as Map? ?? {}, - returnType: A2uiReturnType.fromJson( - json['returnType'] as String? ?? 'boolean', - ), ); } - Map toJson() => { - 'call': call, - 'args': args, - 'returnType': returnType.jsonValue, - }; + Map toJson() => {'call': call, 'args': args}; } /// Triggers a server-side event or a local client-side function. diff --git a/packages/a2ui_core/lib/src/core/contexts.dart b/packages/a2ui_core/lib/src/core/contexts.dart index ca18ac4d1..3fe749f35 100644 --- a/packages/a2ui_core/lib/src/core/contexts.dart +++ b/packages/a2ui_core/lib/src/core/contexts.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import '../primitives/errors.dart'; import '../primitives/reactivity.dart'; import 'catalog.dart'; import 'common.dart'; @@ -28,7 +29,40 @@ class DataContext { final FunctionInvoker _invoke; final String path; - DataContext(this.dataModel, this._invoke, this.path); + /// The 0-based iteration index when this context was created for an item + /// of a template-generated list, or null outside of template instantiation + /// (Collection Scope). + final int? templateIndex; + + DataContext(this.dataModel, this._invoke, this.path, {this.templateIndex}); + + /// Evaluates a reserved `@`-prefixed core system function. + /// + /// Only `@index` is defined by A2UI v1.0; it is valid solely within + /// template instantiation loops. + Object? _resolveSystemFunction(FunctionCall call) { + if (call.call != '@index') { + throw A2uiValidationError( + "Unknown system function '${call.call}'. The '@' prefix is reserved " + 'for core system context evaluations.', + ); + } + final int? index = templateIndex; + if (index == null) { + throw A2uiValidationError( + "'@index' can only be evaluated inside a template instantiation " + 'loop.', + ); + } + final Object offset = resolveSync(call.args['offset']) ?? 0; + if (offset is! num) { + throw A2uiValidationError( + "The 'offset' argument of '@index' must be a number " + '(got $offset).', + ); + } + return index + offset.toInt(); + } String resolvePath(String relativePath) { if (relativePath.startsWith('/')) return relativePath; @@ -48,6 +82,9 @@ class DataContext { } if (value is Map && value.containsKey('call')) { final call = FunctionCall.fromJson(Map.from(value)); + if (call.call.startsWith('@')) { + return _resolveSystemFunction(call); + } final args = {}; for (final MapEntry entry in call.args.entries) { args[entry.key] = resolveSync(entry.value); @@ -79,6 +116,9 @@ class DataContext { } if (value is Map && value.containsKey('call')) { final call = FunctionCall.fromJson(Map.from(value)); + if (call.call.startsWith('@')) { + return signal(_resolveSystemFunction(call)); + } return computed(() { final args = {}; for (final MapEntry entry in call.args.entries) { @@ -97,8 +137,13 @@ class DataContext { return signal(value); } - DataContext nested(String relativePath) { - return DataContext(dataModel, _invoke, resolvePath(relativePath)); + DataContext nested(String relativePath, {int? templateIndex}) { + return DataContext( + dataModel, + _invoke, + resolvePath(relativePath), + templateIndex: templateIndex ?? this.templateIndex, + ); } void set(String relativePath, Object? value) { @@ -112,12 +157,17 @@ class ComponentContext { final ComponentModel componentModel; final DataContext dataContext; - ComponentContext(this.surface, this.componentModel, {String? basePath}) - : dataContext = DataContext( - surface.dataModel, - surface.catalog.invoke, - basePath ?? '/', - ); + ComponentContext( + this.surface, + this.componentModel, { + String? basePath, + int? templateIndex, + }) : dataContext = DataContext( + surface.dataModel, + surface.catalog.invoke, + basePath ?? '/', + templateIndex: templateIndex, + ); /// Dispatches an action from the component. Future dispatchAction(Map action) { @@ -125,7 +175,11 @@ class ComponentContext { } /// Returns a context for rendering a child component. - ComponentContext childContext(String childId, {String? basePath}) { + ComponentContext childContext( + String childId, { + String? basePath, + int? templateIndex, + }) { final ComponentModel? childModel = surface.componentsModel.get(childId); if (childModel == null) { throw ArgumentError('Child component not found: $childId'); @@ -134,6 +188,7 @@ class ComponentContext { surface, childModel, basePath: basePath ?? dataContext.path, + templateIndex: templateIndex ?? dataContext.templateIndex, ); } } diff --git a/packages/a2ui_core/lib/src/core/messages.dart b/packages/a2ui_core/lib/src/core/messages.dart index 923c52695..a4b04a91c 100644 --- a/packages/a2ui_core/lib/src/core/messages.dart +++ b/packages/a2ui_core/lib/src/core/messages.dart @@ -4,10 +4,13 @@ import '../primitives/errors.dart'; +/// The A2UI protocol version implemented by this package. +const String a2uiProtocolVersion = 'v1.0'; + /// Base class for all A2UI messages. abstract class A2uiMessage { final String version; - A2uiMessage({this.version = 'v0.9'}); + A2uiMessage({this.version = a2uiProtocolVersion}); /// Deserializes a JSON envelope into a typed [A2uiMessage]. factory A2uiMessage.fromJson(Map json) { @@ -18,9 +21,10 @@ abstract class A2uiMessage { details: json, ); } - if (rawVersion != 'v0.9') { + if (rawVersion != a2uiProtocolVersion) { throw A2uiValidationError( - "A2UI message must have version 'v0.9' (got '$rawVersion').", + "A2UI message must have version '$a2uiProtocolVersion' " + "(got '$rawVersion').", details: json, ); } @@ -31,6 +35,8 @@ abstract class A2uiMessage { 'updateComponents', 'updateDataModel', 'deleteSurface', + 'callFunction', + 'actionResponse', }; final List presentKeys = messageBodyKeys .where(json.containsKey) @@ -49,8 +55,11 @@ abstract class A2uiMessage { version: version, surfaceId: body['surfaceId'] as String, catalogId: body['catalogId'] as String, - theme: body['theme'] as Map?, + surfaceProperties: body['surfaceProperties'] as Map?, sendDataModel: body['sendDataModel'] as bool? ?? false, + components: (body['components'] as List?) + ?.cast>(), + dataModel: body['dataModel'] as Map?, ); } @@ -81,6 +90,54 @@ abstract class A2uiMessage { ); } + if (json.containsKey('callFunction')) { + final body = json['callFunction'] as Map; + final Object? functionCallId = json['functionCallId']; + if (functionCallId is! String) { + throw A2uiValidationError( + "A2UI callFunction message must have a string 'functionCallId' " + 'field.', + details: json, + ); + } + return CallFunctionMessage( + version: version, + functionCallId: functionCallId, + wantResponse: json['wantResponse'] as bool? ?? false, + call: body['call'] as String, + args: body['args'] as Map?, + ); + } + + if (json.containsKey('actionResponse')) { + final body = json['actionResponse'] as Map; + final Object? actionId = json['actionId']; + if (actionId is! String) { + throw A2uiValidationError( + "A2UI actionResponse message must have a string 'actionId' field.", + details: json, + ); + } + final bool hasValue = body.containsKey('value'); + final Object? rawError = body['error']; + if (hasValue == (rawError != null)) { + throw A2uiValidationError( + "A2UI actionResponse must contain exactly one of 'value' or " + "'error'.", + details: json, + ); + } + return ActionResponseMessage( + version: version, + actionId: actionId, + value: body['value'], + hasValue: hasValue, + error: rawError == null + ? null + : A2uiActionError.fromJson(rawError as Map), + ); + } + throw A2uiValidationError( 'Unknown A2UI message type. Expected one of: ' '${messageBodyKeys.join(', ')}.', @@ -95,15 +152,24 @@ abstract class A2uiMessage { class CreateSurfaceMessage extends A2uiMessage { final String surfaceId; final String catalogId; - final Map? theme; + final Map? surfaceProperties; final bool sendDataModel; + /// Optional initial components, allowing an entire UI to be defined in a + /// single `createSurface` message. + final List>? components; + + /// Optional initial root data model object for the surface. + final Map? dataModel; + CreateSurfaceMessage({ super.version, required this.surfaceId, required this.catalogId, - this.theme, + this.surfaceProperties, this.sendDataModel = false, + this.components, + this.dataModel, }); @override @@ -112,8 +178,10 @@ class CreateSurfaceMessage extends A2uiMessage { 'createSurface': { 'surfaceId': surfaceId, 'catalogId': catalogId, - if (theme != null) 'theme': theme, + if (surfaceProperties != null) 'surfaceProperties': surfaceProperties, 'sendDataModel': sendDataModel, + if (components != null) 'components': components, + if (dataModel != null) 'dataModel': dataModel, }, }; } @@ -137,6 +205,8 @@ class UpdateComponentsMessage extends A2uiMessage { } /// Updates the data model for an existing surface. +/// +/// Setting a path's [value] to `null` deletes the key at that path. class UpdateDataModelMessage extends A2uiMessage { final String surfaceId; final String? path; @@ -155,7 +225,9 @@ class UpdateDataModelMessage extends A2uiMessage { 'updateDataModel': { 'surfaceId': surfaceId, if (path != null) 'path': path, - if (value != null) 'value': value, + // An explicit null value deletes the key at 'path' in v1.0, so null is + // serialized rather than omitted. + 'value': value, }, }; } @@ -173,6 +245,90 @@ class DeleteSurfaceMessage extends A2uiMessage { }; } +/// A server-initiated function call to be executed on the client. +class CallFunctionMessage extends A2uiMessage { + /// Unique ID for this function call instance. Must be copied verbatim into + /// the resulting `functionResponse` or `error` message. + final String functionCallId; + + /// Whether the server expects a `functionResponse` for this call. + final bool wantResponse; + + /// The name of the function to call. + final String call; + + /// Arguments passed to the function. + final Map? args; + + CallFunctionMessage({ + super.version, + required this.functionCallId, + this.wantResponse = false, + required this.call, + this.args, + }); + + @override + Map toJson() => { + 'version': version, + 'functionCallId': functionCallId, + if (wantResponse) 'wantResponse': wantResponse, + 'callFunction': {'call': call, if (args != null) 'args': args}, + }; +} + +/// An error returned by the server in an [ActionResponseMessage]. +class A2uiActionError { + final String code; + final String message; + + A2uiActionError({required this.code, required this.message}); + + factory A2uiActionError.fromJson(Map json) => + A2uiActionError( + code: json['code'] as String, + message: json['message'] as String, + ); + + Map toJson() => {'code': code, 'message': message}; +} + +/// A server response to a client-initiated action that requested a response +/// (`wantResponse: true`). +class ActionResponseMessage extends A2uiMessage { + /// The ID of the action call this response belongs to. + final String actionId; + + /// The return value of the action. May be `null` even when present on the + /// wire; check [hasValue] to distinguish a null value from an error + /// response. + final Object? value; + + /// Whether the response carried a `value` (as opposed to an [error]). + final bool hasValue; + + /// The error returned by the action, if it failed. + final A2uiActionError? error; + + ActionResponseMessage({ + super.version, + required this.actionId, + this.value, + bool? hasValue, + this.error, + }) : hasValue = hasValue ?? error == null; + + @override + Map toJson() => { + 'version': version, + 'actionId': actionId, + 'actionResponse': { + if (hasValue) 'value': value, + if (error != null) 'error': error!.toJson(), + }, + }; +} + /// Reports a user-initiated action from a component. class A2uiClientAction { final String name; @@ -181,12 +337,20 @@ class A2uiClientAction { final DateTime timestamp; final Map context; + /// Whether the client expects an `actionResponse` from the server. + final bool wantResponse; + + /// Unique ID for this action call. Required when [wantResponse] is true. + final String? actionId; + A2uiClientAction({ required this.name, required this.surfaceId, required this.sourceComponentId, required this.timestamp, required this.context, + this.wantResponse = false, + this.actionId, }); Map toJson() => { @@ -195,26 +359,63 @@ class A2uiClientAction { 'sourceComponentId': sourceComponentId, 'timestamp': timestamp.toIso8601String(), 'context': context, + if (wantResponse) 'wantResponse': wantResponse, + if (actionId != null) 'actionId': actionId, + }; +} + +/// Returns the result of a server-initiated function call to the server. +class A2uiFunctionResponse { + /// Unique ID of the function call instance, copied verbatim from the + /// [CallFunctionMessage] that initiated the call. + final String functionCallId; + + /// The name of the function which was called, copied verbatim from the + /// invocation. + final String call; + + /// The return value of the function invocation. + final Object? value; + + A2uiFunctionResponse({ + required this.functionCallId, + required this.call, + this.value, + }); + + Map toJson() => { + 'functionCallId': functionCallId, + 'call': call, + 'value': value, }; } /// Reports a client-side error. +/// +/// Exactly one of [surfaceId] (for surface-scoped errors) or [functionCallId] +/// (for function execution failures) should be set. class A2uiClientError { final String code; - final String surfaceId; + final String? surfaceId; + final String? functionCallId; final String message; final Object? details; A2uiClientError({ required this.code, - required this.surfaceId, + this.surfaceId, + this.functionCallId, required this.message, this.details, - }); + }) : assert( + (surfaceId == null) != (functionCallId == null), + "Exactly one of 'surfaceId' or 'functionCallId' must be set.", + ); Map toJson() => { 'code': code, - 'surfaceId': surfaceId, + if (surfaceId != null) 'surfaceId': surfaceId, + if (functionCallId != null) 'functionCallId': functionCallId, 'message': message, if (details != null) 'details': details, }; diff --git a/packages/a2ui_core/lib/src/core/minimal_catalog.dart b/packages/a2ui_core/lib/src/core/minimal_catalog.dart index 10374cd9a..6950ae26d 100644 --- a/packages/a2ui_core/lib/src/core/minimal_catalog.dart +++ b/packages/a2ui_core/lib/src/core/minimal_catalog.dart @@ -146,7 +146,7 @@ class CapitalizeFunction extends FunctionImplementation { class MinimalCatalog extends Catalog { MinimalCatalog() : super( - id: 'https://a2ui.org/specification/v0_9/catalogs/minimal/minimal_catalog.json', + id: 'https://a2ui.org/specification/v1_0/catalogs/minimal/minimal_catalog.json', components: [ MinimalTextApi(), MinimalRowApi(), @@ -155,9 +155,18 @@ class MinimalCatalog extends Catalog { MinimalTextFieldApi(), ], functions: [CapitalizeFunction()], - themeSchema: Schema.object( + surfacePropertiesSchema: Schema.object( properties: { - 'primaryColor': Schema.string(pattern: r'^#[0-9a-fA-F]{6}$'), + 'iconUrl': Schema.string( + description: + 'A URL for an image that identifies the agent or tool ' + 'associated with the surface.', + ), + 'agentDisplayName': Schema.string( + description: + 'Text to be displayed next to the surface to identify the ' + 'agent or tool that created it.', + ), }, additionalProperties: true, ), diff --git a/packages/a2ui_core/lib/src/core/surface_model.dart b/packages/a2ui_core/lib/src/core/surface_model.dart index 5abc02aac..40687d3c1 100644 --- a/packages/a2ui_core/lib/src/core/surface_model.dart +++ b/packages/a2ui_core/lib/src/core/surface_model.dart @@ -16,7 +16,7 @@ import 'messages.dart'; class SurfaceModel { final String id; final Catalog catalog; - final Map theme; + final Map surfaceProperties; final bool sendDataModel; final DataModel dataModel; @@ -25,6 +25,12 @@ class SurfaceModel { final _onAction = EventNotifier(); final _onError = EventNotifier(); + /// Pending actions awaiting an `actionResponse` from the server, keyed by + /// actionId. The value is the optional JSON Pointer path where the + /// response value should be written in the data model. + final Map _pendingActions = {}; + int _nextActionId = 0; + /// Fires whenever an action is dispatched from this surface. EventListenable get onAction => _onAction; @@ -34,7 +40,7 @@ class SurfaceModel { SurfaceModel( this.id, { required this.catalog, - this.theme = const {}, + this.surfaceProperties = const {}, this.sendDataModel = false, }) : dataModel = DataModel(), componentsModel = SurfaceComponentsModel(); @@ -46,6 +52,12 @@ class SurfaceModel { ) async { if (payload.containsKey('event')) { final event = payload['event'] as Map; + final bool wantResponse = event['wantResponse'] as bool? ?? false; + String? actionId; + if (wantResponse) { + actionId = '$id-action-${_nextActionId++}'; + _pendingActions[actionId] = event['responsePath'] as String?; + } final action = A2uiClientAction( name: (event['name'] as String?) ?? 'unknown', surfaceId: id, @@ -54,11 +66,27 @@ class SurfaceModel { context: Map.from( (event['context'] ?? {}) as Map, ), + wantResponse: wantResponse, + actionId: actionId, ); _onAction.emit(action); } else if (payload.containsKey('functionCall')) { final callJson = payload['functionCall'] as Map; final call = FunctionCall.fromJson(callJson); + final FunctionImplementation? fn = catalog.functions[call.call]; + if (fn != null && !fn.callableFrom.isClientCallable) { + await dispatchError( + A2uiClientError( + code: 'INVALID_FUNCTION_CALL', + surfaceId: id, + message: + "Function '${call.call}' is configured as " + "'${fn.callableFrom.jsonValue}' and cannot be invoked from " + 'the client.', + ), + ); + return; + } catalog.invoke( call.call, Map.from(call.args), @@ -67,6 +95,30 @@ class SurfaceModel { } } + /// Applies a server [ActionResponseMessage] to this surface. + /// + /// Returns true if the response corresponds to an action dispatched from + /// this surface, false otherwise. + bool applyActionResponse(ActionResponseMessage message) { + if (!_pendingActions.containsKey(message.actionId)) { + return false; + } + final String? responsePath = _pendingActions.remove(message.actionId); + final A2uiActionError? error = message.error; + if (error != null) { + _onError.emit( + A2uiClientError( + code: error.code, + surfaceId: id, + message: error.message, + ), + ); + } else if (responsePath != null) { + dataModel.set(responsePath, message.value); + } + return true; + } + /// Dispatches an error from this surface. Future dispatchError(A2uiClientError error) async { _onError.emit(error); diff --git a/packages/a2ui_core/lib/src/primitives/identifiers.dart b/packages/a2ui_core/lib/src/primitives/identifiers.dart new file mode 100644 index 000000000..ec13635b3 --- /dev/null +++ b/packages/a2ui_core/lib/src/primitives/identifiers.dart @@ -0,0 +1,22 @@ +// Copyright 2025 The Flutter Authors. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Matches identifiers conforming to Unicode Standard Annex #31 (UAX #31) +/// default identifier rules (`XID_Start` followed by `XID_Continue*`). +/// +/// The `ID_Start`/`ID_Continue` Unicode property escapes are the closest +/// equivalents to `XID_Start`/`XID_Continue` available in Dart regular +/// expressions; they differ only for a handful of exotic code points. +final RegExp _identifierPattern = RegExp( + r'^\p{ID_Start}\p{ID_Continue}*$', + unicode: true, +); + +/// Whether [name] is a valid A2UI catalog entity identifier per UAX #31. +/// +/// A2UI v1.0 requires component names, function names, and argument keys to +/// conform to UAX #31 identifier rules. The `@` prefix is reserved for core +/// system context evaluations (such as `@index`) and is not a valid catalog +/// entity name. +bool isValidA2uiIdentifier(String name) => _identifierPattern.hasMatch(name); diff --git a/packages/a2ui_core/lib/src/processing/expressions.dart b/packages/a2ui_core/lib/src/processing/expressions.dart index 947a9b3ec..ebd311ddb 100644 --- a/packages/a2ui_core/lib/src/processing/expressions.dart +++ b/packages/a2ui_core/lib/src/processing/expressions.dart @@ -182,7 +182,7 @@ class ExpressionParser { ); } - return {'call': funcName, 'args': args, 'returnType': 'any'}; + return {'call': funcName, 'args': args}; } String _scanIdentifier(_Scanner scanner) { diff --git a/packages/a2ui_core/lib/src/processing/processor.dart b/packages/a2ui_core/lib/src/processing/processor.dart index 05c99363b..8fbf5323b 100644 --- a/packages/a2ui_core/lib/src/processing/processor.dart +++ b/packages/a2ui_core/lib/src/processing/processor.dart @@ -6,23 +6,48 @@ import 'package:json_schema_builder/json_schema_builder.dart'; import '../core/catalog.dart'; import '../core/component_model.dart'; +import '../core/contexts.dart'; +import '../core/data_model.dart'; import '../core/messages.dart'; import '../core/surface_group_model.dart'; import '../core/surface_model.dart'; import '../primitives/errors.dart'; +import '../primitives/event_notifier.dart'; +import '../primitives/reactivity.dart'; /// The central processor for A2UI messages. class MessageProcessor { final SurfaceGroupModel groupModel; final List> catalogs; + final _onFunctionResponse = EventNotifier(); + final _onError = EventNotifier(); + + /// Fires when a server-initiated function call (`callFunction`) that + /// requested a response has been executed. The emitted + /// [A2uiFunctionResponse] should be sent back to the server. + EventListenable get onFunctionResponse => + _onFunctionResponse; + + /// Fires when processing produces a client error that should be reported + /// to the server (e.g. an invalid `callFunction`). + EventListenable get onError => _onError; + MessageProcessor({ required this.catalogs, void Function(A2uiClientAction)? onAction, + void Function(A2uiFunctionResponse)? onFunctionResponse, + void Function(A2uiClientError)? onError, }) : groupModel = SurfaceGroupModel() { if (onAction != null) { groupModel.onAction.addListener(onAction); } + if (onFunctionResponse != null) { + _onFunctionResponse.addListener(onFunctionResponse); + } + if (onError != null) { + _onError.addListener(onError); + } } /// Processes a list of messages. @@ -41,6 +66,10 @@ class MessageProcessor { _processUpdateDataModel(message); } else if (message is DeleteSurfaceMessage) { _processDeleteSurface(message); + } else if (message is CallFunctionMessage) { + _processCallFunction(message); + } else if (message is ActionResponseMessage) { + _processActionResponse(message); } } @@ -58,10 +87,19 @@ class MessageProcessor { final surface = SurfaceModel( message.surfaceId, catalog: catalog, - theme: message.theme ?? {}, + surfaceProperties: message.surfaceProperties ?? {}, sendDataModel: message.sendDataModel, ); groupModel.addSurface(surface); + + final Map? dataModel = message.dataModel; + if (dataModel != null) { + surface.dataModel.set('/', dataModel); + } + final List>? components = message.components; + if (components != null) { + _applyComponents(surface, components); + } } void _processUpdateComponents(UpdateComponentsMessage message) { @@ -70,7 +108,14 @@ class MessageProcessor { throw A2uiStateError('Surface not found: ${message.surfaceId}'); } - for (final Map compJson in message.components) { + _applyComponents(surface, message.components); + } + + void _applyComponents( + SurfaceModel surface, + List> components, + ) { + for (final compJson in components) { final id = compJson['id'] as String?; final type = compJson['component'] as String?; @@ -115,19 +160,89 @@ class MessageProcessor { groupModel.deleteSurface(message.surfaceId); } + void _processCallFunction(CallFunctionMessage message) { + FunctionImplementation? fn; + Catalog? owningCatalog; + for (final Catalog catalog in catalogs) { + fn = catalog.functions[message.call]; + if (fn != null) { + owningCatalog = catalog; + break; + } + } + + if (fn == null || !fn.callableFrom.isRemoteCallable) { + _onError.emit( + A2uiClientError( + code: 'INVALID_FUNCTION_CALL', + functionCallId: message.functionCallId, + message: fn == null + ? "Function '${message.call}' is not registered in any " + 'catalog.' + : "Function '${message.call}' is configured as " + "'${fn.callableFrom.jsonValue}' and cannot be invoked " + 'remotely.', + ), + ); + return; + } + + Object? result; + try { + result = owningCatalog!.invoke( + message.call, + Map.from(message.args ?? {}), + DataContext(DataModel(), owningCatalog.invoke, '/'), + ); + if (result is ReadonlySignal) { + result = result.value; + } + } catch (e) { + _onError.emit( + A2uiClientError( + code: 'FUNCTION_EXECUTION_ERROR', + functionCallId: message.functionCallId, + message: "Function '${message.call}' failed: $e", + ), + ); + return; + } + + if (message.wantResponse) { + _onFunctionResponse.emit( + A2uiFunctionResponse( + functionCallId: message.functionCallId, + call: message.call, + value: result, + ), + ); + } + } + + void _processActionResponse(ActionResponseMessage message) { + for (final SurfaceModel surface in groupModel.allSurfaces) { + if (surface.applyActionResponse(message)) { + return; + } + } + throw A2uiStateError( + 'No pending action found for actionId: ${message.actionId}', + ); + } + /// Generates client capabilities. Map getClientCapabilities({ bool includeInlineCatalogs = false, }) { - final v09 = { + final v10 = { 'supportedCatalogIds': catalogs.map((c) => c.id).toList(), }; if (includeInlineCatalogs) { - v09['inlineCatalogs'] = catalogs.map(_generateInlineCatalog).toList(); + v10['inlineCatalogs'] = catalogs.map(_generateInlineCatalog).toList(); } - return {'v0.9': v09}; + return {'v1.0': v10}; } Map _generateInlineCatalog(Catalog catalog) { @@ -151,30 +266,53 @@ class MessageProcessor { }; } - final List> functions = catalog.functions.values.map(( - f, - ) { + final functions = {}; + for (final FunctionImplementation f in catalog.functions.values) { final Map jsonSchema = f.argumentSchema.toJsonMap(); _processRefs(jsonSchema); - return { - 'name': f.name, + functions[f.name] = { + 'type': 'object', + 'properties': { + 'call': {'const': f.name}, + 'args': jsonSchema, + }, + 'required': ['call'], + 'additionalProperties': false, 'returnType': f.returnType.jsonValue, - 'parameters': jsonSchema, + 'callableFrom': f.callableFrom.jsonValue, }; - }).toList(); + } - Map? theme; - if (catalog.themeSchema != null) { - theme = catalog.themeSchema!.toJsonMap(); - _processRefs(theme); - theme = theme['properties'] as Map?; + Map? surfaceProperties; + if (catalog.surfacePropertiesSchema != null) { + surfaceProperties = catalog.surfacePropertiesSchema!.toJsonMap(); + _processRefs(surfaceProperties); } return { 'catalogId': catalog.id, + if (catalog.instructions != null) 'instructions': catalog.instructions, 'components': components, if (functions.isNotEmpty) 'functions': functions, - 'theme': ?theme, + '\$defs': { + 'anyComponent': { + 'oneOf': [ + for (final String name in components.keys) + {'\$ref': '#/components/$name'}, + ], + }, + // A boolean 'false' schema when there are no functions: no function + // call validates against a catalog that defines none. + 'anyFunction': functions.isEmpty + ? false + : { + 'oneOf': [ + for (final String name in functions.keys) + {'\$ref': '#/functions/$name'}, + ], + }, + 'surfaceProperties': ?surfaceProperties, + }, }; } @@ -220,7 +358,7 @@ class MessageProcessor { if (surfaces.isEmpty) return null; - return {'version': 'v0.9', 'surfaces': surfaces}; + return {'version': a2uiProtocolVersion, 'surfaces': surfaces}; } } diff --git a/packages/a2ui_core/lib/src/rendering/binder.dart b/packages/a2ui_core/lib/src/rendering/binder.dart index 0887f6726..bc2dc1a8c 100644 --- a/packages/a2ui_core/lib/src/rendering/binder.dart +++ b/packages/a2ui_core/lib/src/rendering/binder.dart @@ -24,9 +24,18 @@ class BehaviorNode { class ChildNode { final String id; final String basePath; - ChildNode(this.id, this.basePath); - Map toJson() => {'id': id, 'basePath': basePath}; + /// The 0-based iteration index for template-generated children, or null + /// for children from an explicit list. + final int? templateIndex; + + ChildNode(this.id, this.basePath, {this.templateIndex}); + + Map toJson() => { + 'id': id, + 'basePath': basePath, + if (templateIndex != null) 'templateIndex': templateIndex, + }; } /// Takes a component's raw JSON properties (which may contain data @@ -129,6 +138,7 @@ class GenericBinder { (i) => ChildNode( tpl.componentId, nestedCtx.resolvePath(i.toString()), + templateIndex: i, ), ); } diff --git a/packages/a2ui_core/test/binder_test.dart b/packages/a2ui_core/test/binder_test.dart index 10ac27646..82faad3f9 100644 --- a/packages/a2ui_core/test/binder_test.dart +++ b/packages/a2ui_core/test/binder_test.dart @@ -75,6 +75,23 @@ void main() { expect(children[1].id, 'child2'); }); + test('assigns template indices to template-generated children', () { + final comp = ComponentModel('c1', 'Row', { + 'children': {'componentId': 'tpl', 'path': '/items'}, + }); + surface.componentsModel.addComponent(comp); + surface.dataModel.set('/items', ['a', 'b', 'c']); + + final context = ComponentContext(surface, comp); + final binder = GenericBinder(context, MinimalRowApi().schema); + + final children = + binder.resolvedProps.value['children'] as List; + expect(children.length, 3); + expect(children.map((c) => c.templateIndex), [0, 1, 2]); + expect(children[1].basePath, '/items/1'); + }); + test('resolves checkable validation', () async { final comp = ComponentModel('c1', 'TextField', { 'label': 'Name', diff --git a/packages/a2ui_core/test/contexts_test.dart b/packages/a2ui_core/test/contexts_test.dart new file mode 100644 index 000000000..0b8df8e7f --- /dev/null +++ b/packages/a2ui_core/test/contexts_test.dart @@ -0,0 +1,79 @@ +// Copyright 2025 The Flutter Authors. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:a2ui_core/src/core/contexts.dart'; +import 'package:a2ui_core/src/core/data_model.dart'; +import 'package:a2ui_core/src/primitives/errors.dart'; +import 'package:a2ui_core/src/primitives/identifiers.dart'; +import 'package:test/test.dart'; + +void main() { + group('DataContext @index', () { + late DataModel dataModel; + + setUp(() { + dataModel = DataModel(); + }); + + DataContext contextAt(int? templateIndex) => DataContext( + dataModel, + (name, args, context) => throw StateError('No functions registered.'), + '/', + templateIndex: templateIndex, + ); + + test('resolves to the iteration index inside a template scope', () { + expect(contextAt(2).resolveSync({'call': '@index'}), 2); + }); + + test('applies the offset argument', () { + expect( + contextAt(2).resolveSync({ + 'call': '@index', + 'args': {'offset': 1}, + }), + 3, + ); + }); + + test('resolves reactively via resolveListenable', () { + expect(contextAt(4).resolveListenable({'call': '@index'}).value, 4); + }); + + test('throws when evaluated outside a template scope', () { + expect( + () => contextAt(null).resolveSync({'call': '@index'}), + throwsA(isA()), + ); + }); + + test('throws for unknown @-prefixed system functions', () { + expect( + () => contextAt(0).resolveSync({'call': '@bogus'}), + throwsA(isA()), + ); + }); + + test('is inherited by nested contexts', () { + final DataContext nested = contextAt(5).nested('items'); + expect(nested.resolveSync({'call': '@index'}), 5); + }); + }); + + group('isValidA2uiIdentifier', () { + test('accepts UAX #31 identifiers', () { + expect(isValidA2uiIdentifier('Button'), isTrue); + expect(isValidA2uiIdentifier('formatDate'), isTrue); + expect(isValidA2uiIdentifier('café'), isTrue); + }); + + test('rejects invalid identifiers', () { + expect(isValidA2uiIdentifier(''), isFalse); + expect(isValidA2uiIdentifier('1abc'), isFalse); + expect(isValidA2uiIdentifier('my-function'), isFalse); + expect(isValidA2uiIdentifier('@index'), isFalse); + expect(isValidA2uiIdentifier('has space'), isFalse); + }); + }); +} diff --git a/packages/a2ui_core/test/expressions_test.dart b/packages/a2ui_core/test/expressions_test.dart index 6f9ef2715..05824c0b2 100644 --- a/packages/a2ui_core/test/expressions_test.dart +++ b/packages/a2ui_core/test/expressions_test.dart @@ -37,7 +37,6 @@ void main() { { 'call': 'add', 'args': {'a': 10, 'b': 20}, - 'returnType': 'any', }, ]); }); diff --git a/packages/a2ui_core/test/messages_test.dart b/packages/a2ui_core/test/messages_test.dart index d6438f57c..1a3851322 100644 --- a/packages/a2ui_core/test/messages_test.dart +++ b/packages/a2ui_core/test/messages_test.dart @@ -9,11 +9,11 @@ void main() { group('A2uiMessage.fromJson', () { test('parses createSurface', () { final msg = A2uiMessage.fromJson({ - 'version': 'v0.9', + 'version': 'v1.0', 'createSurface': { 'surfaceId': 's1', 'catalogId': 'cat1', - 'theme': {'primaryColor': '#FF0000'}, + 'surfaceProperties': {'agentDisplayName': 'My Agent'}, 'sendDataModel': true, }, }); @@ -22,25 +22,50 @@ void main() { final cs = msg as CreateSurfaceMessage; expect(cs.surfaceId, 's1'); expect(cs.catalogId, 'cat1'); - expect(cs.theme, {'primaryColor': '#FF0000'}); + expect(cs.surfaceProperties, {'agentDisplayName': 'My Agent'}); expect(cs.sendDataModel, true); - expect(cs.version, 'v0.9'); + expect(cs.version, 'v1.0'); }); test('parses createSurface with defaults', () { final msg = A2uiMessage.fromJson({ - 'version': 'v0.9', + 'version': 'v1.0', 'createSurface': {'surfaceId': 's1', 'catalogId': 'cat1'}, }); final cs = msg as CreateSurfaceMessage; - expect(cs.theme, isNull); + expect(cs.surfaceProperties, isNull); expect(cs.sendDataModel, false); + expect(cs.components, isNull); + expect(cs.dataModel, isNull); + }); + + test('parses createSurface with inline components and dataModel', () { + final msg = A2uiMessage.fromJson({ + 'version': 'v1.0', + 'createSurface': { + 'surfaceId': 's1', + 'catalogId': 'cat1', + 'components': [ + {'id': 'root', 'component': 'Text', 'text': 'Hello'}, + ], + 'dataModel': { + 'user': {'name': 'Alice'}, + }, + }, + }); + + final cs = msg as CreateSurfaceMessage; + expect(cs.components, hasLength(1)); + expect(cs.components![0]['id'], 'root'); + expect(cs.dataModel, { + 'user': {'name': 'Alice'}, + }); }); test('parses updateComponents', () { final msg = A2uiMessage.fromJson({ - 'version': 'v0.9', + 'version': 'v1.0', 'updateComponents': { 'surfaceId': 's1', 'components': [ @@ -58,7 +83,7 @@ void main() { test('parses updateDataModel', () { final msg = A2uiMessage.fromJson({ - 'version': 'v0.9', + 'version': 'v1.0', 'updateDataModel': { 'surfaceId': 's1', 'path': '/user/name', @@ -75,7 +100,7 @@ void main() { test('parses updateDataModel without path or value', () { final msg = A2uiMessage.fromJson({ - 'version': 'v0.9', + 'version': 'v1.0', 'updateDataModel': {'surfaceId': 's1'}, }); @@ -84,9 +109,17 @@ void main() { expect(ud.value, isNull); }); + test('serializes an explicit null value for updateDataModel deletion', () { + final msg = UpdateDataModelMessage(surfaceId: 's1', path: '/user/name'); + final Map json = msg.toJson(); + final body = json['updateDataModel'] as Map; + expect(body.containsKey('value'), isTrue); + expect(body['value'], isNull); + }); + test('parses deleteSurface', () { final msg = A2uiMessage.fromJson({ - 'version': 'v0.9', + 'version': 'v1.0', 'deleteSurface': {'surfaceId': 's1'}, }); @@ -95,10 +128,84 @@ void main() { expect(ds.surfaceId, 's1'); }); + test('parses callFunction', () { + final msg = A2uiMessage.fromJson({ + 'version': 'v1.0', + 'functionCallId': 'fc-1', + 'wantResponse': true, + 'callFunction': { + 'call': 'capitalize', + 'args': {'value': 'hello'}, + }, + }); + + expect(msg, isA()); + final cf = msg as CallFunctionMessage; + expect(cf.functionCallId, 'fc-1'); + expect(cf.wantResponse, true); + expect(cf.call, 'capitalize'); + expect(cf.args, {'value': 'hello'}); + }); + + test('throws on callFunction without functionCallId', () { + expect( + () => A2uiMessage.fromJson({ + 'version': 'v1.0', + 'callFunction': {'call': 'capitalize'}, + }), + throwsA(isA()), + ); + }); + + test('parses actionResponse with a value', () { + final msg = A2uiMessage.fromJson({ + 'version': 'v1.0', + 'actionId': 'a-1', + 'actionResponse': {'value': 42}, + }); + + expect(msg, isA()); + final ar = msg as ActionResponseMessage; + expect(ar.actionId, 'a-1'); + expect(ar.hasValue, isTrue); + expect(ar.value, 42); + expect(ar.error, isNull); + }); + + test('parses actionResponse with an error', () { + final msg = A2uiMessage.fromJson({ + 'version': 'v1.0', + 'actionId': 'a-1', + 'actionResponse': { + 'error': {'code': 'NOT_FOUND', 'message': 'No such item.'}, + }, + }); + + final ar = msg as ActionResponseMessage; + expect(ar.hasValue, isFalse); + expect(ar.error, isNotNull); + expect(ar.error!.code, 'NOT_FOUND'); + expect(ar.error!.message, 'No such item.'); + }); + + test('throws on actionResponse with both value and error', () { + expect( + () => A2uiMessage.fromJson({ + 'version': 'v1.0', + 'actionId': 'a-1', + 'actionResponse': { + 'value': 42, + 'error': {'code': 'X', 'message': 'Y'}, + }, + }), + throwsA(isA()), + ); + }); + test('throws on unknown message type', () { expect( () => A2uiMessage.fromJson({ - 'version': 'v0.9', + 'version': 'v1.0', 'unknownType': {'surfaceId': 's1'}, }), throwsA(isA()), @@ -114,10 +221,10 @@ void main() { ); }); - test('throws when version is not v0.9', () { + test('throws when version is not v1.0', () { expect( () => A2uiMessage.fromJson({ - 'version': 'v0.8', + 'version': 'v0.9', 'createSurface': {'surfaceId': 's1', 'catalogId': 'c1'}, }), throwsA(isA()), @@ -137,7 +244,7 @@ void main() { test('throws when more than one message type is present', () { expect( () => A2uiMessage.fromJson({ - 'version': 'v0.9', + 'version': 'v1.0', 'createSurface': {'surfaceId': 's1', 'catalogId': 'c1'}, 'updateComponents': {'surfaceId': 's1', 'components': []}, }), @@ -149,8 +256,12 @@ void main() { final original = CreateSurfaceMessage( surfaceId: 's1', catalogId: 'cat1', - theme: {'color': 'red'}, + surfaceProperties: {'agentDisplayName': 'Agent'}, sendDataModel: true, + components: [ + {'id': 'root', 'component': 'Text', 'text': 'Hi'}, + ], + dataModel: {'name': 'Alice'}, ); final roundtripped = A2uiMessage.fromJson(original.toJson()); @@ -158,8 +269,122 @@ void main() { final cs = roundtripped as CreateSurfaceMessage; expect(cs.surfaceId, 's1'); expect(cs.catalogId, 'cat1'); - expect(cs.theme, {'color': 'red'}); + expect(cs.surfaceProperties, {'agentDisplayName': 'Agent'}); expect(cs.sendDataModel, true); + expect(cs.components, hasLength(1)); + expect(cs.dataModel, {'name': 'Alice'}); + }); + + test('roundtrips callFunction through toJson/fromJson', () { + final original = CallFunctionMessage( + functionCallId: 'fc-9', + wantResponse: true, + call: 'capitalize', + args: {'value': 'x'}, + ); + + final roundtripped = + A2uiMessage.fromJson(original.toJson()) as CallFunctionMessage; + expect(roundtripped.functionCallId, 'fc-9'); + expect(roundtripped.wantResponse, true); + expect(roundtripped.call, 'capitalize'); + expect(roundtripped.args, {'value': 'x'}); + }); + + test('roundtrips actionResponse through toJson/fromJson', () { + final original = ActionResponseMessage(actionId: 'a-9', value: null); + + final roundtripped = + A2uiMessage.fromJson(original.toJson()) as ActionResponseMessage; + expect(roundtripped.actionId, 'a-9'); + expect(roundtripped.hasValue, isTrue); + expect(roundtripped.value, isNull); + }); + }); + + group('A2uiClientAction', () { + test('serializes actionId and wantResponse when set', () { + final action = A2uiClientAction( + name: 'submit', + surfaceId: 's1', + sourceComponentId: 'button1', + timestamp: DateTime.utc(2026), + context: {'a': 1}, + wantResponse: true, + actionId: 'act-1', + ); + + final Map json = action.toJson(); + expect(json['wantResponse'], true); + expect(json['actionId'], 'act-1'); + }); + + test('omits actionId and wantResponse by default', () { + final action = A2uiClientAction( + name: 'submit', + surfaceId: 's1', + sourceComponentId: 'button1', + timestamp: DateTime.utc(2026), + context: {}, + ); + + final Map json = action.toJson(); + expect(json.containsKey('wantResponse'), isFalse); + expect(json.containsKey('actionId'), isFalse); + }); + }); + + group('A2uiFunctionResponse', () { + test('serializes functionCallId, call, and value', () { + final response = A2uiFunctionResponse( + functionCallId: 'fc-1', + call: 'capitalize', + value: 'Hello', + ); + + expect(response.toJson(), { + 'functionCallId': 'fc-1', + 'call': 'capitalize', + 'value': 'Hello', + }); + }); + }); + + group('A2uiClientError', () { + test('serializes surface-scoped errors', () { + final error = A2uiClientError( + code: 'VALIDATION_FAILED', + surfaceId: 's1', + message: 'Bad input.', + ); + + final Map json = error.toJson(); + expect(json['surfaceId'], 's1'); + expect(json.containsKey('functionCallId'), isFalse); + }); + + test('serializes function-call-scoped errors', () { + final error = A2uiClientError( + code: 'INVALID_FUNCTION_CALL', + functionCallId: 'fc-1', + message: 'Not callable remotely.', + ); + + final Map json = error.toJson(); + expect(json['functionCallId'], 'fc-1'); + expect(json.containsKey('surfaceId'), isFalse); + }); + + test('asserts on both surfaceId and functionCallId', () { + expect( + () => A2uiClientError( + code: 'X', + surfaceId: 's1', + functionCallId: 'fc-1', + message: 'm', + ), + throwsA(isA()), + ); }); }); } diff --git a/packages/a2ui_core/test/processor_test.dart b/packages/a2ui_core/test/processor_test.dart index b35ae4c91..a7605a89c 100644 --- a/packages/a2ui_core/test/processor_test.dart +++ b/packages/a2ui_core/test/processor_test.dart @@ -5,12 +5,44 @@ import 'package:a2ui_core/src/core/catalog.dart'; import 'package:a2ui_core/src/core/common_schemas.dart'; import 'package:a2ui_core/src/core/component_model.dart'; +import 'package:a2ui_core/src/core/contexts.dart'; import 'package:a2ui_core/src/core/messages.dart'; import 'package:a2ui_core/src/core/minimal_catalog.dart'; import 'package:a2ui_core/src/core/surface_model.dart'; +import 'package:a2ui_core/src/primitives/cancellation.dart'; +import 'package:a2ui_core/src/primitives/errors.dart'; import 'package:a2ui_core/src/processing/processor.dart'; +import 'package:json_schema_builder/json_schema_builder.dart'; import 'package:test/test.dart'; +/// A function that may be invoked remotely via `callFunction` messages. +class EchoFunction extends FunctionImplementation { + @override + String get name => 'echo'; + + @override + A2uiReturnType get returnType => A2uiReturnType.string; + + @override + A2uiCallableFrom get callableFrom => A2uiCallableFrom.clientOrRemote; + + @override + Schema get argumentSchema => + Schema.object(properties: {'value': Schema.string()}); + + @override + Object? execute( + Map args, + DataContext context, [ + CancellationSignal? cancellationSignal, + ]) => args['value']; +} + +class RemoteFunctionCatalog extends Catalog { + RemoteFunctionCatalog() + : super(id: 'test:remote', components: [], functions: [EchoFunction()]); +} + void main() { group('MessageProcessor', () { late MinimalCatalog catalog; @@ -76,17 +108,184 @@ void main() { expect(processor.groupModel.getSurface('s1'), isNull); }); + test('creates surface with inline components and data model', () { + processor.processMessages([ + CreateSurfaceMessage( + surfaceId: 's1', + catalogId: catalog.id, + components: [ + {'id': 'root', 'component': 'Text', 'text': 'Hello'}, + ], + dataModel: { + 'user': {'name': 'Alice'}, + }, + ), + ]); + + final SurfaceModel? surface = processor.groupModel + .getSurface('s1'); + expect(surface?.componentsModel.get('root')?.properties['text'], 'Hello'); + expect(surface?.dataModel.get('/user/name'), 'Alice'); + }); + + test('throws on duplicate surfaceId', () { + processor.processMessages([ + CreateSurfaceMessage(surfaceId: 's1', catalogId: catalog.id), + ]); + + expect( + () => processor.processMessages([ + CreateSurfaceMessage(surfaceId: 's1', catalogId: catalog.id), + ]), + throwsA(isA()), + ); + }); + + test('deletes a data model key on explicit null value', () { + processor.processMessages([ + CreateSurfaceMessage(surfaceId: 's1', catalogId: catalog.id), + UpdateDataModelMessage( + surfaceId: 's1', + path: '/', + value: { + 'user': {'name': 'Alice', 'age': 30}, + }, + ), + UpdateDataModelMessage(surfaceId: 's1', path: '/user/age'), + ]); + + final SurfaceModel? surface = processor.groupModel + .getSurface('s1'); + expect(surface?.dataModel.get('/user'), {'name': 'Alice'}); + }); + + test('executes remotely callable functions and emits a response', () { + final responses = []; + final MessageProcessor remoteProcessor = MessageProcessor( + catalogs: [RemoteFunctionCatalog()], + onFunctionResponse: responses.add, + ); + + remoteProcessor.processMessages([ + CallFunctionMessage( + functionCallId: 'fc-1', + wantResponse: true, + call: 'echo', + args: {'value': 'hi'}, + ), + ]); + + expect(responses, hasLength(1)); + expect(responses.first.functionCallId, 'fc-1'); + expect(responses.first.call, 'echo'); + expect(responses.first.value, 'hi'); + }); + + test('rejects callFunction for clientOnly functions', () { + final errors = []; + final MessageProcessor localProcessor = MessageProcessor( + catalogs: [catalog], + onError: errors.add, + ); + + localProcessor.processMessages([ + // 'capitalize' in the minimal catalog defaults to clientOnly. + CallFunctionMessage( + functionCallId: 'fc-1', + call: 'capitalize', + args: {'value': 'hi'}, + ), + ]); + + expect(errors, hasLength(1)); + expect(errors.first.code, 'INVALID_FUNCTION_CALL'); + expect(errors.first.functionCallId, 'fc-1'); + }); + + test('rejects callFunction for unregistered functions', () { + final errors = []; + final MessageProcessor errorProcessor = MessageProcessor( + catalogs: [catalog], + onError: errors.add, + ); + + errorProcessor.processMessages([ + CallFunctionMessage(functionCallId: 'fc-1', call: 'doesNotExist'), + ]); + + expect(errors, hasLength(1)); + expect(errors.first.code, 'INVALID_FUNCTION_CALL'); + }); + + test('routes actionResponse values into the data model', () async { + final actions = []; + final MessageProcessor responseProcessor = MessageProcessor( + catalogs: [catalog], + onAction: actions.add, + ); + + responseProcessor.processMessages([ + CreateSurfaceMessage(surfaceId: 's1', catalogId: catalog.id), + ]); + final SurfaceModel surface = responseProcessor.groupModel + .getSurface('s1')!; + + await surface.dispatchAction({ + 'event': { + 'name': 'submit', + 'wantResponse': true, + 'responsePath': '/result', + }, + }, 'button1'); + + expect(actions, hasLength(1)); + final String actionId = actions.first.actionId!; + expect(actions.first.wantResponse, isTrue); + + responseProcessor.processMessages([ + ActionResponseMessage(actionId: actionId, value: 'ok'), + ]); + + expect(surface.dataModel.get('/result'), 'ok'); + }); + + test('throws on actionResponse for an unknown actionId', () { + expect( + () => processor.processMessages([ + ActionResponseMessage(actionId: 'nope', value: 1), + ]), + throwsA(isA()), + ); + }); + test('generates client capabilities with inline catalogs', () { final Map caps = processor.getClientCapabilities( includeInlineCatalogs: true, ); - final v09 = caps['v0.9'] as Map; - expect(v09['supportedCatalogIds'], contains(catalog.id)); + final v10 = caps['v1.0'] as Map; + expect(v10['supportedCatalogIds'], contains(catalog.id)); - final inline = v09['inlineCatalogs'] as List; + final inline = v10['inlineCatalogs'] as List; final first = inline.first as Map; expect(first['catalogId'], catalog.id); expect(first['components'], contains('Text')); + + // v1.0: functions are a map keyed by function name, with static + // returnType and callableFrom metadata. + final functions = first['functions'] as Map; + expect(functions, contains('capitalize')); + final capitalize = functions['capitalize'] as Map; + expect(capitalize['returnType'], 'string'); + expect(capitalize['callableFrom'], 'clientOnly'); + expect((capitalize['properties'] as Map)['call'], { + 'const': 'capitalize', + }); + + // v1.0: surfaceProperties and unified schemas live under $defs. + final defs = first[r'$defs'] as Map; + expect(defs, contains('anyComponent')); + expect(defs, contains('anyFunction')); + expect(defs, contains('surfaceProperties')); }); test('getClientCapabilities does not corrupt shared schemas', () { From ddc838dc280538fee4b601a5a5e28a5c72adecfc Mon Sep 17 00:00:00 2001 From: Seth Date: Wed, 8 Jul 2026 00:48:37 -0600 Subject: [PATCH 2/4] feat(genui): migrate to A2UI protocol v1.0 - Emit v1.0 envelopes for actions, errors, and message schemas - Rename theme to surfaceProperties in SurfaceDefinition and schemas - Add createSurface inline components/dataModel to message schema - Add callFunction/actionResponse message schemas for prompting - Send functionResponse and processor errors from SurfaceController - Serialize catalog functions as a v1.0 map with returnType/callableFrom; embed systemPromptFragments as catalog 'instructions' - Add callableFrom to ClientFunction (default clientOnly) - Add TextField.placeholder, Slider.steps, Video.posterUrl - Support @index system function in template loops (list/row/column) - Point basicCatalogId at the v1.0 basic catalog; refresh prompt goldens Co-Authored-By: Claude Fable 5 --- .../genui/lib/src/catalog/basic_catalog.dart | 6 +- .../catalog/basic_catalog_widgets/column.dart | 1 + .../catalog/basic_catalog_widgets/list.dart | 2 +- .../catalog/basic_catalog_widgets/row.dart | 1 + .../catalog/basic_catalog_widgets/slider.dart | 14 ++- .../basic_catalog_widgets/text_field.dart | 112 ++++++++++------- .../catalog/basic_catalog_widgets/video.dart | 32 ++++- .../lib/src/engine/surface_controller.dart | 44 ++++++- .../lib/src/functions/format_string.dart | 4 + .../src/model/a2ui_client_capabilities.dart | 2 +- .../genui/lib/src/model/a2ui_message.dart | 38 +++++- .../genui/lib/src/model/a2ui_schemas.dart | 70 ++++++++++- packages/genui/lib/src/model/catalog.dart | 24 ++-- .../genui/lib/src/model/client_function.dart | 28 +++++ packages/genui/lib/src/model/data_model.dart | 57 ++++++++- packages/genui/lib/src/model/ui_models.dart | 30 +++-- .../genui/lib/src/primitives/constants.dart | 2 +- .../transport/a2ui_parser_transformer.dart | 3 +- .../catalog/core_widgets/button_test.dart | 4 + packages/genui/test/catalog_test.dart | 15 ++- .../test/engine/surface_controller_test.dart | 4 +- packages/genui/test/error_reporting_test.dart | 2 +- .../all_operations_with_dataModel_false.txt | 116 ++++++++++++++++-- .../all_operations_with_dataModel_true.txt | 116 ++++++++++++++++-- ...create_and_update_with_dataModel_false.txt | 116 ++++++++++++++++-- .../create_and_update_with_dataModel_true.txt | 116 ++++++++++++++++-- .../create_only_with_dataModel_false.txt | 116 ++++++++++++++++-- .../create_only_with_dataModel_true.txt | 116 ++++++++++++++++-- .../update_only_with_dataModel_false.txt | 116 ++++++++++++++++-- .../update_only_with_dataModel_true.txt | 116 ++++++++++++++++-- .../test/test_infra/message_builders.dart | 16 ++- .../a2ui_parser_transformer_test.dart | 12 +- .../a2ui_transport_adapter_test.dart | 4 +- 33 files changed, 1262 insertions(+), 193 deletions(-) diff --git a/packages/genui/lib/src/catalog/basic_catalog.dart b/packages/genui/lib/src/catalog/basic_catalog.dart index b43fcd997..afe929b44 100644 --- a/packages/genui/lib/src/catalog/basic_catalog.dart +++ b/packages/genui/lib/src/catalog/basic_catalog.dart @@ -161,10 +161,10 @@ const String _basicCatalogRules = r''' 1. Create a surface: ```json { - "version": "v0.9", + "version": "v1.0", "createSurface": { "surfaceId": "main", - "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", "sendDataModel": true } } @@ -173,7 +173,7 @@ const String _basicCatalogRules = r''' 2. Update components: ```json { - "version": "v0.9", + "version": "v1.0", "updateComponents": { "surfaceId": "main", "components": [ diff --git a/packages/genui/lib/src/catalog/basic_catalog_widgets/column.dart b/packages/genui/lib/src/catalog/basic_catalog_widgets/column.dart index 6c92f537b..da8f201cf 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/column.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/column.dart @@ -165,6 +165,7 @@ final column = CatalogItem( componentId: componentId, dataContext: itemContext.dataContext.nested( DataPath('$dataBinding/${keys[i]}'), + templateIndex: i, ), buildChild: itemContext.buildChild, weight: weight, diff --git a/packages/genui/lib/src/catalog/basic_catalog_widgets/list.dart b/packages/genui/lib/src/catalog/basic_catalog_widgets/list.dart index 9517ba3e9..64f635009 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/list.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/list.dart @@ -115,7 +115,7 @@ final list = CatalogItem( final nestedPath = '$dataBinding/${keys[index]}'; final DataContext itemDataContext = itemContext.dataContext - .nested(DataPath(nestedPath)); + .nested(DataPath(nestedPath), templateIndex: index); final Widget child = itemContext.buildChild( componentId, itemDataContext, diff --git a/packages/genui/lib/src/catalog/basic_catalog_widgets/row.dart b/packages/genui/lib/src/catalog/basic_catalog_widgets/row.dart index 629aab2c3..3a6069d1e 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/row.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/row.dart @@ -162,6 +162,7 @@ final row = CatalogItem( componentId: componentId, dataContext: itemContext.dataContext.nested( DataPath('$dataBinding/${keys[i]}'), + templateIndex: i, ), buildChild: itemContext.buildChild, weight: weight, diff --git a/packages/genui/lib/src/catalog/basic_catalog_widgets/slider.dart b/packages/genui/lib/src/catalog/basic_catalog_widgets/slider.dart index 33e290669..55a8ed636 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/slider.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/slider.dart @@ -18,6 +18,12 @@ final _schema = S.object( 'value': A2uiSchemas.numberReference(), 'min': S.number(description: 'The minimum value. Defaults to 0.0.'), 'max': S.number(description: 'The maximum value. Defaults to 1.0.'), + 'steps': S.integer( + minimum: 1, + description: + 'The number of discrete divisions in the slider range. If ' + 'specified, the slider will snap to discrete values.', + ), 'label': A2uiSchemas.stringReference( description: 'The label for the slider.', ), @@ -31,17 +37,20 @@ extension type _SliderData.fromMap(JsonMap _json) { required JsonMap value, double? min, double? max, + int? steps, List? checks, }) => _SliderData.fromMap({ 'value': value, 'min': min, 'max': max, + 'steps': steps, 'checks': checks, }); Object get value => _json['value'] as Object; double get min => (_json['min'] as num?)?.toDouble() ?? 0.0; double get max => (_json['max'] as num?)?.toDouble() ?? 1.0; + int? get steps => (_json['steps'] as num?)?.toInt(); List? get checks => (_json['checks'] as List?)?.cast(); String? get label { @@ -65,6 +74,7 @@ extension type _SliderData.fromMap(JsonMap _json) { /// - `value`: The current value of the slider. /// - `min`: The minimum value of the slider. Defaults to 0.0. /// - `max`: The maximum value of the slider. Defaults to 1.0. +/// - `steps`: The number of discrete divisions in the slider range. /// - `label`: The label for the slider. final slider = CatalogItem( name: 'Slider', @@ -99,7 +109,9 @@ final slider = CatalogItem( value: (effectiveValue ?? sliderData.min).toDouble(), min: sliderData.min, max: sliderData.max, - divisions: (sliderData.max - sliderData.min).toInt(), + divisions: + sliderData.steps ?? + (sliderData.max - sliderData.min).toInt(), onChanged: (newValue) { itemContext.dataContext.update(DataPath(path), newValue); }, diff --git a/packages/genui/lib/src/catalog/basic_catalog_widgets/text_field.dart b/packages/genui/lib/src/catalog/basic_catalog_widgets/text_field.dart index cad6e5c0c..b4482daeb 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/text_field.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/text_field.dart @@ -22,6 +22,9 @@ final _schema = S.object( description: 'The value of the text field.', ), 'label': A2uiSchemas.stringReference(), + 'placeholder': A2uiSchemas.stringReference( + description: 'The placeholder text for the input field.', + ), 'variant': S.string( enumValues: ['shortText', 'longText', 'number', 'obscured'], ), @@ -35,6 +38,7 @@ extension type _TextFieldData.fromMap(JsonMap _json) { factory _TextFieldData({ Object? value, Object? label, + Object? placeholder, List? checks, String? variant, String? validationRegexp, @@ -42,6 +46,7 @@ extension type _TextFieldData.fromMap(JsonMap _json) { }) => _TextFieldData.fromMap({ 'value': value, 'label': label, + 'placeholder': placeholder, 'checks': checks, 'variant': variant, 'validationRegexp': validationRegexp, @@ -50,6 +55,7 @@ extension type _TextFieldData.fromMap(JsonMap _json) { Object? get value => _json['value']; Object? get label => _json['label']; + Object? get placeholder => _json['placeholder']; List? get checks => (_json['checks'] as List?)?.cast(); String? get variant => _json['variant'] as String?; String? get validationRegexp => _json['validationRegexp'] as String?; @@ -60,6 +66,7 @@ class _TextField extends StatefulWidget { const _TextField({ required this.initialValue, this.label, + this.placeholder, this.checks, this.context, this.textFieldType, @@ -70,6 +77,7 @@ class _TextField extends StatefulWidget { final String initialValue; final String? label; + final String? placeholder; final List? checks; final DataContext? context; final String? textFieldType; @@ -143,6 +151,7 @@ class _TextFieldState extends State<_TextField> { controller: _controller, decoration: InputDecoration( labelText: widget.label, + hintText: widget.placeholder, errorText: _errorText, ), obscureText: widget.textFieldType == 'obscured', @@ -176,6 +185,7 @@ class _TextFieldState extends State<_TextField> { /// /// - `text`: The initial value of the text field. /// - `label`: The text to display as the label for the text field. +/// - `placeholder`: The placeholder (hint) text for the input field. /// - `textFieldType`: The type of text field. Can be `shortText`, `longText`, /// `number`, `date`, or `obscured`. /// - `validationRegexp`: A regular expression to validate the input. @@ -226,58 +236,66 @@ final textField = CatalogItem( currentValue?.toString() ?? (valueRef is String ? valueRef : null); - return _TextField( - initialValue: effectiveValue ?? '', - label: label, - checks: textFieldData.checks, - context: itemContext.dataContext, - textFieldType: textFieldData.variant, - validationRegexp: textFieldData.validationRegexp, - onChanged: (newValue) { - if (textFieldData.variant == 'number') { - final num? numberValue = num.tryParse(newValue); - if (numberValue != null) { - itemContext.dataContext.update(DataPath(path), numberValue); + return BoundString( + dataContext: itemContext.dataContext, + value: textFieldData.placeholder, + builder: (context, placeholder) => _TextField( + initialValue: effectiveValue ?? '', + label: label, + placeholder: placeholder, + checks: textFieldData.checks, + context: itemContext.dataContext, + textFieldType: textFieldData.variant, + validationRegexp: textFieldData.validationRegexp, + onChanged: (newValue) { + if (textFieldData.variant == 'number') { + final num? numberValue = num.tryParse(newValue); + if (numberValue != null) { + itemContext.dataContext.update( + DataPath(path), + numberValue, + ); + return; + } + } + itemContext.dataContext.update(DataPath(path), newValue); + }, + onSubmitted: (newValue) async { + final JsonMap? actionData = textFieldData.onSubmittedAction; + if (actionData == null) { return; } - } - itemContext.dataContext.update(DataPath(path), newValue); - }, - onSubmitted: (newValue) async { - final JsonMap? actionData = textFieldData.onSubmittedAction; - if (actionData == null) { - return; - } - if (actionData.containsKey('event')) { - final eventMap = actionData['event'] as JsonMap; - final actionName = eventMap['name'] as String; - final contextDefinition = eventMap['context'] as JsonMap?; - final JsonMap resolvedContext = await resolveContext( - itemContext.dataContext, - contextDefinition, - ); - itemContext.dispatchEvent( - UserActionEvent( - name: actionName, - sourceComponentId: itemContext.id, - context: resolvedContext, - ), - ); - } else if (actionData.containsKey('functionCall')) { - final funcMap = actionData['functionCall'] as JsonMap; - final callName = funcMap['call'] as String; - if (callName == 'closeModal') { - if (itemContext.buildContext.mounted) { - Navigator.of(itemContext.buildContext).pop(); + if (actionData.containsKey('event')) { + final eventMap = actionData['event'] as JsonMap; + final actionName = eventMap['name'] as String; + final contextDefinition = eventMap['context'] as JsonMap?; + final JsonMap resolvedContext = await resolveContext( + itemContext.dataContext, + contextDefinition, + ); + itemContext.dispatchEvent( + UserActionEvent( + name: actionName, + sourceComponentId: itemContext.id, + context: resolvedContext, + ), + ); + } else if (actionData.containsKey('functionCall')) { + final funcMap = actionData['functionCall'] as JsonMap; + final callName = funcMap['call'] as String; + if (callName == 'closeModal') { + if (itemContext.buildContext.mounted) { + Navigator.of(itemContext.buildContext).pop(); + } + return; } - return; + final Stream resultStream = itemContext.dataContext + .resolve(funcMap); + await resultStream.first; } - final Stream resultStream = itemContext.dataContext - .resolve(funcMap); - await resultStream.first; - } - }, + }, + ), ); }, ); diff --git a/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart b/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart index 812847cb0..d33279436 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart @@ -20,6 +20,10 @@ final _schema = S.object( 'url': A2uiSchemas.stringReference( description: 'The URL of the video to play.', ), + 'posterUrl': A2uiSchemas.stringReference( + description: + 'The URL of the poster image to display before the video plays.', + ), }, required: ['url'], ); @@ -33,17 +37,26 @@ bool get _isVideoSupported => /// ## Parameters: /// /// - `url`: The URL of the video to play. +/// - `posterUrl`: The URL of a poster image shown before the video plays. final video = CatalogItem( name: 'Video', dataSchema: _schema, widgetBuilder: (itemContext) { - final Object? url = (itemContext.data as JsonMap)['url']; + final data = itemContext.data as JsonMap; + final Object? url = data['url']; + final Object? posterUrl = data['posterUrl']; return BoundString( dataContext: itemContext.dataContext, value: url, builder: (context, urlValue) { - return _VideoPlayerWidget(url: urlValue); + return BoundString( + dataContext: itemContext.dataContext, + value: posterUrl, + builder: (context, posterUrlValue) { + return _VideoPlayerWidget(url: urlValue, posterUrl: posterUrlValue); + }, + ); }, ); }, @@ -61,9 +74,10 @@ final video = CatalogItem( ); class _VideoPlayerWidget extends StatefulWidget { - const _VideoPlayerWidget({required this.url}); + const _VideoPlayerWidget({required this.url, this.posterUrl}); final String? url; + final String? posterUrl; @override State<_VideoPlayerWidget> createState() => _VideoPlayerWidgetState(); @@ -169,9 +183,17 @@ class _VideoPlayerWidgetState extends State<_VideoPlayerWidget> { } if (controller == null || !controller.value.isInitialized) { - return const AspectRatio( + final String? posterUrl = widget.posterUrl; + return AspectRatio( aspectRatio: 16 / 9, - child: Center(child: CircularProgressIndicator()), + child: posterUrl == null || posterUrl.isEmpty + ? const Center(child: CircularProgressIndicator()) + : Image.network( + posterUrl, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) => + const Center(child: CircularProgressIndicator()), + ), ); } diff --git a/packages/genui/lib/src/engine/surface_controller.dart b/packages/genui/lib/src/engine/surface_controller.dart index 0f1ebb28d..c49b15665 100644 --- a/packages/genui/lib/src/engine/surface_controller.dart +++ b/packages/genui/lib/src/engine/surface_controller.dart @@ -39,6 +39,8 @@ interface class SurfaceController implements SurfaceHost, A2uiMessageSink { }) { _processor = core.MessageProcessor( catalogs: catalogs.map(coreCatalogFor).toList(), + onFunctionResponse: _onFunctionResponse, + onError: _onProcessorError, ); _processor.groupModel.onSurfaceCreated.addListener(_onCoreSurfaceCreated); _processor.groupModel.onSurfaceDeleted.addListener(_onCoreSurfaceDeleted); @@ -278,7 +280,7 @@ interface class SurfaceController implements SurfaceHost, A2uiMessageSink { } final Map errorMsg = { - 'version': 'v0.9', + 'version': core.a2uiProtocolVersion, 'error': { 'code': errorCode, 'surfaceId': ?surfaceId, @@ -294,6 +296,41 @@ interface class SurfaceController implements SurfaceHost, A2uiMessageSink { ); } + /// Sends the result of a server-initiated function call back to the server. + void _onFunctionResponse(core.A2uiFunctionResponse response) { + _onSubmit.add( + ChatMessage.user( + '', + parts: [ + UiInteractionPart.create( + jsonEncode({ + 'version': core.a2uiProtocolVersion, + 'functionResponse': response.toJson(), + }), + ), + ], + ), + ); + } + + /// Sends a processor-level client error (e.g. an invalid `callFunction`) + /// to the server. + void _onProcessorError(core.A2uiClientError error) { + _onSubmit.add( + ChatMessage.user( + '', + parts: [ + UiInteractionPart.create( + jsonEncode({ + 'version': core.a2uiProtocolVersion, + 'error': error.toJson(), + }), + ), + ], + ), + ); + } + void _bufferMessage(String surfaceId, core.A2uiMessage message) { _pendingUpdates.putIfAbsent(surfaceId, () => []).add(message); if (!_pendingUpdateTimers.containsKey(surfaceId)) { @@ -313,7 +350,10 @@ interface class SurfaceController implements SurfaceHost, A2uiMessageSink { '', parts: [ UiInteractionPart.create( - jsonEncode({'version': 'v0.9', 'action': event.toMap()}), + jsonEncode({ + 'version': core.a2uiProtocolVersion, + 'action': event.toMap(), + }), ), ], ), diff --git a/packages/genui/lib/src/functions/format_string.dart b/packages/genui/lib/src/functions/format_string.dart index c966ec01f..1578b7193 100644 --- a/packages/genui/lib/src/functions/format_string.dart +++ b/packages/genui/lib/src/functions/format_string.dart @@ -19,6 +19,10 @@ class FormatStringFunction implements ClientFunction { @override String get name => 'formatString'; + @override + ClientFunctionCallableFrom get callableFrom => + ClientFunctionCallableFrom.clientOnly; + @override String get description => r''' Performs string interpolation of data model values and other functions in the diff --git a/packages/genui/lib/src/model/a2ui_client_capabilities.dart b/packages/genui/lib/src/model/a2ui_client_capabilities.dart index b9a2263d1..ac06fcb82 100644 --- a/packages/genui/lib/src/model/a2ui_client_capabilities.dart +++ b/packages/genui/lib/src/model/a2ui_client_capabilities.dart @@ -92,6 +92,6 @@ class A2UiClientCapabilities { if (inlineCatalogs != null) { json['inlineCatalogs'] = inlineCatalogs; } - return {'v0.9': json}; + return {'v1.0': json}; } } diff --git a/packages/genui/lib/src/model/a2ui_message.dart b/packages/genui/lib/src/model/a2ui_message.dart index f357683d5..0566c6fb9 100644 --- a/packages/genui/lib/src/model/a2ui_message.dart +++ b/packages/genui/lib/src/model/a2ui_message.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:a2ui_core/a2ui_core.dart' as core; import 'package:json_schema_builder/json_schema_builder.dart'; import 'a2ui_schemas.dart'; @@ -20,7 +21,7 @@ Schema a2uiMessageSchema(Catalog catalog) { oneOf: [ S.object( properties: { - 'version': S.string(constValue: 'v0.9'), + 'version': S.string(constValue: core.a2uiProtocolVersion), 'createSurface': A2uiSchemas.createSurfaceSchema(), }, required: ['version', 'createSurface'], @@ -28,7 +29,7 @@ Schema a2uiMessageSchema(Catalog catalog) { ), S.object( properties: { - 'version': S.string(constValue: 'v0.9'), + 'version': S.string(constValue: core.a2uiProtocolVersion), 'updateComponents': A2uiSchemas.updateComponentsSchema(catalog), }, required: ['version', 'updateComponents'], @@ -36,7 +37,7 @@ Schema a2uiMessageSchema(Catalog catalog) { ), S.object( properties: { - 'version': S.string(constValue: 'v0.9'), + 'version': S.string(constValue: core.a2uiProtocolVersion), 'updateDataModel': A2uiSchemas.updateDataModelSchema(), }, required: ['version', 'updateDataModel'], @@ -44,12 +45,41 @@ Schema a2uiMessageSchema(Catalog catalog) { ), S.object( properties: { - 'version': S.string(constValue: 'v0.9'), + 'version': S.string(constValue: core.a2uiProtocolVersion), 'deleteSurface': A2uiSchemas.deleteSurfaceSchema(), }, required: ['version', 'deleteSurface'], additionalProperties: false, ), + S.object( + properties: { + 'version': S.string(constValue: core.a2uiProtocolVersion), + 'functionCallId': S.string( + description: + 'Unique ID for the instance of this function call. It is ' + 'copied verbatim into the functionResponse or error.', + ), + 'wantResponse': S.boolean( + description: + 'If true, the client returns a functionResponse with the ' + 'result of the call.', + ), + 'callFunction': A2uiSchemas.callFunctionSchema(), + }, + required: ['version', 'callFunction', 'functionCallId'], + additionalProperties: false, + ), + S.object( + properties: { + 'version': S.string(constValue: core.a2uiProtocolVersion), + 'actionId': S.string( + description: 'The ID of the action call this response belongs to.', + ), + 'actionResponse': A2uiSchemas.actionResponseSchema(), + }, + required: ['version', 'actionResponse', 'actionId'], + additionalProperties: false, + ), ], ); } diff --git a/packages/genui/lib/src/model/a2ui_schemas.dart b/packages/genui/lib/src/model/a2ui_schemas.dart index 3e92022c9..a7c7b4de0 100644 --- a/packages/genui/lib/src/model/a2ui_schemas.dart +++ b/packages/genui/lib/src/model/a2ui_schemas.dart @@ -42,12 +42,13 @@ abstract final class A2uiSchemas { required String returnType, required Schema args, }) { + // The returnType is static catalog metadata in A2UI v1.0 and is not + // present in wire-level FunctionCall payloads. return S.object( - description: description, + description: '$description Returns $returnType.', properties: { 'call': S.string(constValue: name), 'args': args, - 'returnType': S.string(constValue: returnType), }, required: ['call', 'args'], ); @@ -278,6 +279,40 @@ abstract final class A2uiSchemas { required: ['call'], ); + /// Schema for the body of a server-initiated callFunction message. + static Schema callFunctionSchema() => S.object( + description: + 'A function invoked from the server. The function must be ' + "registered in the catalog with a 'callableFrom' of 'remoteOnly' " + "or 'clientOrRemote'.", + properties: { + 'call': S.string(description: 'The name of the function to call.'), + 'args': S.object( + description: 'Arguments passed to the function.', + additionalProperties: true, + ), + }, + required: ['call'], + ); + + /// Schema for the body of an actionResponse message. + static Schema actionResponseSchema() => S.object( + description: + 'A response to a client-initiated action that requested a response ' + '(wantResponse: true). Contains exactly one of value or error.', + properties: { + 'value': S.any(description: 'The return value of the action.'), + 'error': S.object( + properties: { + 'code': S.string(), + 'message': S.string(), + }, + required: ['code', 'message'], + additionalProperties: false, + ), + }, + ); + /// Schema for a validation check, including logic and an error message. static Schema validationCheck({String? description}) { return S.object( @@ -395,6 +430,16 @@ abstract final class A2uiSchemas { description: 'Arbitrary context data to send with the action.', additionalProperties: true, ), + 'wantResponse': S.boolean( + description: + 'If true, the client expects an actionResponse from the ' + 'server.', + ), + 'responsePath': S.string( + description: + 'Optional JSON Pointer path where the client should save ' + 'the response value in its local data model.', + ), }, required: ['name'], ), @@ -442,13 +487,27 @@ abstract final class A2uiSchemas { 'to prefix this with an internet domain that you own, to avoid ' "conflicts e.g. 'mycompany.com:somecatalog'.", ), - 'theme': S.object( - description: 'Theme parameters for the surface.', + 'surfaceProperties': S.object( + description: + "Initial surface properties (e.g., {'agentDisplayName': " + "'My Agent'}). These must validate against the " + "'surfaceProperties' schema defined in the catalog.", additionalProperties: true, ), 'sendDataModel': S.boolean( description: 'Whether to send the data model to every client request.', ), + 'components': S.list( + description: + 'Optional initial list of UI components for the surface, ' + 'allowing an entire UI to be created in a single message.', + minItems: 1, + items: S.any(), + ), + 'dataModel': S.object( + description: 'The initial root data model object for the surface.', + additionalProperties: true, + ), }, required: [surfaceIdKey, 'catalogId'], ); @@ -475,7 +534,8 @@ abstract final class A2uiSchemas { 'path': S.combined(type: JsonType.string, defaultValue: '/'), 'value': S.any( description: - 'The new value to write to the data model. If null/omitted, the key is removed.', + 'The new value to write to the data model. An explicit null ' + 'value deletes the key at the path.', ), }, required: [surfaceIdKey], diff --git a/packages/genui/lib/src/model/catalog.dart b/packages/genui/lib/src/model/catalog.dart index 0c00b2074..a08b3de36 100644 --- a/packages/genui/lib/src/model/catalog.dart +++ b/packages/genui/lib/src/model/catalog.dart @@ -174,23 +174,33 @@ interface class Catalog { /// within `A2UiClientCapabilities`. /// /// This differs from [definition] because `a2ui_client_capabilities.json` - /// expects `components` to be a direct map of name to schema, and `functions` - /// to be an array of objects. + /// expects `components` to be a direct map of name to schema. Functions are + /// a map of function name to a v1.0 FunctionDefinition, which validates the + /// wire-level FunctionCall and carries static `returnType` and + /// `callableFrom` metadata. JsonMap toCapabilitiesJson() { return { 'catalogId': effectiveCatalogId, + if (systemPromptFragments.isNotEmpty) + 'instructions': systemPromptFragments.join('\n\n'), 'components': { for (final item in items) item.name: item.dataSchema.value, }, - 'functions': [ + 'functions': { for (final func in functions) - { - 'name': func.name, + func.name: { + 'type': 'object', 'description': func.description, - 'parameters': func.argumentSchema.value, + 'properties': { + 'call': {'const': func.name}, + 'args': func.argumentSchema.value, + }, + 'required': ['call'], + 'additionalProperties': false, 'returnType': func.returnType.value, + 'callableFrom': func.callableFrom.value, }, - ], + }, }; } diff --git a/packages/genui/lib/src/model/client_function.dart b/packages/genui/lib/src/model/client_function.dart index d025cacf4..d7e712ce5 100644 --- a/packages/genui/lib/src/model/client_function.dart +++ b/packages/genui/lib/src/model/client_function.dart @@ -64,6 +64,25 @@ enum ClientFunctionReturnType { final String value; } +/// Where a client function can be invoked from. +/// +/// This is static catalog metadata; the client rejects `callFunction` +/// messages for functions that are not remotely callable with an +/// `INVALID_FUNCTION_CALL` error. +enum ClientFunctionCallableFrom { + /// Only callable locally on the client (e.g. in data bindings). + clientOnly('clientOnly'), + + /// Only callable by the server via `callFunction` messages. + remoteOnly('remoteOnly'), + + /// Callable both locally and via `callFunction` messages. + clientOrRemote('clientOrRemote'); + + const ClientFunctionCallableFrom(this.value); + final String value; +} + abstract interface class ClientFunction { /// The name of the function as used in expressions (e.g. 'stringFormat'). String get name; @@ -79,6 +98,11 @@ abstract interface class ClientFunction { /// Defaults to [ClientFunctionReturnType.any]. ClientFunctionReturnType get returnType => ClientFunctionReturnType.any; + /// Where this function may be invoked from. + /// Defaults to [ClientFunctionCallableFrom.clientOnly]. + ClientFunctionCallableFrom get callableFrom => + ClientFunctionCallableFrom.clientOnly; + /// Invokes the function with the given [args]. /// /// Returns a stream of values. @@ -104,6 +128,10 @@ abstract interface class ClientFunction { abstract class SynchronousClientFunction implements ClientFunction { const SynchronousClientFunction(); + @override + ClientFunctionCallableFrom get callableFrom => + ClientFunctionCallableFrom.clientOnly; + @override Stream execute(JsonMap args, ExecutionContext context) { try { diff --git a/packages/genui/lib/src/model/data_model.dart b/packages/genui/lib/src/model/data_model.dart index 9f9e88f61..250378c5e 100644 --- a/packages/genui/lib/src/model/data_model.dart +++ b/packages/genui/lib/src/model/data_model.dart @@ -24,12 +24,18 @@ class DataContext implements cf.ExecutionContext { this._dataModel, this.path, { Iterable? functions, + this.templateIndex, }) : _functions = { if (functions != null) for (final f in functions) f.name: f, }; - DataContext._(this._dataModel, this.path, this._functions); + DataContext._( + this._dataModel, + this.path, + this._functions, { + this.templateIndex, + }); final DataModel _dataModel; @@ -39,6 +45,11 @@ class DataContext implements cf.ExecutionContext { final Map _functions; + /// The 0-based iteration index when this context was created for an item + /// of a template-generated list, or null outside of template instantiation + /// (Collection Scope). Used to evaluate the `@index` system function. + final int? templateIndex; + /// The underlying data model for this context. DataModel get dataModel => _dataModel; @@ -95,8 +106,13 @@ class DataContext implements cf.ExecutionContext { /// Creates a new, nested DataContext for a child widget. @override - DataContext nested(DataPath relativePath) => - DataContext._(_dataModel, resolvePath(relativePath), _functions); + DataContext nested(DataPath relativePath, {int? templateIndex}) => + DataContext._( + _dataModel, + resolvePath(relativePath), + _functions, + templateIndex: templateIndex ?? this.templateIndex, + ); /// Resolves a path against the current context's path. @override @@ -127,6 +143,11 @@ class DataContext implements cf.ExecutionContext { return Stream.value(null); } + // The '@' prefix is reserved for core system context evaluations. + if (name.startsWith('@')) { + return _evaluateSystemFunction(name, callDefinition); + } + final cf.ClientFunction? func = getFunction(name); if (func == null) { genUiLogger.warning('Function not found: $name'); @@ -162,6 +183,36 @@ class DataContext implements cf.ExecutionContext { }); } + /// Evaluates a reserved `@`-prefixed core system function. + /// + /// Only `@index` is defined by A2UI v1.0; it is valid solely within + /// template instantiation loops (Collection Scope). + Stream _evaluateSystemFunction(String name, JsonMap callDefinition) { + if (name != '@index') { + genUiLogger.warning( + "Unknown system function '$name'. The '@' prefix is reserved for " + 'core system context evaluations.', + ); + return Stream.value(null); + } + final int? index = templateIndex; + if (index == null) { + genUiLogger.warning( + "'@index' can only be evaluated inside a template instantiation " + 'loop.', + ); + return Stream.value(null); + } + final Object? argsJson = callDefinition['args']; + final Object? offsetArg = argsJson is Map ? argsJson['offset'] : null; + if (offsetArg == null) { + return Stream.value(index); + } + return _evaluateStream( + offsetArg, + ).map((offset) => index + ((offset as num?)?.toInt() ?? 0)); + } + /// Evaluates a dynamic boolean condition and returns a [Stream]. @override Stream evaluateConditionStream(Object? condition) { diff --git a/packages/genui/lib/src/model/ui_models.dart b/packages/genui/lib/src/model/ui_models.dart index 8f9a2fa4e..567441071 100644 --- a/packages/genui/lib/src/model/ui_models.dart +++ b/packages/genui/lib/src/model/ui_models.dart @@ -58,12 +58,16 @@ extension type UserActionEvent.fromMap(JsonMap _json) implements UiEvent { required String sourceComponentId, DateTime? timestamp, JsonMap? context, + bool wantResponse = false, + String? actionId, }) : _json = { surfaceIdKey: ?surfaceId, 'name': name, 'sourceComponentId': sourceComponentId, 'timestamp': (timestamp ?? DateTime.now()).toIso8601String(), 'context': context ?? {}, + if (wantResponse) 'wantResponse': wantResponse, + 'actionId': ?actionId, }; /// The name of the action. @@ -74,12 +78,18 @@ extension type UserActionEvent.fromMap(JsonMap _json) implements UiEvent { /// Context associated with the action. JsonMap get context => _json['context'] as JsonMap; + + /// Whether the client expects an `actionResponse` from the server. + bool get wantResponse => _json['wantResponse'] as bool? ?? false; + + /// Unique ID for this action call, set when [wantResponse] is true. + String? get actionId => _json['actionId'] as String?; } final class _Json { static const String catalogId = 'catalogId'; static const String components = 'components'; - static const String theme = 'theme'; + static const String surfaceProperties = 'surfaceProperties'; } /// A data object that represents the entire UI definition. @@ -92,7 +102,7 @@ class SurfaceDefinition { required this.surfaceId, this.catalogId = basicCatalogId, Map components = const {}, - this.theme, + this.surfaceProperties, }) : _components = components; /// Creates a [SurfaceDefinition] from a JSON map. @@ -105,7 +115,7 @@ class SurfaceDefinition { (key, value) => MapEntry(key, Component.fromJson(value as JsonMap)), ) ?? const {}, - theme: json[_Json.theme] as JsonMap?, + surfaceProperties: json[_Json.surfaceProperties] as JsonMap?, ); } @@ -118,7 +128,9 @@ class SurfaceDefinition { for (final core.ComponentModel component in surface.componentsModel.all) component.id: Component.fromCore(component), }, - theme: surface.theme.isEmpty ? null : JsonMap.from(surface.theme), + surfaceProperties: surface.surfaceProperties.isEmpty + ? null + : JsonMap.from(surface.surfaceProperties), ); } @@ -132,20 +144,20 @@ class SurfaceDefinition { Map get components => UnmodifiableMapView(_components); final Map _components; - /// The theme for this surface. - final JsonMap? theme; + /// The surface properties for this surface (e.g. agentDisplayName). + final JsonMap? surfaceProperties; /// Creates a copy of this [SurfaceDefinition] with the given fields replaced. SurfaceDefinition copyWith({ String? catalogId, Map? components, - JsonMap? theme, + JsonMap? surfaceProperties, }) { return SurfaceDefinition( surfaceId: surfaceId, catalogId: catalogId ?? this.catalogId, components: components ?? _components, - theme: theme ?? this.theme, + surfaceProperties: surfaceProperties ?? this.surfaceProperties, ); } @@ -157,7 +169,7 @@ class SurfaceDefinition { _Json.components: components.map( (key, value) => MapEntry(key, value.toJson()), ), - _Json.theme: ?theme, + _Json.surfaceProperties: ?surfaceProperties, }; } diff --git a/packages/genui/lib/src/primitives/constants.dart b/packages/genui/lib/src/primitives/constants.dart index 9203a0046..72e3dbc92 100644 --- a/packages/genui/lib/src/primitives/constants.dart +++ b/packages/genui/lib/src/primitives/constants.dart @@ -4,4 +4,4 @@ /// The catalog ID for the basic catalog. const String basicCatalogId = - 'https://a2ui.org/specification/v0_9/basic_catalog.json'; + 'https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json'; diff --git a/packages/genui/lib/src/transport/a2ui_parser_transformer.dart b/packages/genui/lib/src/transport/a2ui_parser_transformer.dart index 50597d30a..c48a53819 100644 --- a/packages/genui/lib/src/transport/a2ui_parser_transformer.dart +++ b/packages/genui/lib/src/transport/a2ui_parser_transformer.dart @@ -237,7 +237,8 @@ class _A2uiParserStream { return core.A2uiMessage.fromJson(json); } on core.A2uiValidationError catch (e) { final String message = e.message.contains("'version'") - ? 'A2UI message must have version "v0.9"' + ? 'A2UI message must have version ' + '"${core.a2uiProtocolVersion}"' : e.message; throw A2uiValidationException(message, json: json, cause: e); } diff --git a/packages/genui/test/catalog/core_widgets/button_test.dart b/packages/genui/test/catalog/core_widgets/button_test.dart index 8743a9691..4957976b4 100644 --- a/packages/genui/test/catalog/core_widgets/button_test.dart +++ b/packages/genui/test/catalog/core_widgets/button_test.dart @@ -274,6 +274,10 @@ class MockFunction implements ClientFunction { @override ClientFunctionReturnType get returnType => ClientFunctionReturnType.any; + @override + ClientFunctionCallableFrom get callableFrom => + ClientFunctionCallableFrom.clientOnly; + final Stream Function(JsonMap args, ExecutionContext context) onExecute; diff --git a/packages/genui/test/catalog_test.dart b/packages/genui/test/catalog_test.dart index 192404d47..a0045d761 100644 --- a/packages/genui/test/catalog_test.dart +++ b/packages/genui/test/catalog_test.dart @@ -211,13 +211,16 @@ void main() { expect(textSchema.containsKey('properties'), isTrue); expect(json.containsKey('functions'), isTrue); - final functions = json['functions'] as List; + // v1.0: functions are a map keyed by function name. + final functions = json['functions'] as JsonMap; expect(functions.length, 1); - final firstFunc = functions.first as JsonMap; - expect(firstFunc['name'], 'regex'); - expect(firstFunc['description'], isNotEmpty); - expect(firstFunc['returnType'], 'boolean'); - expect(firstFunc.containsKey('parameters'), isTrue); + final regexFunc = functions['regex'] as JsonMap; + expect(regexFunc['description'], isNotEmpty); + expect(regexFunc['returnType'], 'boolean'); + expect(regexFunc['callableFrom'], 'clientOnly'); + final funcProps = regexFunc['properties'] as JsonMap; + expect(funcProps['call'], {'const': 'regex'}); + expect(funcProps.containsKey('args'), isTrue); }); }); } diff --git a/packages/genui/test/engine/surface_controller_test.dart b/packages/genui/test/engine/surface_controller_test.dart index cac77d9b2..e8f0168ff 100644 --- a/packages/genui/test/engine/surface_controller_test.dart +++ b/packages/genui/test/engine/surface_controller_test.dart @@ -268,7 +268,7 @@ void main() { expect(message.parts.uiInteractionParts, hasLength(1)); final String expectedJson = jsonEncode({ - 'version': 'v0.9', + 'version': 'v1.0', 'action': { 'surfaceId': 'testSurface', 'name': 'testAction', @@ -315,7 +315,7 @@ void main() { final UiInteractionPart part = message.parts.uiInteractionParts.first; final errorJson = jsonDecode(part.interaction) as Map; - expect(errorJson['version'], 'v0.9'); + expect(errorJson['version'], 'v1.0'); final Object? errorObj = errorJson['error']; expect(errorObj, isA>()); final errorMap = errorObj! as Map; diff --git a/packages/genui/test/error_reporting_test.dart b/packages/genui/test/error_reporting_test.dart index fcd30711d..906c3e673 100644 --- a/packages/genui/test/error_reporting_test.dart +++ b/packages/genui/test/error_reporting_test.dart @@ -30,7 +30,7 @@ void main() { 'type', () { final json = { - 'version': 'v0.9', + 'version': 'v1.0', 'unknownAction': {}, }; diff --git a/packages/genui/test/facade/prompt_builder_test.golden/all_operations_with_dataModel_false.txt b/packages/genui/test/facade/prompt_builder_test.golden/all_operations_with_dataModel_false.txt index 3d27339c3..783520787 100644 --- a/packages/genui/test/facade/prompt_builder_test.golden/all_operations_with_dataModel_false.txt +++ b/packages/genui/test/facade/prompt_builder_test.golden/all_operations_with_dataModel_false.txt @@ -43,10 +43,10 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 1. Create a surface: ```json { - "version": "v0.9", + "version": "v1.0", "createSurface": { "surfaceId": "main", - "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", "sendDataModel": true } } @@ -55,7 +55,7 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 2. Update components: ```json { - "version": "v0.9", + "version": "v1.0", "updateComponents": { "surfaceId": "main", "components": [ @@ -139,7 +139,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "createSurface": { "type": "object", @@ -153,14 +153,25 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "type": "string", "description": "A string that uniquely identifies this catalog. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. 'mycompany.com:somecatalog'." }, - "theme": { + "surfaceProperties": { "type": "object", - "description": "Theme parameters for the surface.", + "description": "Initial surface properties (e.g., {'agentDisplayName': 'My Agent'}). These must validate against the 'surfaceProperties' schema defined in the catalog.", "additionalProperties": true }, "sendDataModel": { "type": "boolean", "description": "Whether to send the data model to every client request." + }, + "components": { + "type": "array", + "description": "Optional initial list of UI components for the surface, allowing an entire UI to be created in a single message.", + "items": {}, + "minItems": 1 + }, + "dataModel": { + "type": "object", + "description": "The initial root data model object for the surface.", + "additionalProperties": true } }, "required": [ @@ -180,7 +191,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateComponents": { "type": "object", @@ -286,7 +297,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateDataModel": { "type": "object", @@ -300,7 +311,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "default": "/" }, "value": { - "description": "The new value to write to the data model. If null/omitted, the key is removed." + "description": "The new value to write to the data model. An explicit null value deletes the key at the path." } }, "required": [ @@ -319,7 +330,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "deleteSurface": { "type": "object", @@ -339,6 +350,91 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "deleteSurface" ], "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "functionCallId": { + "type": "string", + "description": "Unique ID for the instance of this function call. It is copied verbatim into the functionResponse or error." + }, + "wantResponse": { + "type": "boolean", + "description": "If true, the client returns a functionResponse with the result of the call." + }, + "callFunction": { + "type": "object", + "description": "A function invoked from the server. The function must be registered in the catalog with a 'callableFrom' of 'remoteOnly' or 'clientOrRemote'.", + "properties": { + "call": { + "type": "string", + "description": "The name of the function to call." + }, + "args": { + "type": "object", + "description": "Arguments passed to the function.", + "additionalProperties": true + } + }, + "required": [ + "call" + ] + } + }, + "required": [ + "version", + "callFunction", + "functionCallId" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "actionId": { + "type": "string", + "description": "The ID of the action call this response belongs to." + }, + "actionResponse": { + "type": "object", + "description": "A response to a client-initiated action that requested a response (wantResponse: true). Contains exactly one of value or error.", + "properties": { + "value": { + "description": "The return value of the action." + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "additionalProperties": false + } + } + } + }, + "required": [ + "version", + "actionResponse", + "actionId" + ], + "additionalProperties": false } ] } diff --git a/packages/genui/test/facade/prompt_builder_test.golden/all_operations_with_dataModel_true.txt b/packages/genui/test/facade/prompt_builder_test.golden/all_operations_with_dataModel_true.txt index 7661b3a97..c7a56c50b 100644 --- a/packages/genui/test/facade/prompt_builder_test.golden/all_operations_with_dataModel_true.txt +++ b/packages/genui/test/facade/prompt_builder_test.golden/all_operations_with_dataModel_true.txt @@ -43,10 +43,10 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 1. Create a surface: ```json { - "version": "v0.9", + "version": "v1.0", "createSurface": { "surfaceId": "main", - "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", "sendDataModel": true } } @@ -55,7 +55,7 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 2. Update components: ```json { - "version": "v0.9", + "version": "v1.0", "updateComponents": { "surfaceId": "main", "components": [ @@ -141,7 +141,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "createSurface": { "type": "object", @@ -155,14 +155,25 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "type": "string", "description": "A string that uniquely identifies this catalog. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. 'mycompany.com:somecatalog'." }, - "theme": { + "surfaceProperties": { "type": "object", - "description": "Theme parameters for the surface.", + "description": "Initial surface properties (e.g., {'agentDisplayName': 'My Agent'}). These must validate against the 'surfaceProperties' schema defined in the catalog.", "additionalProperties": true }, "sendDataModel": { "type": "boolean", "description": "Whether to send the data model to every client request." + }, + "components": { + "type": "array", + "description": "Optional initial list of UI components for the surface, allowing an entire UI to be created in a single message.", + "items": {}, + "minItems": 1 + }, + "dataModel": { + "type": "object", + "description": "The initial root data model object for the surface.", + "additionalProperties": true } }, "required": [ @@ -182,7 +193,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateComponents": { "type": "object", @@ -288,7 +299,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateDataModel": { "type": "object", @@ -302,7 +313,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "default": "/" }, "value": { - "description": "The new value to write to the data model. If null/omitted, the key is removed." + "description": "The new value to write to the data model. An explicit null value deletes the key at the path." } }, "required": [ @@ -321,7 +332,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "deleteSurface": { "type": "object", @@ -341,6 +352,91 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "deleteSurface" ], "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "functionCallId": { + "type": "string", + "description": "Unique ID for the instance of this function call. It is copied verbatim into the functionResponse or error." + }, + "wantResponse": { + "type": "boolean", + "description": "If true, the client returns a functionResponse with the result of the call." + }, + "callFunction": { + "type": "object", + "description": "A function invoked from the server. The function must be registered in the catalog with a 'callableFrom' of 'remoteOnly' or 'clientOrRemote'.", + "properties": { + "call": { + "type": "string", + "description": "The name of the function to call." + }, + "args": { + "type": "object", + "description": "Arguments passed to the function.", + "additionalProperties": true + } + }, + "required": [ + "call" + ] + } + }, + "required": [ + "version", + "callFunction", + "functionCallId" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "actionId": { + "type": "string", + "description": "The ID of the action call this response belongs to." + }, + "actionResponse": { + "type": "object", + "description": "A response to a client-initiated action that requested a response (wantResponse: true). Contains exactly one of value or error.", + "properties": { + "value": { + "description": "The return value of the action." + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "additionalProperties": false + } + } + } + }, + "required": [ + "version", + "actionResponse", + "actionId" + ], + "additionalProperties": false } ] } diff --git a/packages/genui/test/facade/prompt_builder_test.golden/create_and_update_with_dataModel_false.txt b/packages/genui/test/facade/prompt_builder_test.golden/create_and_update_with_dataModel_false.txt index dd3adc395..e904bc40a 100644 --- a/packages/genui/test/facade/prompt_builder_test.golden/create_and_update_with_dataModel_false.txt +++ b/packages/genui/test/facade/prompt_builder_test.golden/create_and_update_with_dataModel_false.txt @@ -43,10 +43,10 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 1. Create a surface: ```json { - "version": "v0.9", + "version": "v1.0", "createSurface": { "surfaceId": "main", - "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", "sendDataModel": true } } @@ -55,7 +55,7 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 2. Update components: ```json { - "version": "v0.9", + "version": "v1.0", "updateComponents": { "surfaceId": "main", "components": [ @@ -137,7 +137,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "createSurface": { "type": "object", @@ -151,14 +151,25 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "type": "string", "description": "A string that uniquely identifies this catalog. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. 'mycompany.com:somecatalog'." }, - "theme": { + "surfaceProperties": { "type": "object", - "description": "Theme parameters for the surface.", + "description": "Initial surface properties (e.g., {'agentDisplayName': 'My Agent'}). These must validate against the 'surfaceProperties' schema defined in the catalog.", "additionalProperties": true }, "sendDataModel": { "type": "boolean", "description": "Whether to send the data model to every client request." + }, + "components": { + "type": "array", + "description": "Optional initial list of UI components for the surface, allowing an entire UI to be created in a single message.", + "items": {}, + "minItems": 1 + }, + "dataModel": { + "type": "object", + "description": "The initial root data model object for the surface.", + "additionalProperties": true } }, "required": [ @@ -178,7 +189,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateComponents": { "type": "object", @@ -284,7 +295,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateDataModel": { "type": "object", @@ -298,7 +309,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "default": "/" }, "value": { - "description": "The new value to write to the data model. If null/omitted, the key is removed." + "description": "The new value to write to the data model. An explicit null value deletes the key at the path." } }, "required": [ @@ -317,7 +328,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "deleteSurface": { "type": "object", @@ -337,6 +348,91 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "deleteSurface" ], "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "functionCallId": { + "type": "string", + "description": "Unique ID for the instance of this function call. It is copied verbatim into the functionResponse or error." + }, + "wantResponse": { + "type": "boolean", + "description": "If true, the client returns a functionResponse with the result of the call." + }, + "callFunction": { + "type": "object", + "description": "A function invoked from the server. The function must be registered in the catalog with a 'callableFrom' of 'remoteOnly' or 'clientOrRemote'.", + "properties": { + "call": { + "type": "string", + "description": "The name of the function to call." + }, + "args": { + "type": "object", + "description": "Arguments passed to the function.", + "additionalProperties": true + } + }, + "required": [ + "call" + ] + } + }, + "required": [ + "version", + "callFunction", + "functionCallId" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "actionId": { + "type": "string", + "description": "The ID of the action call this response belongs to." + }, + "actionResponse": { + "type": "object", + "description": "A response to a client-initiated action that requested a response (wantResponse: true). Contains exactly one of value or error.", + "properties": { + "value": { + "description": "The return value of the action." + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "additionalProperties": false + } + } + } + }, + "required": [ + "version", + "actionResponse", + "actionId" + ], + "additionalProperties": false } ] } diff --git a/packages/genui/test/facade/prompt_builder_test.golden/create_and_update_with_dataModel_true.txt b/packages/genui/test/facade/prompt_builder_test.golden/create_and_update_with_dataModel_true.txt index c23b0e23b..dd3f838ed 100644 --- a/packages/genui/test/facade/prompt_builder_test.golden/create_and_update_with_dataModel_true.txt +++ b/packages/genui/test/facade/prompt_builder_test.golden/create_and_update_with_dataModel_true.txt @@ -43,10 +43,10 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 1. Create a surface: ```json { - "version": "v0.9", + "version": "v1.0", "createSurface": { "surfaceId": "main", - "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", "sendDataModel": true } } @@ -55,7 +55,7 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 2. Update components: ```json { - "version": "v0.9", + "version": "v1.0", "updateComponents": { "surfaceId": "main", "components": [ @@ -139,7 +139,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "createSurface": { "type": "object", @@ -153,14 +153,25 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "type": "string", "description": "A string that uniquely identifies this catalog. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. 'mycompany.com:somecatalog'." }, - "theme": { + "surfaceProperties": { "type": "object", - "description": "Theme parameters for the surface.", + "description": "Initial surface properties (e.g., {'agentDisplayName': 'My Agent'}). These must validate against the 'surfaceProperties' schema defined in the catalog.", "additionalProperties": true }, "sendDataModel": { "type": "boolean", "description": "Whether to send the data model to every client request." + }, + "components": { + "type": "array", + "description": "Optional initial list of UI components for the surface, allowing an entire UI to be created in a single message.", + "items": {}, + "minItems": 1 + }, + "dataModel": { + "type": "object", + "description": "The initial root data model object for the surface.", + "additionalProperties": true } }, "required": [ @@ -180,7 +191,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateComponents": { "type": "object", @@ -286,7 +297,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateDataModel": { "type": "object", @@ -300,7 +311,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "default": "/" }, "value": { - "description": "The new value to write to the data model. If null/omitted, the key is removed." + "description": "The new value to write to the data model. An explicit null value deletes the key at the path." } }, "required": [ @@ -319,7 +330,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "deleteSurface": { "type": "object", @@ -339,6 +350,91 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "deleteSurface" ], "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "functionCallId": { + "type": "string", + "description": "Unique ID for the instance of this function call. It is copied verbatim into the functionResponse or error." + }, + "wantResponse": { + "type": "boolean", + "description": "If true, the client returns a functionResponse with the result of the call." + }, + "callFunction": { + "type": "object", + "description": "A function invoked from the server. The function must be registered in the catalog with a 'callableFrom' of 'remoteOnly' or 'clientOrRemote'.", + "properties": { + "call": { + "type": "string", + "description": "The name of the function to call." + }, + "args": { + "type": "object", + "description": "Arguments passed to the function.", + "additionalProperties": true + } + }, + "required": [ + "call" + ] + } + }, + "required": [ + "version", + "callFunction", + "functionCallId" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "actionId": { + "type": "string", + "description": "The ID of the action call this response belongs to." + }, + "actionResponse": { + "type": "object", + "description": "A response to a client-initiated action that requested a response (wantResponse: true). Contains exactly one of value or error.", + "properties": { + "value": { + "description": "The return value of the action." + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "additionalProperties": false + } + } + } + }, + "required": [ + "version", + "actionResponse", + "actionId" + ], + "additionalProperties": false } ] } diff --git a/packages/genui/test/facade/prompt_builder_test.golden/create_only_with_dataModel_false.txt b/packages/genui/test/facade/prompt_builder_test.golden/create_only_with_dataModel_false.txt index d35089698..5946371f0 100644 --- a/packages/genui/test/facade/prompt_builder_test.golden/create_only_with_dataModel_false.txt +++ b/packages/genui/test/facade/prompt_builder_test.golden/create_only_with_dataModel_false.txt @@ -43,10 +43,10 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 1. Create a surface: ```json { - "version": "v0.9", + "version": "v1.0", "createSurface": { "surfaceId": "main", - "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", "sendDataModel": true } } @@ -55,7 +55,7 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 2. Update components: ```json { - "version": "v0.9", + "version": "v1.0", "updateComponents": { "surfaceId": "main", "components": [ @@ -136,7 +136,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "createSurface": { "type": "object", @@ -150,14 +150,25 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "type": "string", "description": "A string that uniquely identifies this catalog. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. 'mycompany.com:somecatalog'." }, - "theme": { + "surfaceProperties": { "type": "object", - "description": "Theme parameters for the surface.", + "description": "Initial surface properties (e.g., {'agentDisplayName': 'My Agent'}). These must validate against the 'surfaceProperties' schema defined in the catalog.", "additionalProperties": true }, "sendDataModel": { "type": "boolean", "description": "Whether to send the data model to every client request." + }, + "components": { + "type": "array", + "description": "Optional initial list of UI components for the surface, allowing an entire UI to be created in a single message.", + "items": {}, + "minItems": 1 + }, + "dataModel": { + "type": "object", + "description": "The initial root data model object for the surface.", + "additionalProperties": true } }, "required": [ @@ -177,7 +188,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateComponents": { "type": "object", @@ -283,7 +294,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateDataModel": { "type": "object", @@ -297,7 +308,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "default": "/" }, "value": { - "description": "The new value to write to the data model. If null/omitted, the key is removed." + "description": "The new value to write to the data model. An explicit null value deletes the key at the path." } }, "required": [ @@ -316,7 +327,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "deleteSurface": { "type": "object", @@ -336,6 +347,91 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "deleteSurface" ], "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "functionCallId": { + "type": "string", + "description": "Unique ID for the instance of this function call. It is copied verbatim into the functionResponse or error." + }, + "wantResponse": { + "type": "boolean", + "description": "If true, the client returns a functionResponse with the result of the call." + }, + "callFunction": { + "type": "object", + "description": "A function invoked from the server. The function must be registered in the catalog with a 'callableFrom' of 'remoteOnly' or 'clientOrRemote'.", + "properties": { + "call": { + "type": "string", + "description": "The name of the function to call." + }, + "args": { + "type": "object", + "description": "Arguments passed to the function.", + "additionalProperties": true + } + }, + "required": [ + "call" + ] + } + }, + "required": [ + "version", + "callFunction", + "functionCallId" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "actionId": { + "type": "string", + "description": "The ID of the action call this response belongs to." + }, + "actionResponse": { + "type": "object", + "description": "A response to a client-initiated action that requested a response (wantResponse: true). Contains exactly one of value or error.", + "properties": { + "value": { + "description": "The return value of the action." + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "additionalProperties": false + } + } + } + }, + "required": [ + "version", + "actionResponse", + "actionId" + ], + "additionalProperties": false } ] } diff --git a/packages/genui/test/facade/prompt_builder_test.golden/create_only_with_dataModel_true.txt b/packages/genui/test/facade/prompt_builder_test.golden/create_only_with_dataModel_true.txt index 0ae7dfeed..7375cbebd 100644 --- a/packages/genui/test/facade/prompt_builder_test.golden/create_only_with_dataModel_true.txt +++ b/packages/genui/test/facade/prompt_builder_test.golden/create_only_with_dataModel_true.txt @@ -43,10 +43,10 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 1. Create a surface: ```json { - "version": "v0.9", + "version": "v1.0", "createSurface": { "surfaceId": "main", - "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", "sendDataModel": true } } @@ -55,7 +55,7 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 2. Update components: ```json { - "version": "v0.9", + "version": "v1.0", "updateComponents": { "surfaceId": "main", "components": [ @@ -138,7 +138,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "createSurface": { "type": "object", @@ -152,14 +152,25 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "type": "string", "description": "A string that uniquely identifies this catalog. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. 'mycompany.com:somecatalog'." }, - "theme": { + "surfaceProperties": { "type": "object", - "description": "Theme parameters for the surface.", + "description": "Initial surface properties (e.g., {'agentDisplayName': 'My Agent'}). These must validate against the 'surfaceProperties' schema defined in the catalog.", "additionalProperties": true }, "sendDataModel": { "type": "boolean", "description": "Whether to send the data model to every client request." + }, + "components": { + "type": "array", + "description": "Optional initial list of UI components for the surface, allowing an entire UI to be created in a single message.", + "items": {}, + "minItems": 1 + }, + "dataModel": { + "type": "object", + "description": "The initial root data model object for the surface.", + "additionalProperties": true } }, "required": [ @@ -179,7 +190,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateComponents": { "type": "object", @@ -285,7 +296,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateDataModel": { "type": "object", @@ -299,7 +310,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "default": "/" }, "value": { - "description": "The new value to write to the data model. If null/omitted, the key is removed." + "description": "The new value to write to the data model. An explicit null value deletes the key at the path." } }, "required": [ @@ -318,7 +329,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "deleteSurface": { "type": "object", @@ -338,6 +349,91 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "deleteSurface" ], "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "functionCallId": { + "type": "string", + "description": "Unique ID for the instance of this function call. It is copied verbatim into the functionResponse or error." + }, + "wantResponse": { + "type": "boolean", + "description": "If true, the client returns a functionResponse with the result of the call." + }, + "callFunction": { + "type": "object", + "description": "A function invoked from the server. The function must be registered in the catalog with a 'callableFrom' of 'remoteOnly' or 'clientOrRemote'.", + "properties": { + "call": { + "type": "string", + "description": "The name of the function to call." + }, + "args": { + "type": "object", + "description": "Arguments passed to the function.", + "additionalProperties": true + } + }, + "required": [ + "call" + ] + } + }, + "required": [ + "version", + "callFunction", + "functionCallId" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "actionId": { + "type": "string", + "description": "The ID of the action call this response belongs to." + }, + "actionResponse": { + "type": "object", + "description": "A response to a client-initiated action that requested a response (wantResponse: true). Contains exactly one of value or error.", + "properties": { + "value": { + "description": "The return value of the action." + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "additionalProperties": false + } + } + } + }, + "required": [ + "version", + "actionResponse", + "actionId" + ], + "additionalProperties": false } ] } diff --git a/packages/genui/test/facade/prompt_builder_test.golden/update_only_with_dataModel_false.txt b/packages/genui/test/facade/prompt_builder_test.golden/update_only_with_dataModel_false.txt index 6f63a173a..387c9a282 100644 --- a/packages/genui/test/facade/prompt_builder_test.golden/update_only_with_dataModel_false.txt +++ b/packages/genui/test/facade/prompt_builder_test.golden/update_only_with_dataModel_false.txt @@ -43,10 +43,10 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 1. Create a surface: ```json { - "version": "v0.9", + "version": "v1.0", "createSurface": { "surfaceId": "main", - "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", "sendDataModel": true } } @@ -55,7 +55,7 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 2. Update components: ```json { - "version": "v0.9", + "version": "v1.0", "updateComponents": { "surfaceId": "main", "components": [ @@ -129,7 +129,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "createSurface": { "type": "object", @@ -143,14 +143,25 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "type": "string", "description": "A string that uniquely identifies this catalog. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. 'mycompany.com:somecatalog'." }, - "theme": { + "surfaceProperties": { "type": "object", - "description": "Theme parameters for the surface.", + "description": "Initial surface properties (e.g., {'agentDisplayName': 'My Agent'}). These must validate against the 'surfaceProperties' schema defined in the catalog.", "additionalProperties": true }, "sendDataModel": { "type": "boolean", "description": "Whether to send the data model to every client request." + }, + "components": { + "type": "array", + "description": "Optional initial list of UI components for the surface, allowing an entire UI to be created in a single message.", + "items": {}, + "minItems": 1 + }, + "dataModel": { + "type": "object", + "description": "The initial root data model object for the surface.", + "additionalProperties": true } }, "required": [ @@ -170,7 +181,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateComponents": { "type": "object", @@ -276,7 +287,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateDataModel": { "type": "object", @@ -290,7 +301,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "default": "/" }, "value": { - "description": "The new value to write to the data model. If null/omitted, the key is removed." + "description": "The new value to write to the data model. An explicit null value deletes the key at the path." } }, "required": [ @@ -309,7 +320,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "deleteSurface": { "type": "object", @@ -329,6 +340,91 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "deleteSurface" ], "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "functionCallId": { + "type": "string", + "description": "Unique ID for the instance of this function call. It is copied verbatim into the functionResponse or error." + }, + "wantResponse": { + "type": "boolean", + "description": "If true, the client returns a functionResponse with the result of the call." + }, + "callFunction": { + "type": "object", + "description": "A function invoked from the server. The function must be registered in the catalog with a 'callableFrom' of 'remoteOnly' or 'clientOrRemote'.", + "properties": { + "call": { + "type": "string", + "description": "The name of the function to call." + }, + "args": { + "type": "object", + "description": "Arguments passed to the function.", + "additionalProperties": true + } + }, + "required": [ + "call" + ] + } + }, + "required": [ + "version", + "callFunction", + "functionCallId" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "actionId": { + "type": "string", + "description": "The ID of the action call this response belongs to." + }, + "actionResponse": { + "type": "object", + "description": "A response to a client-initiated action that requested a response (wantResponse: true). Contains exactly one of value or error.", + "properties": { + "value": { + "description": "The return value of the action." + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "additionalProperties": false + } + } + } + }, + "required": [ + "version", + "actionResponse", + "actionId" + ], + "additionalProperties": false } ] } diff --git a/packages/genui/test/facade/prompt_builder_test.golden/update_only_with_dataModel_true.txt b/packages/genui/test/facade/prompt_builder_test.golden/update_only_with_dataModel_true.txt index d85d3f55f..8b9815977 100644 --- a/packages/genui/test/facade/prompt_builder_test.golden/update_only_with_dataModel_true.txt +++ b/packages/genui/test/facade/prompt_builder_test.golden/update_only_with_dataModel_true.txt @@ -43,10 +43,10 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 1. Create a surface: ```json { - "version": "v0.9", + "version": "v1.0", "createSurface": { "surfaceId": "main", - "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", "sendDataModel": true } } @@ -55,7 +55,7 @@ IMPORTANT: You do not have the ability to use function calls for UI generation. 2. Update components: ```json { - "version": "v0.9", + "version": "v1.0", "updateComponents": { "surfaceId": "main", "components": [ @@ -131,7 +131,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "createSurface": { "type": "object", @@ -145,14 +145,25 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "type": "string", "description": "A string that uniquely identifies this catalog. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. 'mycompany.com:somecatalog'." }, - "theme": { + "surfaceProperties": { "type": "object", - "description": "Theme parameters for the surface.", + "description": "Initial surface properties (e.g., {'agentDisplayName': 'My Agent'}). These must validate against the 'surfaceProperties' schema defined in the catalog.", "additionalProperties": true }, "sendDataModel": { "type": "boolean", "description": "Whether to send the data model to every client request." + }, + "components": { + "type": "array", + "description": "Optional initial list of UI components for the surface, allowing an entire UI to be created in a single message.", + "items": {}, + "minItems": 1 + }, + "dataModel": { + "type": "object", + "description": "The initial root data model object for the surface.", + "additionalProperties": true } }, "required": [ @@ -172,7 +183,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateComponents": { "type": "object", @@ -278,7 +289,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "updateDataModel": { "type": "object", @@ -292,7 +303,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "default": "/" }, "value": { - "description": "The new value to write to the data model. If null/omitted, the key is removed." + "description": "The new value to write to the data model. An explicit null value deletes the key at the path." } }, "required": [ @@ -311,7 +322,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "properties": { "version": { "type": "string", - "const": "v0.9" + "const": "v1.0" }, "deleteSurface": { "type": "object", @@ -331,6 +342,91 @@ When constructing UI, you must output a VALID A2UI JSON object representing one "deleteSurface" ], "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "functionCallId": { + "type": "string", + "description": "Unique ID for the instance of this function call. It is copied verbatim into the functionResponse or error." + }, + "wantResponse": { + "type": "boolean", + "description": "If true, the client returns a functionResponse with the result of the call." + }, + "callFunction": { + "type": "object", + "description": "A function invoked from the server. The function must be registered in the catalog with a 'callableFrom' of 'remoteOnly' or 'clientOrRemote'.", + "properties": { + "call": { + "type": "string", + "description": "The name of the function to call." + }, + "args": { + "type": "object", + "description": "Arguments passed to the function.", + "additionalProperties": true + } + }, + "required": [ + "call" + ] + } + }, + "required": [ + "version", + "callFunction", + "functionCallId" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "const": "v1.0" + }, + "actionId": { + "type": "string", + "description": "The ID of the action call this response belongs to." + }, + "actionResponse": { + "type": "object", + "description": "A response to a client-initiated action that requested a response (wantResponse: true). Contains exactly one of value or error.", + "properties": { + "value": { + "description": "The return value of the action." + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "additionalProperties": false + } + } + } + }, + "required": [ + "version", + "actionResponse", + "actionId" + ], + "additionalProperties": false } ] } diff --git a/packages/genui/test/test_infra/message_builders.dart b/packages/genui/test/test_infra/message_builders.dart index f864edaf5..9a6cb7b82 100644 --- a/packages/genui/test/test_infra/message_builders.dart +++ b/packages/genui/test/test_infra/message_builders.dart @@ -19,21 +19,25 @@ JsonMap component({ }) => {'id': id, 'component': type, ...properties}; core.CreateSurfaceMessage createSurface({ - String version = 'v0.9', + String version = 'v1.0', required String surfaceId, required String catalogId, - JsonMap? theme, + JsonMap? surfaceProperties, bool sendDataModel = false, + List? components, + JsonMap? dataModel, }) => core.CreateSurfaceMessage( version: version, surfaceId: surfaceId, catalogId: catalogId, - theme: theme, + surfaceProperties: surfaceProperties, sendDataModel: sendDataModel, + components: components, + dataModel: dataModel, ); core.UpdateComponentsMessage updateComponents({ - String version = 'v0.9', + String version = 'v1.0', required String surfaceId, required List components, }) => core.UpdateComponentsMessage( @@ -43,7 +47,7 @@ core.UpdateComponentsMessage updateComponents({ ); core.UpdateDataModelMessage updateDataModel({ - String version = 'v0.9', + String version = 'v1.0', required String surfaceId, DataPath path = DataPath.root, Object? value, @@ -55,6 +59,6 @@ core.UpdateDataModelMessage updateDataModel({ ); core.DeleteSurfaceMessage deleteSurface({ - String version = 'v0.9', + String version = 'v1.0', required String surfaceId, }) => core.DeleteSurfaceMessage(version: version, surfaceId: surfaceId); diff --git a/packages/genui/test/transport/a2ui_parser_transformer_test.dart b/packages/genui/test/transport/a2ui_parser_transformer_test.dart index f419dcfda..9cd5d7104 100644 --- a/packages/genui/test/transport/a2ui_parser_transformer_test.dart +++ b/packages/genui/test/transport/a2ui_parser_transformer_test.dart @@ -47,7 +47,7 @@ void main() { controller.add('Here is a message:\n'); controller.add('```json\n'); controller.add( - '{"version": "v0.9", "createSurface": {"surfaceId": "foo", ' + '{"version": "v1.0", "createSurface": {"surfaceId": "foo", ' '"catalogId": "cat"}}\n', ); controller.add('```\n'); @@ -89,7 +89,7 @@ void main() { final StreamQueue queue = StreamQueue(stream); controller.add('Start '); - controller.add('{ "version": "v0.9", "deleteSurface": '); + controller.add('{ "version": "v1.0", "deleteSurface": '); controller.add('{ "surfaceId": '); // Needs nesting for wrapper controller.add('"bar" } }'); controller.add(' End'); @@ -115,7 +115,7 @@ void main() { final StreamQueue queue = StreamQueue(stream); controller.add('Start '); - controller.add('{ "version": "v0.9", "deleteSurface": '); + controller.add('{ "version": "v1.0", "deleteSurface": '); controller.add('{ "surfaceId": "[{]bar[}]" } }'); controller.add(' End'); @@ -160,7 +160,7 @@ void main() { final StreamQueue queue = StreamQueue(stream); // Malformed core.CreateSurfaceMessage (missing required fields) - controller.add('{"version": "v0.9", "createSurface": {}}'); + controller.add('{"version": "v1.0", "createSurface": {}}'); // Should emit error expect(queue.next, throwsA(isA())); @@ -243,7 +243,7 @@ void main() { final StreamQueue queue = StreamQueue(stream); controller.add( - '{"version": "v0.9", "createSurface": ' + '{"version": "v1.0", "createSurface": ' '{"surfaceId": "foo", "catalogId": "genui"}' '}', ); @@ -251,7 +251,7 @@ void main() { controller.add('\n'); controller.add( - '{"version": "v0.9", "createSurface": ' + '{"version": "v1.0", "createSurface": ' '{"surfaceId": "bar", "catalogId": "genui"}' '}', ); diff --git a/packages/genui/test/transport/a2ui_transport_adapter_test.dart b/packages/genui/test/transport/a2ui_transport_adapter_test.dart index 3c6b92f08..2c6cfb8eb 100644 --- a/packages/genui/test/transport/a2ui_transport_adapter_test.dart +++ b/packages/genui/test/transport/a2ui_transport_adapter_test.dart @@ -32,7 +32,7 @@ void main() { test('addChunk with message updates state', () async { // Using JSON block final json = '''```json -{"version": "v0.9", "createSurface": {"surfaceId": "test_chunk", "catalogId": "test-cat"}} +{"version": "v1.0", "createSurface": {"surfaceId": "test_chunk", "catalogId": "test-cat"}} ```'''; final Future stateFuture = expectLater( @@ -86,7 +86,7 @@ void main() { ); adapter.addChunk('''```json -{"version": "v0.9", "updateComponents": {"surfaceId": "test", "components": [{"id": "root", "component": "Text", "properties": {"text": "Hello"}}]}} +{"version": "v1.0", "updateComponents": {"surfaceId": "test", "components": [{"id": "root", "component": "Text", "properties": {"text": "Hello"}}]}} ```'''); await expectation; From 4775ca8df8c7594961d9e2e2ebec9c8efec7e0b1 Mon Sep 17 00:00:00 2001 From: Seth Date: Wed, 8 Jul 2026 00:49:52 -0600 Subject: [PATCH 3/4] feat(genui_a2a): migrate to A2UI protocol v1.0 - Bump A2A extension URI to a2ui/v1.0 - Define the standardized application/a2ui+json MIME type constant - Send v1.0 client event envelopes with actionId/wantResponse - Route incoming callFunction and actionResponse data parts Co-Authored-By: Claude Fable 5 --- .../genui_a2a/lib/src/a2ui_agent_connector.dart | 13 ++++++++++--- .../genui_a2a/test/a2ui_agent_connector_test.dart | 6 +++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/genui_a2a/lib/src/a2ui_agent_connector.dart b/packages/genui_a2a/lib/src/a2ui_agent_connector.dart index 72f38f442..04b775c5a 100644 --- a/packages/genui_a2a/lib/src/a2ui_agent_connector.dart +++ b/packages/genui_a2a/lib/src/a2ui_agent_connector.dart @@ -17,9 +17,12 @@ import 'logging_utils.dart'; export 'a2a/a2a.dart' show A2AClient, AgentCard; final Uri a2uiExtensionUri = Uri.parse( - 'https://a2ui.org/a2a-extension/a2ui/v0.9', + 'https://a2ui.org/a2a-extension/a2ui/v1.0', ); +/// The IANA-conformant MIME type for A2UI payloads. +const String a2uiMimeType = 'application/a2ui+json'; + final Logger _log = genui.genUiLogger; /// Connects to an A2UI Agent endpoint and streams the A2UI protocol lines. @@ -287,13 +290,15 @@ class A2uiAgentConnector { } final Map clientEvent = { - 'version': 'v0.9', + 'version': core.a2uiProtocolVersion, 'action': { 'name': event['action'], 'sourceComponentId': event['sourceComponentId'], 'timestamp': DateTime.now().toIso8601String(), 'context': event['context'], if (event.containsKey('surfaceId')) 'surfaceId': event['surfaceId'], + if (event['wantResponse'] == true) 'wantResponse': true, + if (event.containsKey('actionId')) 'actionId': event['actionId'], }, }; @@ -336,7 +341,9 @@ class A2uiAgentConnector { if (data.containsKey('updateComponents') || data.containsKey('updateDataModel') || data.containsKey('createSurface') || - data.containsKey('deleteSurface')) { + data.containsKey('deleteSurface') || + data.containsKey('callFunction') || + data.containsKey('actionResponse')) { if (!_controller.isClosed) { _log.finest('Adding message to stream: $prettyJson'); _controller.add(core.A2uiMessage.fromJson(data)); diff --git a/packages/genui_a2a/test/a2ui_agent_connector_test.dart b/packages/genui_a2a/test/a2ui_agent_connector_test.dart index c739b8c8f..efd641a5b 100644 --- a/packages/genui_a2a/test/a2ui_agent_connector_test.dart +++ b/packages/genui_a2a/test/a2ui_agent_connector_test.dart @@ -63,7 +63,7 @@ void main() { final a2a.Message sentMessage = fakeClient.lastMessageStreamParams!; expect(sentMessage.metadata, isNotNull); expect(sentMessage.metadata!['a2uiClientCapabilities'], { - 'v0.9': { + 'v1.0': { 'supportedCatalogIds': ['cat1', 'cat2'], }, }); @@ -82,7 +82,7 @@ void main() { parts: [ a2a.Part.data( data: { - 'version': 'v0.9', + 'version': 'v1.0', 'updateComponents': { 'surfaceId': 's1', 'components': [ @@ -168,7 +168,7 @@ void main() { expect(sentMessage.referenceTaskIds, ['task1']); final dataPart = sentMessage.parts.first as a2a.DataPart; final Map eventData = dataPart.data; - expect(eventData['version'], 'v0.9'); + expect(eventData['version'], 'v1.0'); final action = eventData['action'] as Map; expect(action['name'], 'testAction'); expect(action['sourceComponentId'], 'c1'); From 3bc7353bf13a22d9a0e7568980a5d7116eccd545 Mon Sep 17 00:00:00 2001 From: Seth Date: Wed, 8 Jul 2026 00:54:31 -0600 Subject: [PATCH 4/4] chore: update samples, dev_tools, and docs for A2UI v1.0 - Bump 46 catalog_gallery samples, simple_chat integration samples, and composer kProtocolVersion to v1.0 envelopes and the v1.0 basic catalog id - Add migration guide docs/usage/migration/migration_a2ui_v0.9_to_v1.0.md - Add CHANGELOG entries for a2ui_core, genui, and genui_a2a - Update README and contributing docs version references Co-Authored-By: Claude Fable 5 --- README.md | 2 +- .../integration_test/app_test.dart | 4 +- .../catalog_gallery/lib/sample_source.dart | 4 +- .../samples/animalKingdomExplorer.sample | 4 +- .../samples/audioPlayer.sample | 4 +- .../samples/calendarEventCreator.sample | 4 +- .../catalog_gallery/samples/chatRoom.sample | 4 +- .../samples/checkoutPage.sample | 6 +- .../samples/cinemaSeatSelection.sample | 4 +- .../samples/clientSideValidation.sample | 4 +- .../samples/contactCard.sample | 4 +- .../samples/courseSyllabus.sample | 4 +- .../catalog_gallery/samples/dashboard.sample | 4 +- .../samples/dogBreedGenerator.sample | 6 +- .../samples/eCommerceProductPage.sample | 4 +- .../samples/fileBrowser.sample | 4 +- .../samples/fitnessTracker.sample | 6 +- .../samples/flashcardApp.sample | 4 +- .../samples/flightBooker.sample | 6 +- .../samples/hello_world.sample | 4 +- .../samples/hotelSearchResults.sample | 4 +- .../samples/interactiveDashboard.sample | 4 +- .../samples/jobApplication.sample | 6 +- .../samples/kanbanBoard.sample | 4 +- .../catalog_gallery/samples/loginForm.sample | 4 +- .../samples/musicPlayer.sample | 4 +- .../samples/nestedDataBinding.sample | 6 +- .../samples/nestedLayoutRecursive.sample | 4 +- .../samples/newsAggregator.sample | 4 +- .../samples/notificationCenter.sample | 4 +- .../samples/openUrlAction.sample | 4 +- .../samples/photoEditor.sample | 6 +- .../samples/podcastEpisode.sample | 4 +- .../samples/productGallery.sample | 6 +- .../samples/profileEditor.sample | 4 +- .../catalog_gallery/samples/recipeCard.sample | 4 +- .../samples/restaurantMenu.sample | 4 +- .../samples/settingsPage.sample | 6 +- .../samples/simpleCalculator.sample | 4 +- .../catalog_gallery/samples/smartHome.sample | 4 +- .../samples/socialMediaPost.sample | 4 +- .../samples/standardFunctions.sample | 6 +- .../samples/stockWatchlist.sample | 4 +- .../catalog_gallery/samples/surveyForm.sample | 6 +- .../samples/travelItinerary.sample | 4 +- .../catalog_gallery/samples/triviaQuiz.sample | 6 +- .../samples/videoCallInterface.sample | 4 +- .../samples/videoPlayer.sample | 4 +- .../samples/weatherForecast.sample | 4 +- .../catalog_gallery/test/layout_test.dart | 2 +- .../test/sample_parser_test.dart | 8 +- dev_tools/composer/lib/surface_utils.dart | 2 +- docs/contributing/design.md | 2 +- .../migration/migration_a2ui_v0.9_to_v1.0.md | 108 ++++++++++++++++++ .../samples/sample_1_hello.json | 6 +- .../samples/sample_2_button.json | 6 +- .../samples/sample_4_form.json | 6 +- .../samples/sample_5_mixed.json | 6 +- packages/a2ui_core/CHANGELOG.md | 22 ++++ packages/a2ui_core/lib/src/core/messages.dart | 3 +- packages/genui/CHANGELOG.md | 21 ++++ .../genui/lib/src/model/a2ui_schemas.dart | 5 +- packages/genui_a2a/CHANGELOG.md | 9 ++ 63 files changed, 288 insertions(+), 136 deletions(-) create mode 100644 docs/usage/migration/migration_a2ui_v0.9_to_v1.0.md diff --git a/README.md b/README.md index 4885c3990..61915319e 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ graph TD The Flutter Gen UI SDK uses the [A2UI protocol](https://a2ui.org) to represent UI content internally. The [genui_a2a](packages/genui_a2a/) package allows it to act as a renderer for UIs generated by an A2UI backend agent, similar to the [other A2UI renderers](https://github.com/google/A2UI/tree/main/renderers) which are maintained within the A2UI repository. -The Flutter Gen UI SDK currently supports A2UI v0.9. +The Flutter Gen UI SDK currently supports A2UI v1.0. ## Getting started diff --git a/dev_tools/catalog_gallery/integration_test/app_test.dart b/dev_tools/catalog_gallery/integration_test/app_test.dart index 790696213..0be2c442c 100644 --- a/dev_tools/catalog_gallery/integration_test/app_test.dart +++ b/dev_tools/catalog_gallery/integration_test/app_test.dart @@ -58,9 +58,7 @@ void main() { testWidgets('catalog_gallery smoke test - verify initial state', ( tester, ) async { - await tester.pumpWidget( - CatalogGalleryApp(sampleSource: sampleSource), - ); + await tester.pumpWidget(CatalogGalleryApp(sampleSource: sampleSource)); await tester.pumpAndSettle(); expect(find.text('Catalog Gallery'), findsOneWidget); diff --git a/dev_tools/catalog_gallery/lib/sample_source.dart b/dev_tools/catalog_gallery/lib/sample_source.dart index a1aaba8f0..0861dc706 100644 --- a/dev_tools/catalog_gallery/lib/sample_source.dart +++ b/dev_tools/catalog_gallery/lib/sample_source.dart @@ -86,9 +86,7 @@ class DirectorySampleSource implements SampleSource { final List refs = files .map( (file) => SampleRef( - name: directory.fileSystem.path.basenameWithoutExtension( - file.path, - ), + name: directory.fileSystem.path.basenameWithoutExtension(file.path), load: file.readAsString, ), ) diff --git a/dev_tools/catalog_gallery/samples/animalKingdomExplorer.sample b/dev_tools/catalog_gallery/samples/animalKingdomExplorer.sample index f5ac33d72..e7059bb0b 100644 --- a/dev_tools/catalog_gallery/samples/animalKingdomExplorer.sample +++ b/dev_tools/catalog_gallery/samples/animalKingdomExplorer.sample @@ -38,5 +38,5 @@ prompt: | IMPORTANT: Do not skip any of the classes, orders, or species above. Include every item that is mentioned. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"List","children":["mainHeading","mainTabs"]},{"id":"mainHeading","component":"Text","text":"Simple Animal Explorer","variant":"h1"},{"id":"mainTabs","component":"Tabs","tabs":[{"title":"Mammals","child":"mammalsTabContent"},{"title":"Birds","child":"birdsTabContent"},{"title":"Reptiles","child":"reptilesTabContent"}]},{"id":"mammalsTabContent","component":"Column","children":["mammalsSearchField","mammaliaCard"]},{"id":"mammalsSearchField","component":"TextField","label":"Search...","value":""},{"id":"mammaliaCard","component":"Card","child":"mammaliaColumn"},{"id":"mammaliaColumn","component":"Column","children":["mammaliaText","carnivoraCard","artiodactylaCard"]},{"id":"mammaliaText","component":"Text","text":"Class: Mammalia"},{"id":"carnivoraCard","component":"Card","child":"carnivoraColumn"},{"id":"carnivoraColumn","component":"Column","children":["carnivoraText","lionCard","tigerCard","wolfCard"]},{"id":"carnivoraText","component":"Text","text":"Order: Carnivora"},{"id":"lionCard","component":"Card","child":"lionRow"},{"id":"lionRow","component":"Row","children":["lionImage","lionText"]},{"id":"lionImage","component":"Image","url":"https://example.com/lion.jpg"},{"id":"lionText","component":"Text","text":"Lion"},{"id":"tigerCard","component":"Card","child":"tigerRow"},{"id":"tigerRow","component":"Row","children":["tigerImage","tigerText"]},{"id":"tigerImage","component":"Image","url":"https://example.com/tiger.jpg"},{"id":"tigerText","component":"Text","text":"Tiger"},{"id":"wolfCard","component":"Card","child":"wolfRow"},{"id":"wolfRow","component":"Row","children":["wolfImage","wolfText"]},{"id":"wolfImage","component":"Image","url":"https://example.com/wolf.jpg"},{"id":"wolfText","component":"Text","text":"Wolf"},{"id":"artiodactylaCard","component":"Card","child":"artiodactylaColumn"},{"id":"artiodactylaColumn","component":"Column","children":["artiodactylaText","giraffeCard","hippopotamusCard"]},{"id":"artiodactylaText","component":"Text","text":"Order: Artiodactyla"},{"id":"giraffeCard","component":"Card","child":"giraffeRow"},{"id":"giraffeRow","component":"Row","children":["giraffeImage","giraffeText"]},{"id":"giraffeImage","component":"Image","url":"https://example.com/giraffe.jpg"},{"id":"giraffeText","component":"Text","text":"Giraffe"},{"id":"hippopotamusCard","component":"Card","child":"hippopotamusRow"},{"id":"hippopotamusRow","component":"Row","children":["hippopotamusImage","hippopotamusText"]},{"id":"hippopotamusImage","component":"Image","url":"https://example.com/hippopotamus.jpg"},{"id":"hippopotamusText","component":"Text","text":"Hippopotamus"},{"id":"birdsTabContent","component":"Column","children":["birdsSearchField","avesCard"]},{"id":"birdsSearchField","component":"TextField","label":"Search...","value":""},{"id":"avesCard","component":"Card","child":"avesColumn"},{"id":"avesColumn","component":"Column","children":["avesText","accipitriformesCard","struthioniformesCard","sphenisciformesCard"]},{"id":"avesText","component":"Text","text":"Class: Aves"},{"id":"accipitriformesCard","component":"Card","child":"accipitriformesColumn"},{"id":"accipitriformesColumn","component":"Column","children":["accipitriformesText","baldEagleCard"]},{"id":"accipitriformesText","component":"Text","text":"Order: Accipitriformes"},{"id":"baldEagleCard","component":"Card","child":"baldEagleRow"},{"id":"baldEagleRow","component":"Row","children":["baldEagleImage","baldEagleText"]},{"id":"baldEagleImage","component":"Image","url":"https://example.com/baldeagle.jpg"},{"id":"baldEagleText","component":"Text","text":"Bald Eagle"},{"id":"struthioniformesCard","component":"Card","child":"struthioniformesColumn"},{"id":"struthioniformesColumn","component":"Column","children":["struthioniformesText","ostrichCard"]},{"id":"struthioniformesText","component":"Text","text":"Order: Struthioniformes"},{"id":"ostrichCard","component":"Card","child":"ostrichRow"},{"id":"ostrichRow","component":"Row","children":["ostrichImage","ostrichText"]},{"id":"ostrichImage","component":"Image","url":"https://example.com/ostrich.jpg"},{"id":"ostrichText","component":"Text","text":"Ostrich"},{"id":"sphenisciformesCard","component":"Card","child":"sphenisciformesColumn"},{"id":"sphenisciformesColumn","component":"Column","children":["sphenisciformesText","penguinCard"]},{"id":"sphenisciformesText","component":"Text","text":"Order: Sphenisciformes"},{"id":"penguinCard","component":"Card","child":"penguinRow"},{"id":"penguinRow","component":"Row","children":["penguinImage","penguinText"]},{"id":"penguinImage","component":"Image","url":"https://example.com/penguin.jpg"},{"id":"penguinText","component":"Text","text":"Penguin"},{"id":"reptilesTabContent","component":"Column","children":["reptilesSearchField","reptiliaCard"]},{"id":"reptilesSearchField","component":"TextField","label":"Search...","value":""},{"id":"reptiliaCard","component":"Card","child":"reptiliaColumn"},{"id":"reptiliaColumn","component":"Column","children":["reptiliaText","crocodiliaCard","squamataCard"]},{"id":"reptiliaText","component":"Text","text":"Class: Reptilia"},{"id":"crocodiliaCard","component":"Card","child":"crocodiliaColumn"},{"id":"crocodiliaColumn","component":"Column","children":["crocodiliaText","nileCrocodileCard"]},{"id":"crocodiliaText","component":"Text","text":"Order: Crocodilia"},{"id":"nileCrocodileCard","component":"Card","child":"nileCrocodileRow"},{"id":"nileCrocodileRow","component":"Row","children":["nileCrocodileImage","nileCrocodileText"]},{"id":"nileCrocodileImage","component":"Image","url":"https://example.com/nilecrocodile.jpg"},{"id":"nileCrocodileText","component":"Text","text":"Nile Crocodile"},{"id":"squamataCard","component":"Card","child":"squamataColumn"},{"id":"squamataColumn","component":"Column","children":["squamataText","komodoDragonCard","ballPythonCard"]},{"id":"squamataText","component":"Text","text":"Order: Squamata"},{"id":"komodoDragonCard","component":"Card","child":"komodoDragonRow"},{"id":"komodoDragonRow","component":"Row","children":["komodoDragonImage","komodoDragonText"]},{"id":"komodoDragonImage","component":"Image","url":"https://example.com/komododragon.jpg"},{"id":"komodoDragonText","component":"Text","text":"Komodo Dragon"},{"id":"ballPythonCard","component":"Card","child":"ballPythonRow"},{"id":"ballPythonRow","component":"Row","children":["ballPythonImage","ballPythonText"]},{"id":"ballPythonImage","component":"Image","url":"https://example.com/ballpython.jpg"},{"id":"ballPythonText","component":"Text","text":"Ball Python"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"List","children":["mainHeading","mainTabs"]},{"id":"mainHeading","component":"Text","text":"Simple Animal Explorer","variant":"h1"},{"id":"mainTabs","component":"Tabs","tabs":[{"title":"Mammals","child":"mammalsTabContent"},{"title":"Birds","child":"birdsTabContent"},{"title":"Reptiles","child":"reptilesTabContent"}]},{"id":"mammalsTabContent","component":"Column","children":["mammalsSearchField","mammaliaCard"]},{"id":"mammalsSearchField","component":"TextField","label":"Search...","value":""},{"id":"mammaliaCard","component":"Card","child":"mammaliaColumn"},{"id":"mammaliaColumn","component":"Column","children":["mammaliaText","carnivoraCard","artiodactylaCard"]},{"id":"mammaliaText","component":"Text","text":"Class: Mammalia"},{"id":"carnivoraCard","component":"Card","child":"carnivoraColumn"},{"id":"carnivoraColumn","component":"Column","children":["carnivoraText","lionCard","tigerCard","wolfCard"]},{"id":"carnivoraText","component":"Text","text":"Order: Carnivora"},{"id":"lionCard","component":"Card","child":"lionRow"},{"id":"lionRow","component":"Row","children":["lionImage","lionText"]},{"id":"lionImage","component":"Image","url":"https://example.com/lion.jpg"},{"id":"lionText","component":"Text","text":"Lion"},{"id":"tigerCard","component":"Card","child":"tigerRow"},{"id":"tigerRow","component":"Row","children":["tigerImage","tigerText"]},{"id":"tigerImage","component":"Image","url":"https://example.com/tiger.jpg"},{"id":"tigerText","component":"Text","text":"Tiger"},{"id":"wolfCard","component":"Card","child":"wolfRow"},{"id":"wolfRow","component":"Row","children":["wolfImage","wolfText"]},{"id":"wolfImage","component":"Image","url":"https://example.com/wolf.jpg"},{"id":"wolfText","component":"Text","text":"Wolf"},{"id":"artiodactylaCard","component":"Card","child":"artiodactylaColumn"},{"id":"artiodactylaColumn","component":"Column","children":["artiodactylaText","giraffeCard","hippopotamusCard"]},{"id":"artiodactylaText","component":"Text","text":"Order: Artiodactyla"},{"id":"giraffeCard","component":"Card","child":"giraffeRow"},{"id":"giraffeRow","component":"Row","children":["giraffeImage","giraffeText"]},{"id":"giraffeImage","component":"Image","url":"https://example.com/giraffe.jpg"},{"id":"giraffeText","component":"Text","text":"Giraffe"},{"id":"hippopotamusCard","component":"Card","child":"hippopotamusRow"},{"id":"hippopotamusRow","component":"Row","children":["hippopotamusImage","hippopotamusText"]},{"id":"hippopotamusImage","component":"Image","url":"https://example.com/hippopotamus.jpg"},{"id":"hippopotamusText","component":"Text","text":"Hippopotamus"},{"id":"birdsTabContent","component":"Column","children":["birdsSearchField","avesCard"]},{"id":"birdsSearchField","component":"TextField","label":"Search...","value":""},{"id":"avesCard","component":"Card","child":"avesColumn"},{"id":"avesColumn","component":"Column","children":["avesText","accipitriformesCard","struthioniformesCard","sphenisciformesCard"]},{"id":"avesText","component":"Text","text":"Class: Aves"},{"id":"accipitriformesCard","component":"Card","child":"accipitriformesColumn"},{"id":"accipitriformesColumn","component":"Column","children":["accipitriformesText","baldEagleCard"]},{"id":"accipitriformesText","component":"Text","text":"Order: Accipitriformes"},{"id":"baldEagleCard","component":"Card","child":"baldEagleRow"},{"id":"baldEagleRow","component":"Row","children":["baldEagleImage","baldEagleText"]},{"id":"baldEagleImage","component":"Image","url":"https://example.com/baldeagle.jpg"},{"id":"baldEagleText","component":"Text","text":"Bald Eagle"},{"id":"struthioniformesCard","component":"Card","child":"struthioniformesColumn"},{"id":"struthioniformesColumn","component":"Column","children":["struthioniformesText","ostrichCard"]},{"id":"struthioniformesText","component":"Text","text":"Order: Struthioniformes"},{"id":"ostrichCard","component":"Card","child":"ostrichRow"},{"id":"ostrichRow","component":"Row","children":["ostrichImage","ostrichText"]},{"id":"ostrichImage","component":"Image","url":"https://example.com/ostrich.jpg"},{"id":"ostrichText","component":"Text","text":"Ostrich"},{"id":"sphenisciformesCard","component":"Card","child":"sphenisciformesColumn"},{"id":"sphenisciformesColumn","component":"Column","children":["sphenisciformesText","penguinCard"]},{"id":"sphenisciformesText","component":"Text","text":"Order: Sphenisciformes"},{"id":"penguinCard","component":"Card","child":"penguinRow"},{"id":"penguinRow","component":"Row","children":["penguinImage","penguinText"]},{"id":"penguinImage","component":"Image","url":"https://example.com/penguin.jpg"},{"id":"penguinText","component":"Text","text":"Penguin"},{"id":"reptilesTabContent","component":"Column","children":["reptilesSearchField","reptiliaCard"]},{"id":"reptilesSearchField","component":"TextField","label":"Search...","value":""},{"id":"reptiliaCard","component":"Card","child":"reptiliaColumn"},{"id":"reptiliaColumn","component":"Column","children":["reptiliaText","crocodiliaCard","squamataCard"]},{"id":"reptiliaText","component":"Text","text":"Class: Reptilia"},{"id":"crocodiliaCard","component":"Card","child":"crocodiliaColumn"},{"id":"crocodiliaColumn","component":"Column","children":["crocodiliaText","nileCrocodileCard"]},{"id":"crocodiliaText","component":"Text","text":"Order: Crocodilia"},{"id":"nileCrocodileCard","component":"Card","child":"nileCrocodileRow"},{"id":"nileCrocodileRow","component":"Row","children":["nileCrocodileImage","nileCrocodileText"]},{"id":"nileCrocodileImage","component":"Image","url":"https://example.com/nilecrocodile.jpg"},{"id":"nileCrocodileText","component":"Text","text":"Nile Crocodile"},{"id":"squamataCard","component":"Card","child":"squamataColumn"},{"id":"squamataColumn","component":"Column","children":["squamataText","komodoDragonCard","ballPythonCard"]},{"id":"squamataText","component":"Text","text":"Order: Squamata"},{"id":"komodoDragonCard","component":"Card","child":"komodoDragonRow"},{"id":"komodoDragonRow","component":"Row","children":["komodoDragonImage","komodoDragonText"]},{"id":"komodoDragonImage","component":"Image","url":"https://example.com/komododragon.jpg"},{"id":"komodoDragonText","component":"Text","text":"Komodo Dragon"},{"id":"ballPythonCard","component":"Card","child":"ballPythonRow"},{"id":"ballPythonRow","component":"Row","children":["ballPythonImage","ballPythonText"]},{"id":"ballPythonImage","component":"Image","url":"https://example.com/ballpython.jpg"},{"id":"ballPythonText","component":"Text","text":"Ball Python"}]}} diff --git a/dev_tools/catalog_gallery/samples/audioPlayer.sample b/dev_tools/catalog_gallery/samples/audioPlayer.sample index 8d4283eda..8feaa2d73 100644 --- a/dev_tools/catalog_gallery/samples/audioPlayer.sample +++ b/dev_tools/catalog_gallery/samples/audioPlayer.sample @@ -4,5 +4,5 @@ name: audioPlayer prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for an AudioPlayer component that plays a short audio clip. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["audioPlayerComponent"]},{"id":"audioPlayerComponent","component":"AudioPlayer","url":"https://upload.wikimedia.org/wikipedia/commons/d/db/Minuet_in_G_%28Beethoven%29%2C_piano.ogg","description":"Beethoven — Minuet in G (public domain)"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["audioPlayerComponent"]},{"id":"audioPlayerComponent","component":"AudioPlayer","url":"https://upload.wikimedia.org/wikipedia/commons/d/db/Minuet_in_G_%28Beethoven%29%2C_piano.ogg","description":"Beethoven — Minuet in G (public domain)"}]}} diff --git a/dev_tools/catalog_gallery/samples/calendarEventCreator.sample b/dev_tools/catalog_gallery/samples/calendarEventCreator.sample index e6b7c79fe..b301fd6f8 100644 --- a/dev_tools/catalog_gallery/samples/calendarEventCreator.sample +++ b/dev_tools/catalog_gallery/samples/calendarEventCreator.sample @@ -4,5 +4,5 @@ name: calendarEventCreator prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a calendar event creation form. It should have a 'Text' (variant 'h1') "New Event". Include a 'TextField' for the "Event Title". Use a 'Row' for two 'DateTimeInput's for "Start Time" and "End Time" (initialize both with a literal empty string value: '' (do not bind to a data path)). Add a 'CheckBox' labeled "All-day event". Finally, a 'Row' with two 'Button's: "Save" and "Cancel". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["newEventTitle","eventTitleField","timeInputsRow","allDayCheckbox","actionButtonsRow"]},{"id":"newEventTitle","component":"Text","text":"New Event","variant":"h1"},{"id":"eventTitleField","component":"TextField","label":"Event Title","value":""},{"id":"timeInputsRow","component":"Row","children":["startTimeInput","endTimeInput"],"justify":"spaceBetween"},{"id":"startTimeInput","component":"DateTimeInput","value":"","enableDate":true,"enableTime":true,"label":"Start Time"},{"id":"endTimeInput","component":"DateTimeInput","value":"","enableDate":true,"enableTime":true,"label":"End Time"},{"id":"allDayCheckbox","component":"CheckBox","label":"All-day event","value":false},{"id":"actionButtonsRow","component":"Row","children":["saveButton","cancelButton"],"justify":"end"},{"id":"saveButton","component":"Button","child":"saveButtonText","action":{"event":{"name":"saveEvent"}},"variant":"primary"},{"id":"saveButtonText","component":"Text","text":"Save"},{"id":"cancelButton","component":"Button","child":"cancelButtonText","action":{"event":{"name":"cancelEvent"}}},{"id":"cancelButtonText","component":"Text","text":"Cancel"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["newEventTitle","eventTitleField","timeInputsRow","allDayCheckbox","actionButtonsRow"]},{"id":"newEventTitle","component":"Text","text":"New Event","variant":"h1"},{"id":"eventTitleField","component":"TextField","label":"Event Title","value":""},{"id":"timeInputsRow","component":"Row","children":["startTimeInput","endTimeInput"],"justify":"spaceBetween"},{"id":"startTimeInput","component":"DateTimeInput","value":"","enableDate":true,"enableTime":true,"label":"Start Time"},{"id":"endTimeInput","component":"DateTimeInput","value":"","enableDate":true,"enableTime":true,"label":"End Time"},{"id":"allDayCheckbox","component":"CheckBox","label":"All-day event","value":false},{"id":"actionButtonsRow","component":"Row","children":["saveButton","cancelButton"],"justify":"end"},{"id":"saveButton","component":"Button","child":"saveButtonText","action":{"event":{"name":"saveEvent"}},"variant":"primary"},{"id":"saveButtonText","component":"Text","text":"Save"},{"id":"cancelButton","component":"Button","child":"cancelButtonText","action":{"event":{"name":"cancelEvent"}}},{"id":"cancelButtonText","component":"Text","text":"Cancel"}]}} diff --git a/dev_tools/catalog_gallery/samples/chatRoom.sample b/dev_tools/catalog_gallery/samples/chatRoom.sample index 15d20c3b0..a81ae27ef 100644 --- a/dev_tools/catalog_gallery/samples/chatRoom.sample +++ b/dev_tools/catalog_gallery/samples/chatRoom.sample @@ -4,5 +4,5 @@ name: chatRoom prompt: | Create a chat room interface. It should have a 'Column' for the message history. Inside, include several 'Card's representing messages, each with a 'Text' for the sender and a 'Text' for the message body. Specifically include these messages: "Alice: Hi there!", "Bob: Hello!". At the bottom, a 'Row' with a 'TextField' (label "Type a message...") and a 'Button' labeled "Send". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["messageHistoryColumn","messageInputRow"]},{"id":"messageHistoryColumn","component":"Column","children":["messageCardAlice","messageCardBob"]},{"id":"messageCardAlice","component":"Card","child":"messageContentAlice"},{"id":"messageContentAlice","component":"Column","children":["senderAlice","bodyAlice"]},{"id":"senderAlice","component":"Text","text":"Alice:"},{"id":"bodyAlice","component":"Text","text":"Hi there!"},{"id":"messageCardBob","component":"Card","child":"messageContentBob"},{"id":"messageContentBob","component":"Column","children":["senderBob","bodyBob"]},{"id":"senderBob","component":"Text","text":"Bob:"},{"id":"bodyBob","component":"Text","text":"Hello!"},{"id":"messageInputRow","component":"Row","children":["messageTextField","sendButton"]},{"id":"messageTextField","component":"TextField","label":"Type a message...","value":""},{"id":"sendButton","component":"Button","child":"sendButtonText","action":{"event":{"name":"sendMessage"}}},{"id":"sendButtonText","component":"Text","text":"Send"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["messageHistoryColumn","messageInputRow"]},{"id":"messageHistoryColumn","component":"Column","children":["messageCardAlice","messageCardBob"]},{"id":"messageCardAlice","component":"Card","child":"messageContentAlice"},{"id":"messageContentAlice","component":"Column","children":["senderAlice","bodyAlice"]},{"id":"senderAlice","component":"Text","text":"Alice:"},{"id":"bodyAlice","component":"Text","text":"Hi there!"},{"id":"messageCardBob","component":"Card","child":"messageContentBob"},{"id":"messageContentBob","component":"Column","children":["senderBob","bodyBob"]},{"id":"senderBob","component":"Text","text":"Bob:"},{"id":"bodyBob","component":"Text","text":"Hello!"},{"id":"messageInputRow","component":"Row","children":["messageTextField","sendButton"]},{"id":"messageTextField","component":"TextField","label":"Type a message...","value":""},{"id":"sendButton","component":"Button","child":"sendButtonText","action":{"event":{"name":"sendMessage"}}},{"id":"sendButtonText","component":"Text","text":"Send"}]}} diff --git a/dev_tools/catalog_gallery/samples/checkoutPage.sample b/dev_tools/catalog_gallery/samples/checkoutPage.sample index c65d87546..062db737b 100644 --- a/dev_tools/catalog_gallery/samples/checkoutPage.sample +++ b/dev_tools/catalog_gallery/samples/checkoutPage.sample @@ -4,6 +4,6 @@ name: checkoutPage prompt: | Create a simplified e-commerce checkout page. It should have a 'Text' (variant 'h1') "Checkout". A 'Column' for shipping info with 'TextField's for "Name", "Address", "City", "Zip Code". A 'Column' for payment info with 'TextField's for "Card Number", "Expiry Date", "CVV". Finally, a 'Text' "Total: $99.99" and a 'Button' "Place Order". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["checkoutTitle","shippingInfoColumn","paymentInfoColumn","totalText","placeOrderButton"]},{"id":"checkoutTitle","component":"Text","text":"Checkout","variant":"h1"},{"id":"shippingInfoColumn","component":"Column","children":["nameField","addressField","cityField","zipCodeField"]},{"id":"nameField","component":"TextField","label":"Name","value":{"path":"/shipping/name"}},{"id":"addressField","component":"TextField","label":"Address","value":{"path":"/shipping/address"}},{"id":"cityField","component":"TextField","label":"City","value":{"path":"/shipping/city"}},{"id":"zipCodeField","component":"TextField","label":"Zip Code","value":{"path":"/shipping/zipCode"}},{"id":"paymentInfoColumn","component":"Column","children":["cardNumberField","expiryDateField","cvvField"]},{"id":"cardNumberField","component":"TextField","label":"Card Number","value":{"path":"/payment/cardNumber"}},{"id":"expiryDateField","component":"TextField","label":"Expiry Date","value":{"path":"/payment/expiryDate"}},{"id":"cvvField","component":"TextField","label":"CVV","value":{"path":"/payment/cvv"}},{"id":"totalText","component":"Text","text":"Total: $99.99"},{"id":"placeOrderButton","component":"Button","child":"placeOrderButtonText","action":{"event":{"name":"placeOrder"}}},{"id":"placeOrderButtonText","component":"Text","text":"Place Order"}]}} -{"version":"v0.9","updateDataModel":{"surfaceId":"main","value":{"shipping":{"name":"","address":"","city":"","zipCode":""},"payment":{"cardNumber":"","expiryDate":"","cvv":""}}}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["checkoutTitle","shippingInfoColumn","paymentInfoColumn","totalText","placeOrderButton"]},{"id":"checkoutTitle","component":"Text","text":"Checkout","variant":"h1"},{"id":"shippingInfoColumn","component":"Column","children":["nameField","addressField","cityField","zipCodeField"]},{"id":"nameField","component":"TextField","label":"Name","value":{"path":"/shipping/name"}},{"id":"addressField","component":"TextField","label":"Address","value":{"path":"/shipping/address"}},{"id":"cityField","component":"TextField","label":"City","value":{"path":"/shipping/city"}},{"id":"zipCodeField","component":"TextField","label":"Zip Code","value":{"path":"/shipping/zipCode"}},{"id":"paymentInfoColumn","component":"Column","children":["cardNumberField","expiryDateField","cvvField"]},{"id":"cardNumberField","component":"TextField","label":"Card Number","value":{"path":"/payment/cardNumber"}},{"id":"expiryDateField","component":"TextField","label":"Expiry Date","value":{"path":"/payment/expiryDate"}},{"id":"cvvField","component":"TextField","label":"CVV","value":{"path":"/payment/cvv"}},{"id":"totalText","component":"Text","text":"Total: $99.99"},{"id":"placeOrderButton","component":"Button","child":"placeOrderButtonText","action":{"event":{"name":"placeOrder"}}},{"id":"placeOrderButtonText","component":"Text","text":"Place Order"}]}} +{"version":"v1.0","updateDataModel":{"surfaceId":"main","value":{"shipping":{"name":"","address":"","city":"","zipCode":""},"payment":{"cardNumber":"","expiryDate":"","cvv":""}}}} diff --git a/dev_tools/catalog_gallery/samples/cinemaSeatSelection.sample b/dev_tools/catalog_gallery/samples/cinemaSeatSelection.sample index dde4e0cf6..300f4c079 100644 --- a/dev_tools/catalog_gallery/samples/cinemaSeatSelection.sample +++ b/dev_tools/catalog_gallery/samples/cinemaSeatSelection.sample @@ -8,5 +8,5 @@ prompt: | - Row C: 4 'CheckBox'es. 'Button' "Confirm Selection". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["titleText","screenText","seatRowA","seatRowB","seatRowC","confirmButton"],"align":"center"},{"id":"titleText","component":"Text","text":"Select Seats","variant":"h1"},{"id":"screenText","component":"Text","text":"Screen","align":"center"},{"id":"seatRowA","component":"Row","children":["seatA1","seatA2","seatA3","seatA4"],"justify":"center"},{"id":"seatA1","component":"CheckBox","label":"A1","value":false},{"id":"seatA2","component":"CheckBox","label":"A2","value":false},{"id":"seatA3","component":"CheckBox","label":"A3","value":false},{"id":"seatA4","component":"CheckBox","label":"A4","value":false},{"id":"seatRowB","component":"Row","children":["seatB1","seatB2","seatB3","seatB4"],"justify":"center"},{"id":"seatB1","component":"CheckBox","label":"B1","value":false},{"id":"seatB2","component":"CheckBox","label":"B2","value":false},{"id":"seatB3","component":"CheckBox","label":"B3","value":false},{"id":"seatB4","component":"CheckBox","label":"B4","value":false},{"id":"seatRowC","component":"Row","children":["seatC1","seatC2","seatC3","seatC4"],"justify":"center"},{"id":"seatC1","component":"CheckBox","label":"C1","value":false},{"id":"seatC2","component":"CheckBox","label":"C2","value":false},{"id":"seatC3","component":"CheckBox","label":"C3","value":false},{"id":"seatC4","component":"CheckBox","label":"C4","value":false},{"id":"confirmButton","component":"Button","child":"confirmButtonText","action":{"event":{"name":"confirmSelection"}}},{"id":"confirmButtonText","component":"Text","text":"Confirm Selection"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["titleText","screenText","seatRowA","seatRowB","seatRowC","confirmButton"],"align":"center"},{"id":"titleText","component":"Text","text":"Select Seats","variant":"h1"},{"id":"screenText","component":"Text","text":"Screen","align":"center"},{"id":"seatRowA","component":"Row","children":["seatA1","seatA2","seatA3","seatA4"],"justify":"center"},{"id":"seatA1","component":"CheckBox","label":"A1","value":false},{"id":"seatA2","component":"CheckBox","label":"A2","value":false},{"id":"seatA3","component":"CheckBox","label":"A3","value":false},{"id":"seatA4","component":"CheckBox","label":"A4","value":false},{"id":"seatRowB","component":"Row","children":["seatB1","seatB2","seatB3","seatB4"],"justify":"center"},{"id":"seatB1","component":"CheckBox","label":"B1","value":false},{"id":"seatB2","component":"CheckBox","label":"B2","value":false},{"id":"seatB3","component":"CheckBox","label":"B3","value":false},{"id":"seatB4","component":"CheckBox","label":"B4","value":false},{"id":"seatRowC","component":"Row","children":["seatC1","seatC2","seatC3","seatC4"],"justify":"center"},{"id":"seatC1","component":"CheckBox","label":"C1","value":false},{"id":"seatC2","component":"CheckBox","label":"C2","value":false},{"id":"seatC3","component":"CheckBox","label":"C3","value":false},{"id":"seatC4","component":"CheckBox","label":"C4","value":false},{"id":"confirmButton","component":"Button","child":"confirmButtonText","action":{"event":{"name":"confirmSelection"}}},{"id":"confirmButtonText","component":"Text","text":"Confirm Selection"}]}} diff --git a/dev_tools/catalog_gallery/samples/clientSideValidation.sample b/dev_tools/catalog_gallery/samples/clientSideValidation.sample index 1a6678c94..cab10717e 100644 --- a/dev_tools/catalog_gallery/samples/clientSideValidation.sample +++ b/dev_tools/catalog_gallery/samples/clientSideValidation.sample @@ -6,5 +6,5 @@ prompt: | Include a 'TextField' for "Username" that MUST match the regex "^[a-zA-Z0-9]{3,}$". If it fails, show error "Username must be at least 3 alphanumeric characters". Include a 'Button' labeled "Register". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["usernameTextField","registerButton"]},{"id":"usernameTextField","component":"TextField","label":"Username","value":{"path":"/username"},"checks":[{"condition":{"call":"regex","args":{"value":{"path":"/username"},"pattern":"^[a-zA-Z0-9]{3,}$"}},"message":"Username must be at least 3 alphanumeric characters"}]},{"id":"registerButton","component":"Button","child":"registerButtonText","action":{"event":{"name":"registerUser"}}},{"id":"registerButtonText","component":"Text","text":"Register"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["usernameTextField","registerButton"]},{"id":"usernameTextField","component":"TextField","label":"Username","value":{"path":"/username"},"checks":[{"condition":{"call":"regex","args":{"value":{"path":"/username"},"pattern":"^[a-zA-Z0-9]{3,}$"}},"message":"Username must be at least 3 alphanumeric characters"}]},{"id":"registerButton","component":"Button","child":"registerButtonText","action":{"event":{"name":"registerUser"}}},{"id":"registerButtonText","component":"Text","text":"Register"}]}} diff --git a/dev_tools/catalog_gallery/samples/contactCard.sample b/dev_tools/catalog_gallery/samples/contactCard.sample index dbec1a5c2..8d90d12c1 100644 --- a/dev_tools/catalog_gallery/samples/contactCard.sample +++ b/dev_tools/catalog_gallery/samples/contactCard.sample @@ -4,5 +4,5 @@ name: contactCard prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a contact card. The root component of the surface must be a 'Card'. This Card should contain a 'Row'. The row contains an 'Image' (as an avatar) and a 'Column'. The column contains a 'Text' for the name "Jane Doe", a 'Text' for the email "jane.doe@example.com", and a 'Text' for the phone number "(123) 456-7890". Below the main row, add a 'Button' labeled "View on Map" (using a child 'Text' component). --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Card","child":"mainColumn"},{"id":"mainColumn","component":"Column","children":["contactRow","mapButton"]},{"id":"contactRow","component":"Row","children":["avatarImage","contactDetailsColumn"],"align":"center"},{"id":"avatarImage","component":"Image","url":"https://example.com/avatar.jpg","variant":"avatar"},{"id":"contactDetailsColumn","component":"Column","children":["nameText","emailText","phoneText"]},{"id":"nameText","component":"Text","text":"Jane Doe","variant":"h5"},{"id":"emailText","component":"Text","text":"jane.doe@example.com","variant":"body"},{"id":"phoneText","component":"Text","text":"(123) 456-7890","variant":"body"},{"id":"mapButton","component":"Button","child":"mapButtonText","action":{"functionCall":{"call":"openUrl","args":{"url":"https://maps.google.com/?q=Jane+Doe+address"},"returnType":"void"}}},{"id":"mapButtonText","component":"Text","text":"View on Map"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Card","child":"mainColumn"},{"id":"mainColumn","component":"Column","children":["contactRow","mapButton"]},{"id":"contactRow","component":"Row","children":["avatarImage","contactDetailsColumn"],"align":"center"},{"id":"avatarImage","component":"Image","url":"https://example.com/avatar.jpg","variant":"avatar"},{"id":"contactDetailsColumn","component":"Column","children":["nameText","emailText","phoneText"]},{"id":"nameText","component":"Text","text":"Jane Doe","variant":"h5"},{"id":"emailText","component":"Text","text":"jane.doe@example.com","variant":"body"},{"id":"phoneText","component":"Text","text":"(123) 456-7890","variant":"body"},{"id":"mapButton","component":"Button","child":"mapButtonText","action":{"functionCall":{"call":"openUrl","args":{"url":"https://maps.google.com/?q=Jane+Doe+address"},"returnType":"void"}}},{"id":"mapButtonText","component":"Text","text":"View on Map"}]}} diff --git a/dev_tools/catalog_gallery/samples/courseSyllabus.sample b/dev_tools/catalog_gallery/samples/courseSyllabus.sample index 468ac3072..a7a2b4586 100644 --- a/dev_tools/catalog_gallery/samples/courseSyllabus.sample +++ b/dev_tools/catalog_gallery/samples/courseSyllabus.sample @@ -6,5 +6,5 @@ prompt: | - For module 1, a 'Card' with 'Text' "Algorithms" and 'List' ("Sorting", "Searching"). - For module 2, a 'Card' with 'Text' "Data Structures" and 'List' ("Arrays", "Linked Lists"). --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["title","modulesList"]},{"id":"title","component":"Text","text":"Introduction to Computer Science","variant":"h1"},{"id":"modulesList","component":"List","children":["module1Card","module2Card"],"direction":"vertical"},{"id":"module1Card","component":"Card","child":"module1Content"},{"id":"module1Content","component":"Column","children":["module1Title","module1Items"]},{"id":"module1Title","component":"Text","text":"Algorithms","variant":"h4"},{"id":"module1Items","component":"List","children":["sortingText","searchingText"],"direction":"vertical"},{"id":"sortingText","component":"Text","text":"Sorting","variant":"body"},{"id":"searchingText","component":"Text","text":"Searching","variant":"body"},{"id":"module2Card","component":"Card","child":"module2Content"},{"id":"module2Content","component":"Column","children":["module2Title","module2Items"]},{"id":"module2Title","component":"Text","text":"Data Structures","variant":"h4"},{"id":"module2Items","component":"List","children":["arraysText","linkedListsText"],"direction":"vertical"},{"id":"arraysText","component":"Text","text":"Arrays","variant":"body"},{"id":"linkedListsText","component":"Text","text":"Linked Lists","variant":"body"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["title","modulesList"]},{"id":"title","component":"Text","text":"Introduction to Computer Science","variant":"h1"},{"id":"modulesList","component":"List","children":["module1Card","module2Card"],"direction":"vertical"},{"id":"module1Card","component":"Card","child":"module1Content"},{"id":"module1Content","component":"Column","children":["module1Title","module1Items"]},{"id":"module1Title","component":"Text","text":"Algorithms","variant":"h4"},{"id":"module1Items","component":"List","children":["sortingText","searchingText"],"direction":"vertical"},{"id":"sortingText","component":"Text","text":"Sorting","variant":"body"},{"id":"searchingText","component":"Text","text":"Searching","variant":"body"},{"id":"module2Card","component":"Card","child":"module2Content"},{"id":"module2Content","component":"Column","children":["module2Title","module2Items"]},{"id":"module2Title","component":"Text","text":"Data Structures","variant":"h4"},{"id":"module2Items","component":"List","children":["arraysText","linkedListsText"],"direction":"vertical"},{"id":"arraysText","component":"Text","text":"Arrays","variant":"body"},{"id":"linkedListsText","component":"Text","text":"Linked Lists","variant":"body"}]}} diff --git a/dev_tools/catalog_gallery/samples/dashboard.sample b/dev_tools/catalog_gallery/samples/dashboard.sample index 2e1bbb914..fe7bae99b 100644 --- a/dev_tools/catalog_gallery/samples/dashboard.sample +++ b/dev_tools/catalog_gallery/samples/dashboard.sample @@ -4,5 +4,5 @@ name: dashboard prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a simple dashboard. It should have a 'Text' (variant 'h1') "Sales Dashboard". Below, a 'Row' containing three 'Card's. The first card has a 'Text' "Revenue" and another 'Text' "$50,000". The second card has "New Customers" and "1,200". The third card has "Conversion Rate" and "4.5%". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["dashboardTitle","dashboardCardsRow"]},{"id":"dashboardTitle","component":"Text","text":"Sales Dashboard","variant":"h1"},{"id":"dashboardCardsRow","component":"Row","children":["revenueCard","customersCard","conversionCard"],"justify":"spaceBetween"},{"id":"revenueCard","component":"Card","child":"revenueCardContent"},{"id":"revenueCardContent","component":"Column","children":["revenueLabel","revenueValue"]},{"id":"revenueLabel","component":"Text","text":"Revenue"},{"id":"revenueValue","component":"Text","text":"$50,000","variant":"h3"},{"id":"customersCard","component":"Card","child":"customersCardContent"},{"id":"customersCardContent","component":"Column","children":["customersLabel","customersValue"]},{"id":"customersLabel","component":"Text","text":"New Customers"},{"id":"customersValue","component":"Text","text":"1,200","variant":"h3"},{"id":"conversionCard","component":"Card","child":"conversionCardContent"},{"id":"conversionCardContent","component":"Column","children":["conversionLabel","conversionValue"]},{"id":"conversionLabel","component":"Text","text":"Conversion Rate"},{"id":"conversionValue","component":"Text","text":"4.5%","variant":"h3"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["dashboardTitle","dashboardCardsRow"]},{"id":"dashboardTitle","component":"Text","text":"Sales Dashboard","variant":"h1"},{"id":"dashboardCardsRow","component":"Row","children":["revenueCard","customersCard","conversionCard"],"justify":"spaceBetween"},{"id":"revenueCard","component":"Card","child":"revenueCardContent"},{"id":"revenueCardContent","component":"Column","children":["revenueLabel","revenueValue"]},{"id":"revenueLabel","component":"Text","text":"Revenue"},{"id":"revenueValue","component":"Text","text":"$50,000","variant":"h3"},{"id":"customersCard","component":"Card","child":"customersCardContent"},{"id":"customersCardContent","component":"Column","children":["customersLabel","customersValue"]},{"id":"customersLabel","component":"Text","text":"New Customers"},{"id":"customersValue","component":"Text","text":"1,200","variant":"h3"},{"id":"conversionCard","component":"Card","child":"conversionCardContent"},{"id":"conversionCardContent","component":"Column","children":["conversionLabel","conversionValue"]},{"id":"conversionLabel","component":"Text","text":"Conversion Rate"},{"id":"conversionValue","component":"Text","text":"4.5%","variant":"h3"}]}} diff --git a/dev_tools/catalog_gallery/samples/dogBreedGenerator.sample b/dev_tools/catalog_gallery/samples/dogBreedGenerator.sample index c5d722b53..c79502b84 100644 --- a/dev_tools/catalog_gallery/samples/dogBreedGenerator.sample +++ b/dev_tools/catalog_gallery/samples/dogBreedGenerator.sample @@ -21,6 +21,6 @@ prompt: | - A section which shows the generated content --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateDataModel":{"surfaceId":"main","value":{"dogBreedName":"","numberOfLegs":0,"selectedSkills":[],"generatedDescription":"","breeds":[{"url":"https://upload.wikimedia.org/wikipedia/commons/a/a2/National_geographic_dog_on_fence.jpeg"},{"url":"https://upload.wikimedia.org/wikipedia/commons/e/e0/Dog_training.jpeg"},{"url":"https://upload.wikimedia.org/wikipedia/commons/a/a5/Red_Kangaroo_-_Taronga_Zoo.jpg"}],"allSkills":[{"label":"Jumping","value":"jumping"},{"label":"Swimming","value":"swimming"},{"label":"Running","value":"running"},{"label":"Flying","value":"flying"}]}}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["dogInfoCard","dogGeneratorCard"]},{"id":"dogInfoCard","component":"Card","child":"dogInfoColumn"},{"id":"dogInfoColumn","component":"Column","children":["dogInfoTitle","headerImage","dogBreedList"]},{"id":"dogInfoTitle","component":"Text","text":"Famous Dog breeds","variant":"h3"},{"id":"headerImage","component":"Image","url":"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Golden_retriever_for_service.jpg/1024px-Golden_retriever_for_service.jpg","variant":"header"},{"id":"dogBreedList","component":"List","direction":"horizontal","children":{"componentId":"breedImage","path":"/breeds"}},{"id":"breedImage","component":"Image","url":{"path":"/url"},"variant":"avatar"},{"id":"dogGeneratorCard","component":"Card","child":"dogGeneratorColumn"},{"id":"dogGeneratorColumn","component":"Column","children":["generatorTitle","generatorDescriptionText","dogBreedNameInput","numberOfLegsInput","skillsChoicePicker","generateButton","divider","generatedContent"]},{"id":"generatorTitle","component":"Text","text":"Dog Generator","variant":"h3"},{"id":"generatorDescriptionText","component":"Text","text":"Generate a fictional dog breed with a description.","variant":"body"},{"id":"dogBreedNameInput","component":"TextField","label":"Dog breed name","value":{"path":"/dogBreedName"}},{"id":"numberOfLegsInput","component":"TextField","label":"Number of legs","variant":"number","value":{"path":"/numberOfLegs"}},{"id":"skillsChoicePicker","component":"ChoicePicker","label":"Skills","variant":"multipleSelection","options":{"path":"/allSkills"},"value":{"path":"/selectedSkills"}},{"id":"generateButton","component":"Button","child":"generateButtonText","action":{"event":{"name":"generateDogDescription","context":{"breedName":{"path":"/dogBreedName"},"legs":{"path":"/numberOfLegs"},"skills":{"path":"/selectedSkills"}}}}},{"id":"generateButtonText","component":"Text","text":"Generate"},{"id":"divider","component":"Divider","axis":"horizontal"},{"id":"generatedContent","component":"Column","children":["generatedContentTitle","generatedDescriptionText"]},{"id":"generatedContentTitle","component":"Text","text":"Generated Dog Description:","variant":"h4"},{"id":"generatedDescriptionText","component":"Text","text":{"path":"/generatedDescription"},"variant":"body"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateDataModel":{"surfaceId":"main","value":{"dogBreedName":"","numberOfLegs":0,"selectedSkills":[],"generatedDescription":"","breeds":[{"url":"https://upload.wikimedia.org/wikipedia/commons/a/a2/National_geographic_dog_on_fence.jpeg"},{"url":"https://upload.wikimedia.org/wikipedia/commons/e/e0/Dog_training.jpeg"},{"url":"https://upload.wikimedia.org/wikipedia/commons/a/a5/Red_Kangaroo_-_Taronga_Zoo.jpg"}],"allSkills":[{"label":"Jumping","value":"jumping"},{"label":"Swimming","value":"swimming"},{"label":"Running","value":"running"},{"label":"Flying","value":"flying"}]}}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["dogInfoCard","dogGeneratorCard"]},{"id":"dogInfoCard","component":"Card","child":"dogInfoColumn"},{"id":"dogInfoColumn","component":"Column","children":["dogInfoTitle","headerImage","dogBreedList"]},{"id":"dogInfoTitle","component":"Text","text":"Famous Dog breeds","variant":"h3"},{"id":"headerImage","component":"Image","url":"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Golden_retriever_for_service.jpg/1024px-Golden_retriever_for_service.jpg","variant":"header"},{"id":"dogBreedList","component":"List","direction":"horizontal","children":{"componentId":"breedImage","path":"/breeds"}},{"id":"breedImage","component":"Image","url":{"path":"/url"},"variant":"avatar"},{"id":"dogGeneratorCard","component":"Card","child":"dogGeneratorColumn"},{"id":"dogGeneratorColumn","component":"Column","children":["generatorTitle","generatorDescriptionText","dogBreedNameInput","numberOfLegsInput","skillsChoicePicker","generateButton","divider","generatedContent"]},{"id":"generatorTitle","component":"Text","text":"Dog Generator","variant":"h3"},{"id":"generatorDescriptionText","component":"Text","text":"Generate a fictional dog breed with a description.","variant":"body"},{"id":"dogBreedNameInput","component":"TextField","label":"Dog breed name","value":{"path":"/dogBreedName"}},{"id":"numberOfLegsInput","component":"TextField","label":"Number of legs","variant":"number","value":{"path":"/numberOfLegs"}},{"id":"skillsChoicePicker","component":"ChoicePicker","label":"Skills","variant":"multipleSelection","options":{"path":"/allSkills"},"value":{"path":"/selectedSkills"}},{"id":"generateButton","component":"Button","child":"generateButtonText","action":{"event":{"name":"generateDogDescription","context":{"breedName":{"path":"/dogBreedName"},"legs":{"path":"/numberOfLegs"},"skills":{"path":"/selectedSkills"}}}}},{"id":"generateButtonText","component":"Text","text":"Generate"},{"id":"divider","component":"Divider","axis":"horizontal"},{"id":"generatedContent","component":"Column","children":["generatedContentTitle","generatedDescriptionText"]},{"id":"generatedContentTitle","component":"Text","text":"Generated Dog Description:","variant":"h4"},{"id":"generatedDescriptionText","component":"Text","text":{"path":"/generatedDescription"},"variant":"body"}]}} diff --git a/dev_tools/catalog_gallery/samples/eCommerceProductPage.sample b/dev_tools/catalog_gallery/samples/eCommerceProductPage.sample index 131cdf2e7..0bab940cc 100644 --- a/dev_tools/catalog_gallery/samples/eCommerceProductPage.sample +++ b/dev_tools/catalog_gallery/samples/eCommerceProductPage.sample @@ -14,5 +14,5 @@ prompt: | - A 'Button' with a 'Text' child "Add to Cart". - A 'Text' component for the product description below the button. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Row","children":["productImageColumn","productInfoColumn"]},{"id":"productImageColumn","component":"Column","weight":1,"children":["mainProductImage","thumbnailImagesRow"]},{"id":"mainProductImage","component":"Image","url":"https://example.com/leather-jacket-main.jpg","variant":"largeFeature"},{"id":"thumbnailImagesRow","component":"Row","children":["thumbnailImage1","thumbnailImage2","thumbnailImage3"]},{"id":"thumbnailImage1","component":"Image","url":"https://example.com/leather-jacket-thumb1.jpg","variant":"smallFeature"},{"id":"thumbnailImage2","component":"Image","url":"https://example.com/leather-jacket-thumb2.jpg","variant":"smallFeature"},{"id":"thumbnailImage3","component":"Image","url":"https://example.com/leather-jacket-thumb3.jpg","variant":"smallFeature"},{"id":"productInfoColumn","component":"Column","weight":1,"children":["productName","productPrice","infoDivider","sizePicker","colorPicker","addToCartButton","addToCartButtonText","productDescription"]},{"id":"productName","component":"Text","text":"Premium Leather Jacket","variant":"h1"},{"id":"productPrice","component":"Text","text":"$299.99"},{"id":"infoDivider","component":"Divider"},{"id":"sizePicker","component":"ChoicePicker","label":"Select Size","variant":"mutuallyExclusive","options":[{"label":"S","value":"small"},{"label":"M","value":"medium"},{"label":"L","value":"large"},{"label":"XL","value":"extraLarge"}],"value":{"path":"/selectedSize"}},{"id":"colorPicker","component":"ChoicePicker","label":"Select Color","variant":"mutuallyExclusive","options":[{"label":"Black","value":"black"},{"label":"Brown","value":"brown"},{"label":"Red","value":"red"}],"value":{"path":"/selectedColor"}},{"id":"addToCartButton","component":"Button","child":"addToCartButtonText","action":{"event":{"name":"addToCart","context":{"productId":"jacket123","size":{"path":"/selectedSize"},"color":{"path":"/selectedColor"}}}}},{"id":"addToCartButtonText","component":"Text","text":"Add to Cart"},{"id":"productDescription","component":"Text","text":"Crafted from genuine leather, this jacket offers timeless style and exceptional durability. Perfect for any season."}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Row","children":["productImageColumn","productInfoColumn"]},{"id":"productImageColumn","component":"Column","weight":1,"children":["mainProductImage","thumbnailImagesRow"]},{"id":"mainProductImage","component":"Image","url":"https://example.com/leather-jacket-main.jpg","variant":"largeFeature"},{"id":"thumbnailImagesRow","component":"Row","children":["thumbnailImage1","thumbnailImage2","thumbnailImage3"]},{"id":"thumbnailImage1","component":"Image","url":"https://example.com/leather-jacket-thumb1.jpg","variant":"smallFeature"},{"id":"thumbnailImage2","component":"Image","url":"https://example.com/leather-jacket-thumb2.jpg","variant":"smallFeature"},{"id":"thumbnailImage3","component":"Image","url":"https://example.com/leather-jacket-thumb3.jpg","variant":"smallFeature"},{"id":"productInfoColumn","component":"Column","weight":1,"children":["productName","productPrice","infoDivider","sizePicker","colorPicker","addToCartButton","addToCartButtonText","productDescription"]},{"id":"productName","component":"Text","text":"Premium Leather Jacket","variant":"h1"},{"id":"productPrice","component":"Text","text":"$299.99"},{"id":"infoDivider","component":"Divider"},{"id":"sizePicker","component":"ChoicePicker","label":"Select Size","variant":"mutuallyExclusive","options":[{"label":"S","value":"small"},{"label":"M","value":"medium"},{"label":"L","value":"large"},{"label":"XL","value":"extraLarge"}],"value":{"path":"/selectedSize"}},{"id":"colorPicker","component":"ChoicePicker","label":"Select Color","variant":"mutuallyExclusive","options":[{"label":"Black","value":"black"},{"label":"Brown","value":"brown"},{"label":"Red","value":"red"}],"value":{"path":"/selectedColor"}},{"id":"addToCartButton","component":"Button","child":"addToCartButtonText","action":{"event":{"name":"addToCart","context":{"productId":"jacket123","size":{"path":"/selectedSize"},"color":{"path":"/selectedColor"}}}}},{"id":"addToCartButtonText","component":"Text","text":"Add to Cart"},{"id":"productDescription","component":"Text","text":"Crafted from genuine leather, this jacket offers timeless style and exceptional durability. Perfect for any season."}]}} diff --git a/dev_tools/catalog_gallery/samples/fileBrowser.sample b/dev_tools/catalog_gallery/samples/fileBrowser.sample index 2bd26bd42..53804a0ad 100644 --- a/dev_tools/catalog_gallery/samples/fileBrowser.sample +++ b/dev_tools/catalog_gallery/samples/fileBrowser.sample @@ -4,5 +4,5 @@ name: fileBrowser prompt: | Create a file browser. It should have a 'Text' (variant 'h1') "My Files". A 'List' of 'Row's. Each row has an 'Icon' (folder or attachFile) and a 'Text' (filename). Examples (create these as static rows, not data bound): "Documents", "Images", "Work.txt". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["myFilesTitle","fileList"]},{"id":"myFilesTitle","component":"Text","text":"My Files","variant":"h1"},{"id":"fileList","component":"List","direction":"vertical","children":["row1","row2","row3"]},{"id":"row1","component":"Row","children":["iconFolder1","textDocuments"]},{"id":"iconFolder1","component":"Icon","name":"folder"},{"id":"textDocuments","component":"Text","text":"Documents"},{"id":"row2","component":"Row","children":["iconFolder2","textImages"]},{"id":"iconFolder2","component":"Icon","name":"folder"},{"id":"textImages","component":"Text","text":"Images"},{"id":"row3","component":"Row","children":["iconFile3","textWorkTxt"]},{"id":"iconFile3","component":"Icon","name":"attachFile"},{"id":"textWorkTxt","component":"Text","text":"Work.txt"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["myFilesTitle","fileList"]},{"id":"myFilesTitle","component":"Text","text":"My Files","variant":"h1"},{"id":"fileList","component":"List","direction":"vertical","children":["row1","row2","row3"]},{"id":"row1","component":"Row","children":["iconFolder1","textDocuments"]},{"id":"iconFolder1","component":"Icon","name":"folder"},{"id":"textDocuments","component":"Text","text":"Documents"},{"id":"row2","component":"Row","children":["iconFolder2","textImages"]},{"id":"iconFolder2","component":"Icon","name":"folder"},{"id":"textImages","component":"Text","text":"Images"},{"id":"row3","component":"Row","children":["iconFile3","textWorkTxt"]},{"id":"iconFile3","component":"Icon","name":"attachFile"},{"id":"textWorkTxt","component":"Text","text":"Work.txt"}]}} diff --git a/dev_tools/catalog_gallery/samples/fitnessTracker.sample b/dev_tools/catalog_gallery/samples/fitnessTracker.sample index 6be9fc7fe..da9e1273c 100644 --- a/dev_tools/catalog_gallery/samples/fitnessTracker.sample +++ b/dev_tools/catalog_gallery/samples/fitnessTracker.sample @@ -4,6 +4,6 @@ name: fitnessTracker prompt: | Create a fitness tracker dashboard. It should have a 'Text' (variant 'h1') "Daily Activity", and a 'Row' of 'Card's. Each card should contain a 'Column' with a 'Text' label (e.g. "Steps") and a 'Text' value (e.g. "10,000"). Create cards for "Steps" ("10,000"), "Calories" ("500 kcal"), "Distance" ("5 km"). Below that, a 'Slider' labeled "Daily Goal" (initialize value to 50). Finally, a 'List' of recent workouts. Use 'Text' components for the list items, for example: "Morning Run", "Evening Yoga", "Gym Session". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["dailyActivityTitle","activityCardsRow","dailyGoalSlider","workoutsList"]},{"id":"dailyActivityTitle","component":"Text","text":"Daily Activity","variant":"h1"},{"id":"activityCardsRow","component":"Row","children":["stepsCard","caloriesCard","distanceCard"],"justify":"spaceBetween"},{"id":"stepsCard","component":"Card","child":"stepsColumn"},{"id":"stepsColumn","component":"Column","children":["stepsLabel","stepsValue"]},{"id":"stepsLabel","component":"Text","text":"Steps"},{"id":"stepsValue","component":"Text","text":"10,000"},{"id":"caloriesCard","component":"Card","child":"caloriesColumn"},{"id":"caloriesColumn","component":"Column","children":["caloriesLabel","caloriesValue"]},{"id":"caloriesLabel","component":"Text","text":"Calories"},{"id":"caloriesValue","component":"Text","text":"500 kcal"},{"id":"distanceCard","component":"Card","child":"distanceColumn"},{"id":"distanceColumn","component":"Column","children":["distanceLabel","distanceValue"]},{"id":"distanceLabel","component":"Text","text":"Distance"},{"id":"distanceValue","component":"Text","text":"5 km"},{"id":"dailyGoalSlider","component":"Slider","label":"Daily Goal","min":0,"max":100,"value":{"path":"/dailyGoal"}},{"id":"workoutsList","component":"List","children":["workout1","workout2","workout3"],"direction":"vertical"},{"id":"workout1","component":"Text","text":"Morning Run"},{"id":"workout2","component":"Text","text":"Evening Yoga"},{"id":"workout3","component":"Text","text":"Gym Session"}]}} -{"version":"v0.9","updateDataModel":{"surfaceId":"main","value":{"dailyGoal":50}}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["dailyActivityTitle","activityCardsRow","dailyGoalSlider","workoutsList"]},{"id":"dailyActivityTitle","component":"Text","text":"Daily Activity","variant":"h1"},{"id":"activityCardsRow","component":"Row","children":["stepsCard","caloriesCard","distanceCard"],"justify":"spaceBetween"},{"id":"stepsCard","component":"Card","child":"stepsColumn"},{"id":"stepsColumn","component":"Column","children":["stepsLabel","stepsValue"]},{"id":"stepsLabel","component":"Text","text":"Steps"},{"id":"stepsValue","component":"Text","text":"10,000"},{"id":"caloriesCard","component":"Card","child":"caloriesColumn"},{"id":"caloriesColumn","component":"Column","children":["caloriesLabel","caloriesValue"]},{"id":"caloriesLabel","component":"Text","text":"Calories"},{"id":"caloriesValue","component":"Text","text":"500 kcal"},{"id":"distanceCard","component":"Card","child":"distanceColumn"},{"id":"distanceColumn","component":"Column","children":["distanceLabel","distanceValue"]},{"id":"distanceLabel","component":"Text","text":"Distance"},{"id":"distanceValue","component":"Text","text":"5 km"},{"id":"dailyGoalSlider","component":"Slider","label":"Daily Goal","min":0,"max":100,"value":{"path":"/dailyGoal"}},{"id":"workoutsList","component":"List","children":["workout1","workout2","workout3"],"direction":"vertical"},{"id":"workout1","component":"Text","text":"Morning Run"},{"id":"workout2","component":"Text","text":"Evening Yoga"},{"id":"workout3","component":"Text","text":"Gym Session"}]}} +{"version":"v1.0","updateDataModel":{"surfaceId":"main","value":{"dailyGoal":50}}} diff --git a/dev_tools/catalog_gallery/samples/flashcardApp.sample b/dev_tools/catalog_gallery/samples/flashcardApp.sample index af56e5359..94531f968 100644 --- a/dev_tools/catalog_gallery/samples/flashcardApp.sample +++ b/dev_tools/catalog_gallery/samples/flashcardApp.sample @@ -4,5 +4,5 @@ name: flashcardApp prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a flashcard app. 'Text' (h1) "Spanish Vocabulary". 'Card' (the flashcard). Inside the card, a 'Column' with 'Text' (h2) "Hola" (Front). 'Divider'. 'Text' "Hello" (Back - conceptually hidden, but rendered here). 'Row' of buttons: "Hard", "Good", "Easy". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["appTitle","flashcard","buttonsRow"],"justify":"center","align":"center"},{"id":"appTitle","component":"Text","text":"Spanish Vocabulary","variant":"h1"},{"id":"flashcard","component":"Card","child":"flashcardContent"},{"id":"flashcardContent","component":"Column","children":["flashcardFront","flashcardDivider","flashcardBack"],"align":"center"},{"id":"flashcardFront","component":"Text","text":"Hola","variant":"h2"},{"id":"flashcardDivider","component":"Divider","axis":"horizontal"},{"id":"flashcardBack","component":"Text","text":"Hello"},{"id":"buttonsRow","component":"Row","children":["hardButton","goodButton","easyButton"],"justify":"spaceBetween"},{"id":"hardButton","component":"Button","child":"hardButtonText","action":{"event":{"name":"rateFlashcard","context":{"rating":"hard"}}}},{"id":"hardButtonText","component":"Text","text":"Hard"},{"id":"goodButton","component":"Button","child":"goodButtonText","action":{"event":{"name":"rateFlashcard","context":{"rating":"good"}}}},{"id":"goodButtonText","component":"Text","text":"Good"},{"id":"easyButton","component":"Button","child":"easyButtonText","action":{"event":{"name":"rateFlashcard","context":{"rating":"easy"}}}},{"id":"easyButtonText","component":"Text","text":"Easy"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["appTitle","flashcard","buttonsRow"],"justify":"center","align":"center"},{"id":"appTitle","component":"Text","text":"Spanish Vocabulary","variant":"h1"},{"id":"flashcard","component":"Card","child":"flashcardContent"},{"id":"flashcardContent","component":"Column","children":["flashcardFront","flashcardDivider","flashcardBack"],"align":"center"},{"id":"flashcardFront","component":"Text","text":"Hola","variant":"h2"},{"id":"flashcardDivider","component":"Divider","axis":"horizontal"},{"id":"flashcardBack","component":"Text","text":"Hello"},{"id":"buttonsRow","component":"Row","children":["hardButton","goodButton","easyButton"],"justify":"spaceBetween"},{"id":"hardButton","component":"Button","child":"hardButtonText","action":{"event":{"name":"rateFlashcard","context":{"rating":"hard"}}}},{"id":"hardButtonText","component":"Text","text":"Hard"},{"id":"goodButton","component":"Button","child":"goodButtonText","action":{"event":{"name":"rateFlashcard","context":{"rating":"good"}}}},{"id":"goodButtonText","component":"Text","text":"Good"},{"id":"easyButton","component":"Button","child":"easyButtonText","action":{"event":{"name":"rateFlashcard","context":{"rating":"easy"}}}},{"id":"easyButtonText","component":"Text","text":"Easy"}]}} diff --git a/dev_tools/catalog_gallery/samples/flightBooker.sample b/dev_tools/catalog_gallery/samples/flightBooker.sample index d75e37a86..faaafd11b 100644 --- a/dev_tools/catalog_gallery/samples/flightBooker.sample +++ b/dev_tools/catalog_gallery/samples/flightBooker.sample @@ -4,6 +4,6 @@ name: flightBooker prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a flight booking form. It should have a 'Text' (variant 'h1') "Book a Flight". Then a 'Row' with two 'TextField's for "Origin" and "Destination". Below that, a 'Row' with two 'DateTimeInput's for "Departure Date" and "Return Date" (initialize with empty values). Add a 'Slider' labeled "Passengers" (min 1, max 10, value 1). Finally, a 'Button' labeled "Search Flights". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["bookFlightTitle","originDestinationRow","datesRow","passengersSlider","searchFlightsButton"]},{"id":"bookFlightTitle","component":"Text","text":"Book a Flight","variant":"h1"},{"id":"originDestinationRow","component":"Row","children":["originTextField","destinationTextField"]},{"id":"originTextField","component":"TextField","label":"Origin","value":{"path":"/origin"}},{"id":"destinationTextField","component":"TextField","label":"Destination","value":{"path":"/destination"}},{"id":"datesRow","component":"Row","children":["departureDateInput","returnDateInput"]},{"id":"departureDateInput","component":"DateTimeInput","label":"Departure Date","value":{"path":"/departureDate"},"enableDate":true},{"id":"returnDateInput","component":"DateTimeInput","label":"Return Date","value":{"path":"/returnDate"},"enableDate":true},{"id":"passengersSlider","component":"Slider","label":"Passengers","min":1,"max":10,"value":{"path":"/passengers"}},{"id":"searchFlightsButton","component":"Button","child":"searchFlightsButtonText","action":{"event":{"name":"searchFlights","context":{"origin":{"path":"/origin"},"destination":{"path":"/destination"},"departureDate":{"path":"/departureDate"},"returnDate":{"path":"/returnDate"},"passengers":{"path":"/passengers"}}}}},{"id":"searchFlightsButtonText","component":"Text","text":"Search Flights"}]}} -{"version":"v0.9","updateDataModel":{"surfaceId":"main","value":{"origin":"","destination":"","departureDate":"","returnDate":"","passengers":1}}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["bookFlightTitle","originDestinationRow","datesRow","passengersSlider","searchFlightsButton"]},{"id":"bookFlightTitle","component":"Text","text":"Book a Flight","variant":"h1"},{"id":"originDestinationRow","component":"Row","children":["originTextField","destinationTextField"]},{"id":"originTextField","component":"TextField","label":"Origin","value":{"path":"/origin"}},{"id":"destinationTextField","component":"TextField","label":"Destination","value":{"path":"/destination"}},{"id":"datesRow","component":"Row","children":["departureDateInput","returnDateInput"]},{"id":"departureDateInput","component":"DateTimeInput","label":"Departure Date","value":{"path":"/departureDate"},"enableDate":true},{"id":"returnDateInput","component":"DateTimeInput","label":"Return Date","value":{"path":"/returnDate"},"enableDate":true},{"id":"passengersSlider","component":"Slider","label":"Passengers","min":1,"max":10,"value":{"path":"/passengers"}},{"id":"searchFlightsButton","component":"Button","child":"searchFlightsButtonText","action":{"event":{"name":"searchFlights","context":{"origin":{"path":"/origin"},"destination":{"path":"/destination"},"departureDate":{"path":"/departureDate"},"returnDate":{"path":"/returnDate"},"passengers":{"path":"/passengers"}}}}},{"id":"searchFlightsButtonText","component":"Text","text":"Search Flights"}]}} +{"version":"v1.0","updateDataModel":{"surfaceId":"main","value":{"origin":"","destination":"","departureDate":"","returnDate":"","passengers":1}}} diff --git a/dev_tools/catalog_gallery/samples/hello_world.sample b/dev_tools/catalog_gallery/samples/hello_world.sample index 5ae773870..389e81032 100644 --- a/dev_tools/catalog_gallery/samples/hello_world.sample +++ b/dev_tools/catalog_gallery/samples/hello_world.sample @@ -1,5 +1,5 @@ name: Test Sample description: This is a test sample to verify the parser. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Text","text":"Hello World!","variant":"h1"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Text","text":"Hello World!","variant":"h1"}]}} diff --git a/dev_tools/catalog_gallery/samples/hotelSearchResults.sample b/dev_tools/catalog_gallery/samples/hotelSearchResults.sample index 1051b6ee0..6fb712d5e 100644 --- a/dev_tools/catalog_gallery/samples/hotelSearchResults.sample +++ b/dev_tools/catalog_gallery/samples/hotelSearchResults.sample @@ -6,5 +6,5 @@ prompt: | - Card 1: 'Row' with 'Image', 'Column' ('Text' "Grand Hotel", 'Text' "5 Stars", 'Text' "$200/night"), 'Button' "Book". - Card 2: 'Row' with 'Image', 'Column' ('Text' "City Inn", 'Text' "3 Stars", 'Text' "$100/night"), 'Button' "Book". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["hotelsTitle","hotelList"]},{"id":"hotelsTitle","component":"Text","text":"Hotels in Tokyo","variant":"h1"},{"id":"hotelList","component":"List","direction":"vertical","children":["card1","card2"]},{"id":"card1","component":"Card","child":"card1Content"},{"id":"card1Content","component":"Row","children":["card1Image","card1Details","card1Button"]},{"id":"card1Image","component":"Image","url":"https://example.com/grand-hotel.jpg","variant":"smallFeature"},{"id":"card1Details","component":"Column","children":["card1Name","card1Stars","card1Price"]},{"id":"card1Name","component":"Text","text":"Grand Hotel"},{"id":"card1Stars","component":"Text","text":"5 Stars"},{"id":"card1Price","component":"Text","text":"$200/night"},{"id":"card1Button","component":"Button","child":"card1ButtonText","action":{"event":{"name":"bookHotel","context":{"hotelId":"grandHotel"}}}},{"id":"card1ButtonText","component":"Text","text":"Book"},{"id":"card2","component":"Card","child":"card2Content"},{"id":"card2Content","component":"Row","children":["card2Image","card2Details","card2Button"]},{"id":"card2Image","component":"Image","url":"https://example.com/city-inn.jpg","variant":"smallFeature"},{"id":"card2Details","component":"Column","children":["card2Name","card2Stars","card2Price"]},{"id":"card2Name","component":"Text","text":"City Inn"},{"id":"card2Stars","component":"Text","text":"3 Stars"},{"id":"card2Price","component":"Text","text":"$100/night"},{"id":"card2Button","component":"Button","child":"card2ButtonText","action":{"event":{"name":"bookHotel","context":{"hotelId":"cityInn"}}}},{"id":"card2ButtonText","component":"Text","text":"Book"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["hotelsTitle","hotelList"]},{"id":"hotelsTitle","component":"Text","text":"Hotels in Tokyo","variant":"h1"},{"id":"hotelList","component":"List","direction":"vertical","children":["card1","card2"]},{"id":"card1","component":"Card","child":"card1Content"},{"id":"card1Content","component":"Row","children":["card1Image","card1Details","card1Button"]},{"id":"card1Image","component":"Image","url":"https://example.com/grand-hotel.jpg","variant":"smallFeature"},{"id":"card1Details","component":"Column","children":["card1Name","card1Stars","card1Price"]},{"id":"card1Name","component":"Text","text":"Grand Hotel"},{"id":"card1Stars","component":"Text","text":"5 Stars"},{"id":"card1Price","component":"Text","text":"$200/night"},{"id":"card1Button","component":"Button","child":"card1ButtonText","action":{"event":{"name":"bookHotel","context":{"hotelId":"grandHotel"}}}},{"id":"card1ButtonText","component":"Text","text":"Book"},{"id":"card2","component":"Card","child":"card2Content"},{"id":"card2Content","component":"Row","children":["card2Image","card2Details","card2Button"]},{"id":"card2Image","component":"Image","url":"https://example.com/city-inn.jpg","variant":"smallFeature"},{"id":"card2Details","component":"Column","children":["card2Name","card2Stars","card2Price"]},{"id":"card2Name","component":"Text","text":"City Inn"},{"id":"card2Stars","component":"Text","text":"3 Stars"},{"id":"card2Price","component":"Text","text":"$100/night"},{"id":"card2Button","component":"Button","child":"card2ButtonText","action":{"event":{"name":"bookHotel","context":{"hotelId":"cityInn"}}}},{"id":"card2ButtonText","component":"Text","text":"Book"}]}} diff --git a/dev_tools/catalog_gallery/samples/interactiveDashboard.sample b/dev_tools/catalog_gallery/samples/interactiveDashboard.sample index 64ce1ed27..9875266a6 100644 --- a/dev_tools/catalog_gallery/samples/interactiveDashboard.sample +++ b/dev_tools/catalog_gallery/samples/interactiveDashboard.sample @@ -13,5 +13,5 @@ prompt: | - The second 'Card' has a 'Text' (variant 'h2') "New Users" and a 'Text' component showing "4,321". Finally, a large 'Card' at the bottom with a 'Text' (variant 'h2') "Revenue Over Time" and a placeholder 'Image' with a valid URL to represent a line chart. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["dashboardTitle","filtersCard","metricsRow","revenueChartCard"]},{"id":"dashboardTitle","component":"Text","text":"Company Dashboard","variant":"h1"},{"id":"filtersCard","component":"Card","child":"filterControlsRow"},{"id":"filterControlsRow","component":"Row","children":["startDateInput","endDateInput","applyFiltersButton"],"justify":"spaceBetween"},{"id":"startDateInput","component":"DateTimeInput","label":"Start Date","value":"","enableDate":true},{"id":"endDateInput","component":"DateTimeInput","label":"End Date","value":"","enableDate":true},{"id":"applyFiltersButton","component":"Button","child":"applyFiltersText","action":{"event":{"name":"applyFilters"}}},{"id":"applyFiltersText","component":"Text","text":"Apply Filters"},{"id":"metricsRow","component":"Row","children":["totalRevenueCard","newUsersCard"],"justify":"spaceEvenly"},{"id":"totalRevenueCard","component":"Card","child":"totalRevenueColumn"},{"id":"totalRevenueColumn","component":"Column","children":["totalRevenueTitle","totalRevenueValue"]},{"id":"totalRevenueTitle","component":"Text","text":"Total Revenue","variant":"h2"},{"id":"totalRevenueValue","component":"Text","text":"$1,234,567"},{"id":"newUsersCard","component":"Card","child":"newUsersColumn"},{"id":"newUsersColumn","component":"Column","children":["newUsersTitle","newUsersValue"]},{"id":"newUsersTitle","component":"Text","text":"New Users","variant":"h2"},{"id":"newUsersValue","component":"Text","text":"4,321"},{"id":"revenueChartCard","component":"Card","child":"revenueChartColumn"},{"id":"revenueChartColumn","component":"Column","children":["revenueChartTitle","revenueChartImage"]},{"id":"revenueChartTitle","component":"Text","text":"Revenue Over Time","variant":"h2"},{"id":"revenueChartImage","component":"Image","url":"https://via.placeholder.com/600x300?text=Line+Chart"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["dashboardTitle","filtersCard","metricsRow","revenueChartCard"]},{"id":"dashboardTitle","component":"Text","text":"Company Dashboard","variant":"h1"},{"id":"filtersCard","component":"Card","child":"filterControlsRow"},{"id":"filterControlsRow","component":"Row","children":["startDateInput","endDateInput","applyFiltersButton"],"justify":"spaceBetween"},{"id":"startDateInput","component":"DateTimeInput","label":"Start Date","value":"","enableDate":true},{"id":"endDateInput","component":"DateTimeInput","label":"End Date","value":"","enableDate":true},{"id":"applyFiltersButton","component":"Button","child":"applyFiltersText","action":{"event":{"name":"applyFilters"}}},{"id":"applyFiltersText","component":"Text","text":"Apply Filters"},{"id":"metricsRow","component":"Row","children":["totalRevenueCard","newUsersCard"],"justify":"spaceEvenly"},{"id":"totalRevenueCard","component":"Card","child":"totalRevenueColumn"},{"id":"totalRevenueColumn","component":"Column","children":["totalRevenueTitle","totalRevenueValue"]},{"id":"totalRevenueTitle","component":"Text","text":"Total Revenue","variant":"h2"},{"id":"totalRevenueValue","component":"Text","text":"$1,234,567"},{"id":"newUsersCard","component":"Card","child":"newUsersColumn"},{"id":"newUsersColumn","component":"Column","children":["newUsersTitle","newUsersValue"]},{"id":"newUsersTitle","component":"Text","text":"New Users","variant":"h2"},{"id":"newUsersValue","component":"Text","text":"4,321"},{"id":"revenueChartCard","component":"Card","child":"revenueChartColumn"},{"id":"revenueChartColumn","component":"Column","children":["revenueChartTitle","revenueChartImage"]},{"id":"revenueChartTitle","component":"Text","text":"Revenue Over Time","variant":"h2"},{"id":"revenueChartImage","component":"Image","url":"https://via.placeholder.com/600x300?text=Line+Chart"}]}} diff --git a/dev_tools/catalog_gallery/samples/jobApplication.sample b/dev_tools/catalog_gallery/samples/jobApplication.sample index 7b4e46b38..768aa8dcb 100644 --- a/dev_tools/catalog_gallery/samples/jobApplication.sample +++ b/dev_tools/catalog_gallery/samples/jobApplication.sample @@ -4,6 +4,6 @@ name: jobApplication prompt: | Create a job application form. It should have 'TextField's for "Name", "Email", "Phone", "Resume URL". A 'ChoicePicker' (variant 'mutuallyExclusive') labeled "Years of Experience" (options: "0-1", "2-5", "5+"). A 'Button' "Submit Application". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateDataModel":{"surfaceId":"main","value":{"name":"","email":"","phone":"","resumeUrl":"","experience":""}}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["nameField","emailField","phoneField","resumeUrlField","experiencePicker","submitButton"],"justify":"start","align":"stretch"},{"id":"nameField","component":"TextField","label":"Name","value":{"path":"/name"},"checks":[{"condition":{"call":"required","args":{"value":{"path":"/name"}}},"message":"Name is required."}]},{"id":"emailField","component":"TextField","label":"Email","value":{"path":"/email"},"checks":[{"condition":{"call":"required","args":{"value":{"path":"/email"}}},"message":"Email is required."},{"condition":{"call":"email","args":{"value":{"path":"/email"}}},"message":"Invalid email format."}]},{"id":"phoneField","component":"TextField","label":"Phone","value":{"path":"/phone"},"checks":[{"condition":{"call":"required","args":{"value":{"path":"/phone"}}},"message":"Phone number is required."},{"condition":{"call":"regex","args":{"value":{"path":"/phone"},"pattern":"^\\+?[1-9]\\d{1,14}$"}},"message":"Invalid phone number format."}]},{"id":"resumeUrlField","component":"TextField","label":"Resume URL","value":{"path":"/resumeUrl"},"checks":[{"condition":{"call":"required","args":{"value":{"path":"/resumeUrl"}}},"message":"Resume URL is required."}]},{"id":"experiencePicker","component":"ChoicePicker","label":"Years of Experience","variant":"mutuallyExclusive","options":[{"label":"0-1","value":"0-1"},{"label":"2-5","value":"2-5"},{"label":"5+","value":"5+"}],"value":{"path":"/experience"}},{"id":"submitButton","component":"Button","child":"submitButtonText","action":{"event":{"name":"submitApplication","context":{"name":{"path":"/name"},"email":{"path":"/email"},"phone":{"path":"/phone"},"resumeUrl":{"path":"/resumeUrl"},"experience":{"path":"/experience"}}}}},{"id":"submitButtonText","component":"Text","text":"Submit Application"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateDataModel":{"surfaceId":"main","value":{"name":"","email":"","phone":"","resumeUrl":"","experience":""}}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["nameField","emailField","phoneField","resumeUrlField","experiencePicker","submitButton"],"justify":"start","align":"stretch"},{"id":"nameField","component":"TextField","label":"Name","value":{"path":"/name"},"checks":[{"condition":{"call":"required","args":{"value":{"path":"/name"}}},"message":"Name is required."}]},{"id":"emailField","component":"TextField","label":"Email","value":{"path":"/email"},"checks":[{"condition":{"call":"required","args":{"value":{"path":"/email"}}},"message":"Email is required."},{"condition":{"call":"email","args":{"value":{"path":"/email"}}},"message":"Invalid email format."}]},{"id":"phoneField","component":"TextField","label":"Phone","value":{"path":"/phone"},"checks":[{"condition":{"call":"required","args":{"value":{"path":"/phone"}}},"message":"Phone number is required."},{"condition":{"call":"regex","args":{"value":{"path":"/phone"},"pattern":"^\\+?[1-9]\\d{1,14}$"}},"message":"Invalid phone number format."}]},{"id":"resumeUrlField","component":"TextField","label":"Resume URL","value":{"path":"/resumeUrl"},"checks":[{"condition":{"call":"required","args":{"value":{"path":"/resumeUrl"}}},"message":"Resume URL is required."}]},{"id":"experiencePicker","component":"ChoicePicker","label":"Years of Experience","variant":"mutuallyExclusive","options":[{"label":"0-1","value":"0-1"},{"label":"2-5","value":"2-5"},{"label":"5+","value":"5+"}],"value":{"path":"/experience"}},{"id":"submitButton","component":"Button","child":"submitButtonText","action":{"event":{"name":"submitApplication","context":{"name":{"path":"/name"},"email":{"path":"/email"},"phone":{"path":"/phone"},"resumeUrl":{"path":"/resumeUrl"},"experience":{"path":"/experience"}}}}},{"id":"submitButtonText","component":"Text","text":"Submit Application"}]}} diff --git a/dev_tools/catalog_gallery/samples/kanbanBoard.sample b/dev_tools/catalog_gallery/samples/kanbanBoard.sample index 542c0f62f..fc4ab0924 100644 --- a/dev_tools/catalog_gallery/samples/kanbanBoard.sample +++ b/dev_tools/catalog_gallery/samples/kanbanBoard.sample @@ -8,5 +8,5 @@ prompt: | - "Done" column: Card "Planning". Each card should just contain a 'Text' with the task name. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["projectTitle","kanbanBoard"]},{"id":"projectTitle","component":"Text","text":"Project Tasks","variant":"h1"},{"id":"kanbanBoard","component":"Row","children":["toDoColumn","inProgressColumn","doneColumn"],"justify":"spaceEvenly"},{"id":"toDoColumn","component":"Column","children":["toDoHeader","cardResearch","cardDesign"],"align":"start"},{"id":"toDoHeader","component":"Text","text":"To Do","variant":"h2"},{"id":"cardResearch","component":"Card","child":"textResearch"},{"id":"textResearch","component":"Text","text":"Research"},{"id":"cardDesign","component":"Card","child":"textDesign"},{"id":"textDesign","component":"Text","text":"Design"},{"id":"inProgressColumn","component":"Column","children":["inProgressHeader","cardImplementation"],"align":"start"},{"id":"inProgressHeader","component":"Text","text":"In Progress","variant":"h2"},{"id":"cardImplementation","component":"Card","child":"textImplementation"},{"id":"textImplementation","component":"Text","text":"Implementation"},{"id":"doneColumn","component":"Column","children":["doneHeader","cardPlanning"],"align":"start"},{"id":"doneHeader","component":"Text","text":"Done","variant":"h2"},{"id":"cardPlanning","component":"Card","child":"textPlanning"},{"id":"textPlanning","component":"Text","text":"Planning"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["projectTitle","kanbanBoard"]},{"id":"projectTitle","component":"Text","text":"Project Tasks","variant":"h1"},{"id":"kanbanBoard","component":"Row","children":["toDoColumn","inProgressColumn","doneColumn"],"justify":"spaceEvenly"},{"id":"toDoColumn","component":"Column","children":["toDoHeader","cardResearch","cardDesign"],"align":"start"},{"id":"toDoHeader","component":"Text","text":"To Do","variant":"h2"},{"id":"cardResearch","component":"Card","child":"textResearch"},{"id":"textResearch","component":"Text","text":"Research"},{"id":"cardDesign","component":"Card","child":"textDesign"},{"id":"textDesign","component":"Text","text":"Design"},{"id":"inProgressColumn","component":"Column","children":["inProgressHeader","cardImplementation"],"align":"start"},{"id":"inProgressHeader","component":"Text","text":"In Progress","variant":"h2"},{"id":"cardImplementation","component":"Card","child":"textImplementation"},{"id":"textImplementation","component":"Text","text":"Implementation"},{"id":"doneColumn","component":"Column","children":["doneHeader","cardPlanning"],"align":"start"},{"id":"doneHeader","component":"Text","text":"Done","variant":"h2"},{"id":"cardPlanning","component":"Card","child":"textPlanning"},{"id":"textPlanning","component":"Text","text":"Planning"}]}} diff --git a/dev_tools/catalog_gallery/samples/loginForm.sample b/dev_tools/catalog_gallery/samples/loginForm.sample index c514d4846..d17fc3020 100644 --- a/dev_tools/catalog_gallery/samples/loginForm.sample +++ b/dev_tools/catalog_gallery/samples/loginForm.sample @@ -4,5 +4,5 @@ name: loginForm prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a login form. It should have a "Login" text (variant 'h1'), two text fields for username and password (bound to /login/username and /login/password), a checkbox for "Remember Me" (bound to /login/rememberMe), and a "Sign In" button. The button's action should have a 'event' property with 'name': 'login', and a 'context' containing the username, password, and rememberMe status. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["loginTitle","usernameField","passwordField","rememberMeCheckbox","signInButton"],"justify":"center","align":"center"},{"id":"loginTitle","component":"Text","text":"Login","variant":"h1"},{"id":"usernameField","component":"TextField","label":"Username","value":{"path":"/login/username"}},{"id":"passwordField","component":"TextField","label":"Password","value":{"path":"/login/password"},"variant":"obscured"},{"id":"rememberMeCheckbox","component":"CheckBox","label":"Remember Me","value":{"path":"/login/rememberMe"}},{"id":"signInButton","component":"Button","child":"signInButtonText","action":{"event":{"name":"login","context":{"username":{"path":"/login/username"},"password":{"path":"/login/password"},"rememberMe":{"path":"/login/rememberMe"}}}}},{"id":"signInButtonText","component":"Text","text":"Sign In"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["loginTitle","usernameField","passwordField","rememberMeCheckbox","signInButton"],"justify":"center","align":"center"},{"id":"loginTitle","component":"Text","text":"Login","variant":"h1"},{"id":"usernameField","component":"TextField","label":"Username","value":{"path":"/login/username"}},{"id":"passwordField","component":"TextField","label":"Password","value":{"path":"/login/password"},"variant":"obscured"},{"id":"rememberMeCheckbox","component":"CheckBox","label":"Remember Me","value":{"path":"/login/rememberMe"}},{"id":"signInButton","component":"Button","child":"signInButtonText","action":{"event":{"name":"login","context":{"username":{"path":"/login/username"},"password":{"path":"/login/password"},"rememberMe":{"path":"/login/rememberMe"}}}}},{"id":"signInButtonText","component":"Text","text":"Sign In"}]}} diff --git a/dev_tools/catalog_gallery/samples/musicPlayer.sample b/dev_tools/catalog_gallery/samples/musicPlayer.sample index 05c37c79e..3612a3740 100644 --- a/dev_tools/catalog_gallery/samples/musicPlayer.sample +++ b/dev_tools/catalog_gallery/samples/musicPlayer.sample @@ -4,5 +4,5 @@ name: musicPlayer prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a music player. It should be a 'Card' containing a 'Column'. Inside the column, there's an 'Image' for the album art, a 'Text' for the song title "Bohemian Rhapsody", another 'Text' for the artist "Queen", a 'Slider' labeled "Progress", and a 'Row' with three 'Button' components. Each Button should have a child 'Text' component. The Text components should have the labels "Previous", "Play", and "Next" respectively. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Card","child":"musicPlayerColumn"},{"id":"musicPlayerColumn","component":"Column","children":["albumArtImage","songTitleText","artistText","progressSlider","controlsRow"]},{"id":"albumArtImage","component":"Image","url":"https://upload.wikimedia.org/wikipedia/en/thumb/9/91/Queen_Bohemian_Rhapsody.png/220px-Queen_Bohemian_Rhapsody.png","variant":"mediumFeature"},{"id":"songTitleText","component":"Text","text":"Bohemian Rhapsody","variant":"h4"},{"id":"artistText","component":"Text","text":"Queen","variant":"body"},{"id":"progressSlider","component":"Slider","label":"Progress","min":0,"max":100,"value":50},{"id":"controlsRow","component":"Row","justify":"spaceAround","children":["previousButton","playButton","nextButton"]},{"id":"previousButton","component":"Button","child":"previousButtonText","action":{"event":{"name":"previousSong"}}},{"id":"previousButtonText","component":"Text","text":"Previous"},{"id":"playButton","component":"Button","child":"playButtonText","action":{"event":{"name":"playPause"}}},{"id":"playButtonText","component":"Text","text":"Play"},{"id":"nextButton","component":"Button","child":"nextButtonText","action":{"event":{"name":"nextSong"}}},{"id":"nextButtonText","component":"Text","text":"Next"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Card","child":"musicPlayerColumn"},{"id":"musicPlayerColumn","component":"Column","children":["albumArtImage","songTitleText","artistText","progressSlider","controlsRow"]},{"id":"albumArtImage","component":"Image","url":"https://upload.wikimedia.org/wikipedia/en/thumb/9/91/Queen_Bohemian_Rhapsody.png/220px-Queen_Bohemian_Rhapsody.png","variant":"mediumFeature"},{"id":"songTitleText","component":"Text","text":"Bohemian Rhapsody","variant":"h4"},{"id":"artistText","component":"Text","text":"Queen","variant":"body"},{"id":"progressSlider","component":"Slider","label":"Progress","min":0,"max":100,"value":50},{"id":"controlsRow","component":"Row","justify":"spaceAround","children":["previousButton","playButton","nextButton"]},{"id":"previousButton","component":"Button","child":"previousButtonText","action":{"event":{"name":"previousSong"}}},{"id":"previousButtonText","component":"Text","text":"Previous"},{"id":"playButton","component":"Button","child":"playButtonText","action":{"event":{"name":"playPause"}}},{"id":"playButtonText","component":"Text","text":"Play"},{"id":"nextButton","component":"Button","child":"nextButtonText","action":{"event":{"name":"nextSong"}}},{"id":"nextButtonText","component":"Text","text":"Next"}]}} diff --git a/dev_tools/catalog_gallery/samples/nestedDataBinding.sample b/dev_tools/catalog_gallery/samples/nestedDataBinding.sample index ffcfdafc7..8365ab20f 100644 --- a/dev_tools/catalog_gallery/samples/nestedDataBinding.sample +++ b/dev_tools/catalog_gallery/samples/nestedDataBinding.sample @@ -28,6 +28,6 @@ prompt: | Ensure all referenced component IDs (like 'subtaskList') are explicitly defined in the 'components' list. The component with id 'subtaskList' must effectively exist in the output list. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["dashboardTitle","projectList"]},{"id":"dashboardTitle","component":"Text","variant":"h1","text":"Project Dashboard"},{"id":"projectList","component":"List","direction":"vertical","children":{"componentId":"projectCardTemplate","path":"/projects"}},{"id":"projectCardTemplate","component":"Card","child":"projectCardContent"},{"id":"projectCardContent","component":"Column","children":["projectTitle","taskList"]},{"id":"projectTitle","component":"Text","variant":"h2","text":{"path":"title"}},{"id":"taskList","component":"List","direction":"vertical","children":{"componentId":"taskItemTemplate","path":"tasks"}},{"id":"taskItemTemplate","component":"Column","children":["taskDescription","assigneeRow","subtaskList"]},{"id":"taskDescription","component":"Text","text":{"path":"description"}},{"id":"assigneeRow","component":"Row","children":["assigneeName","assigneeRole"]},{"id":"assigneeName","component":"Text","text":{"path":"assignee/name"}},{"id":"assigneeRole","component":"Text","text":{"path":"assignee/role"}},{"id":"subtaskList","component":"List","direction":"vertical","children":{"componentId":"subtaskItemTemplate","path":"subtasks"}},{"id":"subtaskItemTemplate","component":"Text","text":{"path":"title"}}]}} -{"version":"v0.9","updateDataModel":{"surfaceId":"main","value":{"projects":[{"title":"Website Redesign","tasks":[{"description":"Design mockups for homepage","assignee":{"name":"Alice Smith","role":"UI Designer"},"subtasks":[{"title":"Gather requirements"},{"title":"Sketch wireframes"},{"title":"Create high-fidelity designs"}]},{"description":"Develop user authentication module","assignee":{"name":"Bob Johnson","role":"Backend Developer"},"subtasks":[{"title":"Set up database"},{"title":"Implement login API"},{"title":"Integrate with frontend"}]}]},{"title":"Mobile App Development","tasks":[{"description":"Plan app features","assignee":{"name":"Charlie Brown","role":"Product Manager"},"subtasks":[{"title":"Market research"},{"title":"User stories"}]}]}]}}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["dashboardTitle","projectList"]},{"id":"dashboardTitle","component":"Text","variant":"h1","text":"Project Dashboard"},{"id":"projectList","component":"List","direction":"vertical","children":{"componentId":"projectCardTemplate","path":"/projects"}},{"id":"projectCardTemplate","component":"Card","child":"projectCardContent"},{"id":"projectCardContent","component":"Column","children":["projectTitle","taskList"]},{"id":"projectTitle","component":"Text","variant":"h2","text":{"path":"title"}},{"id":"taskList","component":"List","direction":"vertical","children":{"componentId":"taskItemTemplate","path":"tasks"}},{"id":"taskItemTemplate","component":"Column","children":["taskDescription","assigneeRow","subtaskList"]},{"id":"taskDescription","component":"Text","text":{"path":"description"}},{"id":"assigneeRow","component":"Row","children":["assigneeName","assigneeRole"]},{"id":"assigneeName","component":"Text","text":{"path":"assignee/name"}},{"id":"assigneeRole","component":"Text","text":{"path":"assignee/role"}},{"id":"subtaskList","component":"List","direction":"vertical","children":{"componentId":"subtaskItemTemplate","path":"subtasks"}},{"id":"subtaskItemTemplate","component":"Text","text":{"path":"title"}}]}} +{"version":"v1.0","updateDataModel":{"surfaceId":"main","value":{"projects":[{"title":"Website Redesign","tasks":[{"description":"Design mockups for homepage","assignee":{"name":"Alice Smith","role":"UI Designer"},"subtasks":[{"title":"Gather requirements"},{"title":"Sketch wireframes"},{"title":"Create high-fidelity designs"}]},{"description":"Develop user authentication module","assignee":{"name":"Bob Johnson","role":"Backend Developer"},"subtasks":[{"title":"Set up database"},{"title":"Implement login API"},{"title":"Integrate with frontend"}]}]},{"title":"Mobile App Development","tasks":[{"description":"Plan app features","assignee":{"name":"Charlie Brown","role":"Product Manager"},"subtasks":[{"title":"Market research"},{"title":"User stories"}]}]}]}}} diff --git a/dev_tools/catalog_gallery/samples/nestedLayoutRecursive.sample b/dev_tools/catalog_gallery/samples/nestedLayoutRecursive.sample index ea291384b..7435807c4 100644 --- a/dev_tools/catalog_gallery/samples/nestedLayoutRecursive.sample +++ b/dev_tools/catalog_gallery/samples/nestedLayoutRecursive.sample @@ -13,5 +13,5 @@ prompt: | Use explicit, static components for this structure (no data binding for the list). Level 5 Text should say "Deep content". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Card","child":"column1"},{"id":"column1","component":"Column","children":["row1"]},{"id":"row1","component":"Row","children":["list1"]},{"id":"list1","component":"List","children":["text1"],"direction":"vertical"},{"id":"text1","component":"Text","text":"Deep content"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Card","child":"column1"},{"id":"column1","component":"Column","children":["row1"]},{"id":"row1","component":"Row","children":["list1"]},{"id":"list1","component":"List","children":["text1"],"direction":"vertical"},{"id":"text1","component":"Text","text":"Deep content"}]}} diff --git a/dev_tools/catalog_gallery/samples/newsAggregator.sample b/dev_tools/catalog_gallery/samples/newsAggregator.sample index 0dcf7bc37..f7ee0ed54 100644 --- a/dev_tools/catalog_gallery/samples/newsAggregator.sample +++ b/dev_tools/catalog_gallery/samples/newsAggregator.sample @@ -4,5 +4,5 @@ name: newsAggregator prompt: | Create a news aggregator. The root component should be a 'Column'. Inside this column, place a 'Text' (variant 'h1') "Top Headlines". Below the text, place a 'List' of 'Card's. The 'List' should be a sibling of the 'Text', not a parent. Each card has a 'Column' with an 'Image', a 'Text' (headline), and a 'Text' (summary). Include headlines "Tech Breakthrough" and "Local Sports". Each card should have a 'Button' labeled "Read More". Create these as static components, not data bound. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["headlineText","newsList"]},{"id":"headlineText","component":"Text","text":"Top Headlines","variant":"h1"},{"id":"newsList","component":"List","direction":"vertical","children":["card1","card2"]},{"id":"card1","component":"Card","child":"card1Content"},{"id":"card1Content","component":"Column","children":["image1","headline1","summary1","button1"]},{"id":"image1","component":"Image","url":"https://picsum.photos/id/237/200/100"},{"id":"headline1","component":"Text","text":"Tech Breakthrough"},{"id":"summary1","component":"Text","text":"Scientists announce a major leap forward in AI."},{"id":"button1","component":"Button","child":"button1Label","action":{"event":{"name":"readMore","context":{"articleId":"tech-breakthrough"}}}},{"id":"button1Label","component":"Text","text":"Read More"},{"id":"card2","component":"Card","child":"card2Content"},{"id":"card2Content","component":"Column","children":["image2","headline2","summary2","button2"]},{"id":"image2","component":"Image","url":"https://picsum.photos/id/238/200/100"},{"id":"headline2","component":"Text","text":"Local Sports"},{"id":"summary2","component":"Text","text":"High school team wins championship title."},{"id":"button2","component":"Button","child":"button2Label","action":{"event":{"name":"readMore","context":{"articleId":"local-sports"}}}},{"id":"button2Label","component":"Text","text":"Read More"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["headlineText","newsList"]},{"id":"headlineText","component":"Text","text":"Top Headlines","variant":"h1"},{"id":"newsList","component":"List","direction":"vertical","children":["card1","card2"]},{"id":"card1","component":"Card","child":"card1Content"},{"id":"card1Content","component":"Column","children":["image1","headline1","summary1","button1"]},{"id":"image1","component":"Image","url":"https://picsum.photos/id/237/200/100"},{"id":"headline1","component":"Text","text":"Tech Breakthrough"},{"id":"summary1","component":"Text","text":"Scientists announce a major leap forward in AI."},{"id":"button1","component":"Button","child":"button1Label","action":{"event":{"name":"readMore","context":{"articleId":"tech-breakthrough"}}}},{"id":"button1Label","component":"Text","text":"Read More"},{"id":"card2","component":"Card","child":"card2Content"},{"id":"card2Content","component":"Column","children":["image2","headline2","summary2","button2"]},{"id":"image2","component":"Image","url":"https://picsum.photos/id/238/200/100"},{"id":"headline2","component":"Text","text":"Local Sports"},{"id":"summary2","component":"Text","text":"High school team wins championship title."},{"id":"button2","component":"Button","child":"button2Label","action":{"event":{"name":"readMore","context":{"articleId":"local-sports"}}}},{"id":"button2Label","component":"Text","text":"Read More"}]}} diff --git a/dev_tools/catalog_gallery/samples/notificationCenter.sample b/dev_tools/catalog_gallery/samples/notificationCenter.sample index fbcc17ed1..5196f154b 100644 --- a/dev_tools/catalog_gallery/samples/notificationCenter.sample +++ b/dev_tools/catalog_gallery/samples/notificationCenter.sample @@ -4,5 +4,5 @@ name: notificationCenter prompt: | Create a notification center. It should have a 'Text' (variant 'h1') "Notifications". A 'List' of 'Card's. Include cards for "New message from Sarah" and "Your order has shipped". Each card should have a 'Button' "Dismiss". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["notificationCenterTitle","notificationList"]},{"id":"notificationCenterTitle","component":"Text","text":"Notifications","variant":"h1"},{"id":"notificationList","component":"List","direction":"vertical","children":["messageNotificationCard","orderShippedNotificationCard"]},{"id":"messageNotificationCard","component":"Card","child":"messageCardContent"},{"id":"messageCardContent","component":"Column","children":["messageText","dismissMessageButton"]},{"id":"messageText","component":"Text","text":"New message from Sarah"},{"id":"dismissMessageButton","component":"Button","child":"dismissMessageButtonText","action":{"event":{"name":"dismissNotification","context":{"notificationId":"messageFromSarah"}}}},{"id":"dismissMessageButtonText","component":"Text","text":"Dismiss"},{"id":"orderShippedNotificationCard","component":"Card","child":"orderShippedCardContent"},{"id":"orderShippedCardContent","component":"Column","children":["orderShippedText","dismissOrderButton"]},{"id":"orderShippedText","component":"Text","text":"Your order has shipped"},{"id":"dismissOrderButton","component":"Button","child":"dismissOrderButtonText","action":{"event":{"name":"dismissNotification","context":{"notificationId":"orderShipped"}}}},{"id":"dismissOrderButtonText","component":"Text","text":"Dismiss"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["notificationCenterTitle","notificationList"]},{"id":"notificationCenterTitle","component":"Text","text":"Notifications","variant":"h1"},{"id":"notificationList","component":"List","direction":"vertical","children":["messageNotificationCard","orderShippedNotificationCard"]},{"id":"messageNotificationCard","component":"Card","child":"messageCardContent"},{"id":"messageCardContent","component":"Column","children":["messageText","dismissMessageButton"]},{"id":"messageText","component":"Text","text":"New message from Sarah"},{"id":"dismissMessageButton","component":"Button","child":"dismissMessageButtonText","action":{"event":{"name":"dismissNotification","context":{"notificationId":"messageFromSarah"}}}},{"id":"dismissMessageButtonText","component":"Text","text":"Dismiss"},{"id":"orderShippedNotificationCard","component":"Card","child":"orderShippedCardContent"},{"id":"orderShippedCardContent","component":"Column","children":["orderShippedText","dismissOrderButton"]},{"id":"orderShippedText","component":"Text","text":"Your order has shipped"},{"id":"dismissOrderButton","component":"Button","child":"dismissOrderButtonText","action":{"event":{"name":"dismissNotification","context":{"notificationId":"orderShipped"}}}},{"id":"dismissOrderButtonText","component":"Text","text":"Dismiss"}]}} diff --git a/dev_tools/catalog_gallery/samples/openUrlAction.sample b/dev_tools/catalog_gallery/samples/openUrlAction.sample index 63aa314e5..699d02fc9 100644 --- a/dev_tools/catalog_gallery/samples/openUrlAction.sample +++ b/dev_tools/catalog_gallery/samples/openUrlAction.sample @@ -6,5 +6,5 @@ prompt: | Include a 'Button' labeled "Visit Website". The button's action should be a client-side function call to 'openUrl' with the argument 'url': 'https://a2ui.org'. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["websiteButton"]},{"id":"websiteButton","component":"Button","child":"buttonLabel","action":{"functionCall":{"call":"openUrl","args":{"url":"https://a2ui.org"},"returnType":"void"}}},{"id":"buttonLabel","component":"Text","text":"Visit Website"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["websiteButton"]},{"id":"websiteButton","component":"Button","child":"buttonLabel","action":{"functionCall":{"call":"openUrl","args":{"url":"https://a2ui.org"},"returnType":"void"}}},{"id":"buttonLabel","component":"Text","text":"Visit Website"}]}} diff --git a/dev_tools/catalog_gallery/samples/photoEditor.sample b/dev_tools/catalog_gallery/samples/photoEditor.sample index 3f1c36b06..daf544796 100644 --- a/dev_tools/catalog_gallery/samples/photoEditor.sample +++ b/dev_tools/catalog_gallery/samples/photoEditor.sample @@ -4,6 +4,6 @@ name: photoEditor prompt: | Create a photo editor. It should have a large 'Image' (photo). Below it, a 'Row' of 'Button's (Filters, Crop, Adjust). Below that, a 'Slider' labeled "Intensity" (initialize value to 50). --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["photo","buttonRow","intensitySlider"]},{"id":"photo","component":"Image","url":"https://a2ui.org/img/image_placeholder.png","variant":"largeFeature"},{"id":"buttonRow","component":"Row","justify":"spaceBetween","children":["filtersButton","cropButton","adjustButton"]},{"id":"filtersButton","component":"Button","child":"filtersButtonText","action":{"event":{"name":"filters_clicked"}}},{"id":"filtersButtonText","component":"Text","text":"Filters"},{"id":"cropButton","component":"Button","child":"cropButtonText","action":{"event":{"name":"crop_clicked"}}},{"id":"cropButtonText","component":"Text","text":"Crop"},{"id":"adjustButton","component":"Button","child":"adjustButtonText","action":{"event":{"name":"adjust_clicked"}}},{"id":"adjustButtonText","component":"Text","text":"Adjust"},{"id":"intensitySlider","component":"Slider","label":"Intensity","min":0,"max":100,"value":{"path":"/intensity"}}]}} -{"version":"v0.9","updateDataModel":{"surfaceId":"main","value":{"intensity":50}}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["photo","buttonRow","intensitySlider"]},{"id":"photo","component":"Image","url":"https://a2ui.org/img/image_placeholder.png","variant":"largeFeature"},{"id":"buttonRow","component":"Row","justify":"spaceBetween","children":["filtersButton","cropButton","adjustButton"]},{"id":"filtersButton","component":"Button","child":"filtersButtonText","action":{"event":{"name":"filters_clicked"}}},{"id":"filtersButtonText","component":"Text","text":"Filters"},{"id":"cropButton","component":"Button","child":"cropButtonText","action":{"event":{"name":"crop_clicked"}}},{"id":"cropButtonText","component":"Text","text":"Crop"},{"id":"adjustButton","component":"Button","child":"adjustButtonText","action":{"event":{"name":"adjust_clicked"}}},{"id":"adjustButtonText","component":"Text","text":"Adjust"},{"id":"intensitySlider","component":"Slider","label":"Intensity","min":0,"max":100,"value":{"path":"/intensity"}}]}} +{"version":"v1.0","updateDataModel":{"surfaceId":"main","value":{"intensity":50}}} diff --git a/dev_tools/catalog_gallery/samples/podcastEpisode.sample b/dev_tools/catalog_gallery/samples/podcastEpisode.sample index 439259932..bb7ebf339 100644 --- a/dev_tools/catalog_gallery/samples/podcastEpisode.sample +++ b/dev_tools/catalog_gallery/samples/podcastEpisode.sample @@ -10,5 +10,5 @@ prompt: | - 'Row' with 'Button' (child 'Text' "1x"), 'Button' (child 'Text' "Play/Pause"), 'Button' (child 'Text' "Share"). Create these as static components, not data bound. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["podcastCard"]},{"id":"podcastCard","component":"Card","child":"podcastContentColumn"},{"id":"podcastContentColumn","component":"Column","children":["coverArtImage","episodeTitleText","hostText","progressBarSlider","controlsRow"]},{"id":"coverArtImage","component":"Image","url":"https://example.com/cover_art.jpg","variant":"mediumFeature"},{"id":"episodeTitleText","component":"Text","text":"Episode 42: The Future of AI","variant":"h2"},{"id":"hostText","component":"Text","text":"Host: Jane Smith","variant":"body"},{"id":"progressBarSlider","component":"Slider","label":"Progress","min":0,"max":100,"value":0},{"id":"controlsRow","component":"Row","justify":"spaceBetween","children":["speedButton","playPauseButton","shareButton"]},{"id":"speedButton","component":"Button","child":"speedButtonText","action":{"event":{"name":"changeSpeed"}}},{"id":"speedButtonText","component":"Text","text":"1x"},{"id":"playPauseButton","component":"Button","child":"playPauseButtonText","action":{"event":{"name":"togglePlayPause"}}},{"id":"playPauseButtonText","component":"Text","text":"Play/Pause"},{"id":"shareButton","component":"Button","child":"shareButtonText","action":{"event":{"name":"shareEpisode"}}},{"id":"shareButtonText","component":"Text","text":"Share"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["podcastCard"]},{"id":"podcastCard","component":"Card","child":"podcastContentColumn"},{"id":"podcastContentColumn","component":"Column","children":["coverArtImage","episodeTitleText","hostText","progressBarSlider","controlsRow"]},{"id":"coverArtImage","component":"Image","url":"https://example.com/cover_art.jpg","variant":"mediumFeature"},{"id":"episodeTitleText","component":"Text","text":"Episode 42: The Future of AI","variant":"h2"},{"id":"hostText","component":"Text","text":"Host: Jane Smith","variant":"body"},{"id":"progressBarSlider","component":"Slider","label":"Progress","min":0,"max":100,"value":0},{"id":"controlsRow","component":"Row","justify":"spaceBetween","children":["speedButton","playPauseButton","shareButton"]},{"id":"speedButton","component":"Button","child":"speedButtonText","action":{"event":{"name":"changeSpeed"}}},{"id":"speedButtonText","component":"Text","text":"1x"},{"id":"playPauseButton","component":"Button","child":"playPauseButtonText","action":{"event":{"name":"togglePlayPause"}}},{"id":"playPauseButtonText","component":"Text","text":"Play/Pause"},{"id":"shareButton","component":"Button","child":"shareButtonText","action":{"event":{"name":"shareEpisode"}}},{"id":"shareButtonText","component":"Text","text":"Share"}]}} diff --git a/dev_tools/catalog_gallery/samples/productGallery.sample b/dev_tools/catalog_gallery/samples/productGallery.sample index 5dfc1dfd9..9929cfa27 100644 --- a/dev_tools/catalog_gallery/samples/productGallery.sample +++ b/dev_tools/catalog_gallery/samples/productGallery.sample @@ -4,6 +4,6 @@ name: productGallery prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a product gallery. It should display a list of products from the data model at '/products'. Use a template for the list items. Each item should be a Card containing a Column. The Column should contain an Image (from '/products/item/imageUrl'), a Text component for the product name (from '/products/item/name'), and a Button labeled "Add to Cart". The button's action should have a 'event' with 'name': 'addToCart' and a 'context' with the product ID, for example, 'productId': 'static-id-123' (use this exact literal string). You should create a template component and then a list that uses it. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateDataModel":{"surfaceId":"main","path":"/products","value":{"product1":{"id":"product1","name":"Super Widget","imageUrl":"https://example.com/super_widget.jpg"},"product2":{"id":"product2","name":"Mega Doodad","imageUrl":"https://example.com/mega_doodad.png"},"product3":{"id":"product3","name":"Ultra Gizmo","imageUrl":"https://example.com/ultra_gizmo.jpeg"}}}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["productList"]},{"id":"productList","component":"List","direction":"vertical","children":{"componentId":"productCardTemplate","path":"/products"}},{"id":"productCardTemplate","component":"Card","child":"productCardColumn"},{"id":"productCardColumn","component":"Column","children":["productImage","productNameText","addToCartButton"]},{"id":"productImage","component":"Image","url":{"path":"imageUrl"}},{"id":"productNameText","component":"Text","text":{"path":"name"},"variant":"h5"},{"id":"addToCartButton","component":"Button","child":"addToCartButtonText","action":{"event":{"name":"addToCart","context":{"productId":"static-id-123"}}}},{"id":"addToCartButtonText","component":"Text","text":"Add to Cart"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateDataModel":{"surfaceId":"main","path":"/products","value":{"product1":{"id":"product1","name":"Super Widget","imageUrl":"https://example.com/super_widget.jpg"},"product2":{"id":"product2","name":"Mega Doodad","imageUrl":"https://example.com/mega_doodad.png"},"product3":{"id":"product3","name":"Ultra Gizmo","imageUrl":"https://example.com/ultra_gizmo.jpeg"}}}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["productList"]},{"id":"productList","component":"List","direction":"vertical","children":{"componentId":"productCardTemplate","path":"/products"}},{"id":"productCardTemplate","component":"Card","child":"productCardColumn"},{"id":"productCardColumn","component":"Column","children":["productImage","productNameText","addToCartButton"]},{"id":"productImage","component":"Image","url":{"path":"imageUrl"}},{"id":"productNameText","component":"Text","text":{"path":"name"},"variant":"h5"},{"id":"addToCartButton","component":"Button","child":"addToCartButtonText","action":{"event":{"name":"addToCart","context":{"productId":"static-id-123"}}}},{"id":"addToCartButtonText","component":"Text","text":"Add to Cart"}]}} diff --git a/dev_tools/catalog_gallery/samples/profileEditor.sample b/dev_tools/catalog_gallery/samples/profileEditor.sample index e100b5e23..81927d850 100644 --- a/dev_tools/catalog_gallery/samples/profileEditor.sample +++ b/dev_tools/catalog_gallery/samples/profileEditor.sample @@ -4,5 +4,5 @@ name: profileEditor prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for editing a profile. 'Text' (h1) "Edit Profile". 'Image' (Current Avatar). 'Button' "Change Photo". 'TextField' "Display Name". 'TextField' "Bio" (multiline). 'TextField' "Website". 'Button' "Save Changes". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["editProfileTitle","currentAvatarImage","changePhotoButton","displayNameTextField","bioTextField","websiteTextField","saveChangesButton"]},{"id":"editProfileTitle","component":"Text","text":"Edit Profile","variant":"h1"},{"id":"currentAvatarImage","component":"Image","url":"https://a2ui.org/img/avatar_placeholder.png","variant":"avatar"},{"id":"changePhotoButton","component":"Button","child":"changePhotoText","action":{"event":{"name":"changePhoto"}}},{"id":"changePhotoText","component":"Text","text":"Change Photo"},{"id":"displayNameTextField","component":"TextField","label":"Display Name","value":{"path":"/profile/displayName"}},{"id":"bioTextField","component":"TextField","label":"Bio","value":{"path":"/profile/bio"},"variant":"longText"},{"id":"websiteTextField","component":"TextField","label":"Website","value":{"path":"/profile/website"},"checks":[{"condition":{"call":"regex","args":{"value":{"path":"/profile/website"},"pattern":"^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w \\.-]*)*\\/?$"},"returnType":"boolean"},"message":"Please enter a valid URL."}]},{"id":"saveChangesButton","component":"Button","child":"saveChangesText","variant":"primary","action":{"event":{"name":"saveProfileChanges"}}},{"id":"saveChangesText","component":"Text","text":"Save Changes"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["editProfileTitle","currentAvatarImage","changePhotoButton","displayNameTextField","bioTextField","websiteTextField","saveChangesButton"]},{"id":"editProfileTitle","component":"Text","text":"Edit Profile","variant":"h1"},{"id":"currentAvatarImage","component":"Image","url":"https://a2ui.org/img/avatar_placeholder.png","variant":"avatar"},{"id":"changePhotoButton","component":"Button","child":"changePhotoText","action":{"event":{"name":"changePhoto"}}},{"id":"changePhotoText","component":"Text","text":"Change Photo"},{"id":"displayNameTextField","component":"TextField","label":"Display Name","value":{"path":"/profile/displayName"}},{"id":"bioTextField","component":"TextField","label":"Bio","value":{"path":"/profile/bio"},"variant":"longText"},{"id":"websiteTextField","component":"TextField","label":"Website","value":{"path":"/profile/website"},"checks":[{"condition":{"call":"regex","args":{"value":{"path":"/profile/website"},"pattern":"^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w \\.-]*)*\\/?$"},"returnType":"boolean"},"message":"Please enter a valid URL."}]},{"id":"saveChangesButton","component":"Button","child":"saveChangesText","variant":"primary","action":{"event":{"name":"saveProfileChanges"}}},{"id":"saveChangesText","component":"Text","text":"Save Changes"}]}} diff --git a/dev_tools/catalog_gallery/samples/recipeCard.sample b/dev_tools/catalog_gallery/samples/recipeCard.sample index 341d0ea7a..3c561ab8b 100644 --- a/dev_tools/catalog_gallery/samples/recipeCard.sample +++ b/dev_tools/catalog_gallery/samples/recipeCard.sample @@ -4,5 +4,5 @@ name: recipeCard prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a recipe card. It should have a 'Text' (variant 'h1') for the recipe title, "Classic Lasagna". Below the title, an 'Image' of the lasagna. Then, a 'Row' containing two 'Column's. The first column has a 'Text' (variant 'h2') "Ingredients" and a 'List' of ingredients (use 'Text' components for items: "Pasta", "Cheese", "Sauce"). The second column has a 'Text' (variant 'h2') "Instructions" and a 'List' of step-by-step instructions (use 'Text' components: "Boil pasta", "Layer ingredients", "Bake"). Finally, a 'Button' at the bottom labeled "Watch Video Tutorial". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["recipeTitle","recipeImage","contentRow","watchVideoButton"]},{"id":"recipeTitle","component":"Text","text":"Classic Lasagna","variant":"h1"},{"id":"recipeImage","component":"Image","url":"https://example.com/lasagna.jpg","fit":"cover"},{"id":"contentRow","component":"Row","children":["ingredientsColumn","instructionsColumn"],"justify":"spaceBetween"},{"id":"ingredientsColumn","component":"Column","weight":1,"children":["ingredientsTitle","ingredientsList"]},{"id":"ingredientsTitle","component":"Text","text":"Ingredients","variant":"h2"},{"id":"ingredientsList","component":"List","children":["ingredient1","ingredient2","ingredient3"],"direction":"vertical"},{"id":"ingredient1","component":"Text","text":"Pasta"},{"id":"ingredient2","component":"Text","text":"Cheese"},{"id":"ingredient3","component":"Text","text":"Sauce"},{"id":"instructionsColumn","component":"Column","weight":1,"children":["instructionsTitle","instructionsList"]},{"id":"instructionsTitle","component":"Text","text":"Instructions","variant":"h2"},{"id":"instructionsList","component":"List","children":["instruction1","instruction2","instruction3"],"direction":"vertical"},{"id":"instruction1","component":"Text","text":"Boil pasta"},{"id":"instruction2","component":"Text","text":"Layer ingredients"},{"id":"instruction3","component":"Text","text":"Bake"},{"id":"watchVideoButton","component":"Button","child":"watchVideoText","action":{"functionCall":{"call":"openUrl","args":{"url":"https://example.com/lasagna-tutorial"},"returnType":"void"}}},{"id":"watchVideoText","component":"Text","text":"Watch Video Tutorial"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["recipeTitle","recipeImage","contentRow","watchVideoButton"]},{"id":"recipeTitle","component":"Text","text":"Classic Lasagna","variant":"h1"},{"id":"recipeImage","component":"Image","url":"https://example.com/lasagna.jpg","fit":"cover"},{"id":"contentRow","component":"Row","children":["ingredientsColumn","instructionsColumn"],"justify":"spaceBetween"},{"id":"ingredientsColumn","component":"Column","weight":1,"children":["ingredientsTitle","ingredientsList"]},{"id":"ingredientsTitle","component":"Text","text":"Ingredients","variant":"h2"},{"id":"ingredientsList","component":"List","children":["ingredient1","ingredient2","ingredient3"],"direction":"vertical"},{"id":"ingredient1","component":"Text","text":"Pasta"},{"id":"ingredient2","component":"Text","text":"Cheese"},{"id":"ingredient3","component":"Text","text":"Sauce"},{"id":"instructionsColumn","component":"Column","weight":1,"children":["instructionsTitle","instructionsList"]},{"id":"instructionsTitle","component":"Text","text":"Instructions","variant":"h2"},{"id":"instructionsList","component":"List","children":["instruction1","instruction2","instruction3"],"direction":"vertical"},{"id":"instruction1","component":"Text","text":"Boil pasta"},{"id":"instruction2","component":"Text","text":"Layer ingredients"},{"id":"instruction3","component":"Text","text":"Bake"},{"id":"watchVideoButton","component":"Button","child":"watchVideoText","action":{"functionCall":{"call":"openUrl","args":{"url":"https://example.com/lasagna-tutorial"},"returnType":"void"}}},{"id":"watchVideoText","component":"Text","text":"Watch Video Tutorial"}]}} diff --git a/dev_tools/catalog_gallery/samples/restaurantMenu.sample b/dev_tools/catalog_gallery/samples/restaurantMenu.sample index e8064bdc1..f91141b8c 100644 --- a/dev_tools/catalog_gallery/samples/restaurantMenu.sample +++ b/dev_tools/catalog_gallery/samples/restaurantMenu.sample @@ -7,5 +7,5 @@ prompt: | - "Mains": 'List' containing IDs of separate 'Row' components. Create rows for "Steak - $25", "Pasta - $18". - "Desserts": 'List' containing IDs of separate 'Row' components. Create rows for "Cake - $8", "Pie - $7". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["restaurantTitle","menuTabs"]},{"id":"restaurantTitle","component":"Text","text":"Gourmet Bistro","variant":"h1"},{"id":"menuTabs","component":"Tabs","tabs":[{"title":"Starters","child":"startersList"},{"title":"Mains","child":"mainsList"},{"title":"Desserts","child":"dessertsList"}]},{"id":"startersList","component":"List","direction":"vertical","children":["soupRow","saladRow"]},{"id":"soupRow","component":"Row","children":["soupText","soupPrice"],"justify":"spaceBetween"},{"id":"soupText","component":"Text","text":"Soup"},{"id":"soupPrice","component":"Text","text":"$8"},{"id":"saladRow","component":"Row","children":["saladText","saladPrice"],"justify":"spaceBetween"},{"id":"saladText","component":"Text","text":"Salad"},{"id":"saladPrice","component":"Text","text":"$10"},{"id":"mainsList","component":"List","direction":"vertical","children":["steakRow","pastaRow"]},{"id":"steakRow","component":"Row","children":["steakText","steakPrice"],"justify":"spaceBetween"},{"id":"steakText","component":"Text","text":"Steak"},{"id":"steakPrice","component":"Text","text":"$25"},{"id":"pastaRow","component":"Row","children":["pastaText","pastaPrice"],"justify":"spaceBetween"},{"id":"pastaText","component":"Text","text":"Pasta"},{"id":"pastaPrice","component":"Text","text":"$18"},{"id":"dessertsList","component":"List","direction":"vertical","children":["cakeRow","pieRow"]},{"id":"cakeRow","component":"Row","children":["cakeText","cakePrice"],"justify":"spaceBetween"},{"id":"cakeText","component":"Text","text":"Cake"},{"id":"cakePrice","component":"Text","text":"$8"},{"id":"pieRow","component":"Row","children":["pieText","piePrice"],"justify":"spaceBetween"},{"id":"pieText","component":"Text","text":"Pie"},{"id":"piePrice","component":"Text","text":"$7"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["restaurantTitle","menuTabs"]},{"id":"restaurantTitle","component":"Text","text":"Gourmet Bistro","variant":"h1"},{"id":"menuTabs","component":"Tabs","tabs":[{"title":"Starters","child":"startersList"},{"title":"Mains","child":"mainsList"},{"title":"Desserts","child":"dessertsList"}]},{"id":"startersList","component":"List","direction":"vertical","children":["soupRow","saladRow"]},{"id":"soupRow","component":"Row","children":["soupText","soupPrice"],"justify":"spaceBetween"},{"id":"soupText","component":"Text","text":"Soup"},{"id":"soupPrice","component":"Text","text":"$8"},{"id":"saladRow","component":"Row","children":["saladText","saladPrice"],"justify":"spaceBetween"},{"id":"saladText","component":"Text","text":"Salad"},{"id":"saladPrice","component":"Text","text":"$10"},{"id":"mainsList","component":"List","direction":"vertical","children":["steakRow","pastaRow"]},{"id":"steakRow","component":"Row","children":["steakText","steakPrice"],"justify":"spaceBetween"},{"id":"steakText","component":"Text","text":"Steak"},{"id":"steakPrice","component":"Text","text":"$25"},{"id":"pastaRow","component":"Row","children":["pastaText","pastaPrice"],"justify":"spaceBetween"},{"id":"pastaText","component":"Text","text":"Pasta"},{"id":"pastaPrice","component":"Text","text":"$18"},{"id":"dessertsList","component":"List","direction":"vertical","children":["cakeRow","pieRow"]},{"id":"cakeRow","component":"Row","children":["cakeText","cakePrice"],"justify":"spaceBetween"},{"id":"cakeText","component":"Text","text":"Cake"},{"id":"cakePrice","component":"Text","text":"$8"},{"id":"pieRow","component":"Row","children":["pieText","piePrice"],"justify":"spaceBetween"},{"id":"pieText","component":"Text","text":"Pie"},{"id":"piePrice","component":"Text","text":"$7"}]}} diff --git a/dev_tools/catalog_gallery/samples/settingsPage.sample b/dev_tools/catalog_gallery/samples/settingsPage.sample index b26fc83a0..073820a20 100644 --- a/dev_tools/catalog_gallery/samples/settingsPage.sample +++ b/dev_tools/catalog_gallery/samples/settingsPage.sample @@ -4,6 +4,6 @@ name: settingsPage prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a user settings page. Use a Tabs component with two tabs: "Profile" and "Notifications". The "Profile" tab should contain a simple column with a text field for the user's name. The "Notifications" tab should contain a checkbox for "Enable email notifications". Also, include a Modal component. The modal's trigger should be a button labeled "Delete Account", and its content should be a column with a confirmation text and two buttons: "Confirm Deletion" and "Cancel". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["settingsTabs","deleteAccountModal"]},{"id":"settingsTabs","component":"Tabs","tabs":[{"title":"Profile","child":"profileTabContent"},{"title":"Notifications","child":"notificationsTabContent"}]},{"id":"profileTabContent","component":"Column","children":["userNameTextField"]},{"id":"userNameTextField","component":"TextField","label":"Name","value":{"path":"/user/name"}},{"id":"notificationsTabContent","component":"Column","children":["emailNotificationsCheckbox"]},{"id":"emailNotificationsCheckbox","component":"CheckBox","label":"Enable email notifications","value":{"path":"/user/emailNotificationsEnabled"}},{"id":"deleteAccountModal","component":"Modal","trigger":"deleteAccountButton","content":"deleteConfirmationContent"},{"id":"deleteAccountButton","component":"Button","child":"deleteAccountButtonText","action":{"event":{"name":"openDeleteAccountModal"}}},{"id":"deleteAccountButtonText","component":"Text","text":"Delete Account"},{"id":"deleteConfirmationContent","component":"Column","children":["confirmationText","modalButtonsRow"]},{"id":"confirmationText","component":"Text","text":"Are you sure you want to delete your account? This action cannot be undone."},{"id":"modalButtonsRow","component":"Row","justify":"spaceBetween","children":["confirmDeletionButton","cancelDeletionButton"]},{"id":"confirmDeletionButton","component":"Button","child":"confirmDeletionButtonText","action":{"event":{"name":"confirmAccountDeletion"}}},{"id":"confirmDeletionButtonText","component":"Text","text":"Confirm Deletion"},{"id":"cancelDeletionButton","component":"Button","child":"cancelDeletionButtonText","action":{"event":{"name":"cancelAccountDeletion"}}},{"id":"cancelDeletionButtonText","component":"Text","text":"Cancel"}]}} -{"version":"v0.9","updateDataModel":{"surfaceId":"main","value":{"user":{"name":"John Doe","emailNotificationsEnabled":true}}}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["settingsTabs","deleteAccountModal"]},{"id":"settingsTabs","component":"Tabs","tabs":[{"title":"Profile","child":"profileTabContent"},{"title":"Notifications","child":"notificationsTabContent"}]},{"id":"profileTabContent","component":"Column","children":["userNameTextField"]},{"id":"userNameTextField","component":"TextField","label":"Name","value":{"path":"/user/name"}},{"id":"notificationsTabContent","component":"Column","children":["emailNotificationsCheckbox"]},{"id":"emailNotificationsCheckbox","component":"CheckBox","label":"Enable email notifications","value":{"path":"/user/emailNotificationsEnabled"}},{"id":"deleteAccountModal","component":"Modal","trigger":"deleteAccountButton","content":"deleteConfirmationContent"},{"id":"deleteAccountButton","component":"Button","child":"deleteAccountButtonText","action":{"event":{"name":"openDeleteAccountModal"}}},{"id":"deleteAccountButtonText","component":"Text","text":"Delete Account"},{"id":"deleteConfirmationContent","component":"Column","children":["confirmationText","modalButtonsRow"]},{"id":"confirmationText","component":"Text","text":"Are you sure you want to delete your account? This action cannot be undone."},{"id":"modalButtonsRow","component":"Row","justify":"spaceBetween","children":["confirmDeletionButton","cancelDeletionButton"]},{"id":"confirmDeletionButton","component":"Button","child":"confirmDeletionButtonText","action":{"event":{"name":"confirmAccountDeletion"}}},{"id":"confirmDeletionButtonText","component":"Text","text":"Confirm Deletion"},{"id":"cancelDeletionButton","component":"Button","child":"cancelDeletionButtonText","action":{"event":{"name":"cancelAccountDeletion"}}},{"id":"cancelDeletionButtonText","component":"Text","text":"Cancel"}]}} +{"version":"v1.0","updateDataModel":{"surfaceId":"main","value":{"user":{"name":"John Doe","emailNotificationsEnabled":true}}}} diff --git a/dev_tools/catalog_gallery/samples/simpleCalculator.sample b/dev_tools/catalog_gallery/samples/simpleCalculator.sample index e996c73e5..39b4d2d69 100644 --- a/dev_tools/catalog_gallery/samples/simpleCalculator.sample +++ b/dev_tools/catalog_gallery/samples/simpleCalculator.sample @@ -9,5 +9,5 @@ prompt: | - Row 4: "0", ".", "=", "+" Each button should be a 'Button' component with a child 'Text' component for the label (e.g. '7', '+'). --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Card","child":"calculatorColumn"},{"id":"calculatorColumn","component":"Column","children":["display","buttonsColumn"]},{"id":"display","component":"Text","text":"0","variant":"h3"},{"id":"buttonsColumn","component":"Column","children":["row1","row2","row3","row4"]},{"id":"row1","component":"Row","children":["button7","button8","button9","buttonDivide"],"justify":"spaceBetween"},{"id":"button7","component":"Button","child":"text7","action":{"event":{"name":"digitPress","context":{"digit":"7"}}}},{"id":"text7","component":"Text","text":"7"},{"id":"button8","component":"Button","child":"text8","action":{"event":{"name":"digitPress","context":{"digit":"8"}}}},{"id":"text8","component":"Text","text":"8"},{"id":"button9","component":"Button","child":"text9","action":{"event":{"name":"digitPress","context":{"digit":"9"}}}},{"id":"text9","component":"Text","text":"9"},{"id":"buttonDivide","component":"Button","child":"textDivide","action":{"event":{"name":"operatorPress","context":{"operator":"/"}}}},{"id":"textDivide","component":"Text","text":"/"},{"id":"row2","component":"Row","children":["button4","button5","button6","buttonMultiply"],"justify":"spaceBetween"},{"id":"button4","component":"Button","child":"text4","action":{"event":{"name":"digitPress","context":{"digit":"4"}}}},{"id":"text4","component":"Text","text":"4"},{"id":"button5","component":"Button","child":"text5","action":{"event":{"name":"digitPress","context":{"digit":"5"}}}},{"id":"text5","component":"Text","text":"5"},{"id":"button6","component":"Button","child":"text6","action":{"event":{"name":"digitPress","context":{"digit":"6"}}}},{"id":"text6","component":"Text","text":"6"},{"id":"buttonMultiply","component":"Button","child":"textMultiply","action":{"event":{"name":"operatorPress","context":{"operator":"*"}}}},{"id":"textMultiply","component":"Text","text":"*"},{"id":"row3","component":"Row","children":["button1","button2","button3","buttonMinus"],"justify":"spaceBetween"},{"id":"button1","component":"Button","child":"text1","action":{"event":{"name":"digitPress","context":{"digit":"1"}}}},{"id":"text1","component":"Text","text":"1"},{"id":"button2","component":"Button","child":"text2","action":{"event":{"name":"digitPress","context":{"digit":"2"}}}},{"id":"text2","component":"Text","text":"2"},{"id":"button3","component":"Button","child":"text3","action":{"event":{"name":"digitPress","context":{"digit":"3"}}}},{"id":"text3","component":"Text","text":"3"},{"id":"buttonMinus","component":"Button","child":"textMinus","action":{"event":{"name":"operatorPress","context":{"operator":"-"}}}},{"id":"textMinus","component":"Text","text":"-"},{"id":"row4","component":"Row","children":["button0","buttonDot","buttonEquals","buttonPlus"],"justify":"spaceBetween"},{"id":"button0","component":"Button","child":"text0","action":{"event":{"name":"digitPress","context":{"digit":"0"}}}},{"id":"text0","component":"Text","text":"0"},{"id":"buttonDot","component":"Button","child":"textDot","action":{"event":{"name":"decimalPress"}}},{"id":"textDot","component":"Text","text":"."},{"id":"buttonEquals","component":"Button","child":"textEquals","action":{"event":{"name":"calculate"}}},{"id":"textEquals","component":"Text","text":"="},{"id":"buttonPlus","component":"Button","child":"textPlus","action":{"event":{"name":"operatorPress","context":{"operator":"+"}}}},{"id":"textPlus","component":"Text","text":"+"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Card","child":"calculatorColumn"},{"id":"calculatorColumn","component":"Column","children":["display","buttonsColumn"]},{"id":"display","component":"Text","text":"0","variant":"h3"},{"id":"buttonsColumn","component":"Column","children":["row1","row2","row3","row4"]},{"id":"row1","component":"Row","children":["button7","button8","button9","buttonDivide"],"justify":"spaceBetween"},{"id":"button7","component":"Button","child":"text7","action":{"event":{"name":"digitPress","context":{"digit":"7"}}}},{"id":"text7","component":"Text","text":"7"},{"id":"button8","component":"Button","child":"text8","action":{"event":{"name":"digitPress","context":{"digit":"8"}}}},{"id":"text8","component":"Text","text":"8"},{"id":"button9","component":"Button","child":"text9","action":{"event":{"name":"digitPress","context":{"digit":"9"}}}},{"id":"text9","component":"Text","text":"9"},{"id":"buttonDivide","component":"Button","child":"textDivide","action":{"event":{"name":"operatorPress","context":{"operator":"/"}}}},{"id":"textDivide","component":"Text","text":"/"},{"id":"row2","component":"Row","children":["button4","button5","button6","buttonMultiply"],"justify":"spaceBetween"},{"id":"button4","component":"Button","child":"text4","action":{"event":{"name":"digitPress","context":{"digit":"4"}}}},{"id":"text4","component":"Text","text":"4"},{"id":"button5","component":"Button","child":"text5","action":{"event":{"name":"digitPress","context":{"digit":"5"}}}},{"id":"text5","component":"Text","text":"5"},{"id":"button6","component":"Button","child":"text6","action":{"event":{"name":"digitPress","context":{"digit":"6"}}}},{"id":"text6","component":"Text","text":"6"},{"id":"buttonMultiply","component":"Button","child":"textMultiply","action":{"event":{"name":"operatorPress","context":{"operator":"*"}}}},{"id":"textMultiply","component":"Text","text":"*"},{"id":"row3","component":"Row","children":["button1","button2","button3","buttonMinus"],"justify":"spaceBetween"},{"id":"button1","component":"Button","child":"text1","action":{"event":{"name":"digitPress","context":{"digit":"1"}}}},{"id":"text1","component":"Text","text":"1"},{"id":"button2","component":"Button","child":"text2","action":{"event":{"name":"digitPress","context":{"digit":"2"}}}},{"id":"text2","component":"Text","text":"2"},{"id":"button3","component":"Button","child":"text3","action":{"event":{"name":"digitPress","context":{"digit":"3"}}}},{"id":"text3","component":"Text","text":"3"},{"id":"buttonMinus","component":"Button","child":"textMinus","action":{"event":{"name":"operatorPress","context":{"operator":"-"}}}},{"id":"textMinus","component":"Text","text":"-"},{"id":"row4","component":"Row","children":["button0","buttonDot","buttonEquals","buttonPlus"],"justify":"spaceBetween"},{"id":"button0","component":"Button","child":"text0","action":{"event":{"name":"digitPress","context":{"digit":"0"}}}},{"id":"text0","component":"Text","text":"0"},{"id":"buttonDot","component":"Button","child":"textDot","action":{"event":{"name":"decimalPress"}}},{"id":"textDot","component":"Text","text":"."},{"id":"buttonEquals","component":"Button","child":"textEquals","action":{"event":{"name":"calculate"}}},{"id":"textEquals","component":"Text","text":"="},{"id":"buttonPlus","component":"Button","child":"textPlus","action":{"event":{"name":"operatorPress","context":{"operator":"+"}}}},{"id":"textPlus","component":"Text","text":"+"}]}} diff --git a/dev_tools/catalog_gallery/samples/smartHome.sample b/dev_tools/catalog_gallery/samples/smartHome.sample index d1f039648..e76252a0d 100644 --- a/dev_tools/catalog_gallery/samples/smartHome.sample +++ b/dev_tools/catalog_gallery/samples/smartHome.sample @@ -4,5 +4,5 @@ name: smartHome prompt: | Create a smart home dashboard. It should have a 'Text' (variant 'h1') "Living Room". A 'Grid' of 'Card's. To create the grid, use a 'Column' that contains multiple 'Row's. Each 'Row' should contain 'Card's. Create a row with cards for "Lights" (CheckBox, label "Lights", value true) and "Thermostat" (Slider, label "Thermostat", value 72). Create another row with a card for "Music" (CheckBox, label "Music", value false). Ensure the CheckBox labels are exactly "Lights" and "Music". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["livingRoomTitle","gridColumn"]},{"id":"livingRoomTitle","component":"Text","text":"Living Room","variant":"h1"},{"id":"gridColumn","component":"Column","children":["firstRow","secondRow"]},{"id":"firstRow","component":"Row","children":["lightsCard","thermostatCard"],"justify":"spaceEvenly"},{"id":"lightsCard","component":"Card","child":"lightsCheckbox","weight":1},{"id":"lightsCheckbox","component":"CheckBox","label":"Lights","value":true},{"id":"thermostatCard","component":"Card","child":"thermostatSlider","weight":1},{"id":"thermostatSlider","component":"Slider","label":"Thermostat","min":60,"max":80,"value":72},{"id":"secondRow","component":"Row","children":["musicCard"],"justify":"spaceEvenly"},{"id":"musicCard","component":"Card","child":"musicCheckbox","weight":1},{"id":"musicCheckbox","component":"CheckBox","label":"Music","value":false}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["livingRoomTitle","gridColumn"]},{"id":"livingRoomTitle","component":"Text","text":"Living Room","variant":"h1"},{"id":"gridColumn","component":"Column","children":["firstRow","secondRow"]},{"id":"firstRow","component":"Row","children":["lightsCard","thermostatCard"],"justify":"spaceEvenly"},{"id":"lightsCard","component":"Card","child":"lightsCheckbox","weight":1},{"id":"lightsCheckbox","component":"CheckBox","label":"Lights","value":true},{"id":"thermostatCard","component":"Card","child":"thermostatSlider","weight":1},{"id":"thermostatSlider","component":"Slider","label":"Thermostat","min":60,"max":80,"value":72},{"id":"secondRow","component":"Row","children":["musicCard"],"justify":"spaceEvenly"},{"id":"musicCard","component":"Card","child":"musicCheckbox","weight":1},{"id":"musicCheckbox","component":"CheckBox","label":"Music","value":false}]}} diff --git a/dev_tools/catalog_gallery/samples/socialMediaPost.sample b/dev_tools/catalog_gallery/samples/socialMediaPost.sample index 22799285d..66b0d8556 100644 --- a/dev_tools/catalog_gallery/samples/socialMediaPost.sample +++ b/dev_tools/catalog_gallery/samples/socialMediaPost.sample @@ -4,5 +4,5 @@ name: socialMediaPost prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a social media post. It should be a 'Card' containing a 'Column'. The first item is a 'Row' with an 'Image' (user avatar) and a 'Text' (username "user123"). Below that, a 'Text' component for the post content: "Enjoying the beautiful weather today!". Then, an 'Image' for the main post picture. Finally, a 'Row' with three 'Button's: "Like", "Comment", and "Share". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Card","child":"postColumn"},{"id":"postColumn","component":"Column","children":["userInfoRow","postContentText","postImage","actionsRow"]},{"id":"userInfoRow","component":"Row","children":["userAvatar","usernameText"],"align":"center"},{"id":"userAvatar","component":"Image","url":"https://a2ui.org/img/avatar.png","variant":"avatar"},{"id":"usernameText","component":"Text","text":"user123","variant":"h5"},{"id":"postContentText","component":"Text","text":"Enjoying the beautiful weather today!","variant":"body"},{"id":"postImage","component":"Image","url":"https://a2ui.org/img/placeholder.png","variant":"mediumFeature","fit":"cover"},{"id":"actionsRow","component":"Row","children":["likeButton","commentButton","shareButton"],"justify":"spaceBetween"},{"id":"likeButton","component":"Button","child":"likeButtonText","action":{"event":{"name":"likePost"}},"variant":"borderless"},{"id":"likeButtonText","component":"Text","text":"Like"},{"id":"commentButton","component":"Button","child":"commentButtonText","action":{"event":{"name":"commentPost"}},"variant":"borderless"},{"id":"commentButtonText","component":"Text","text":"Comment"},{"id":"shareButton","component":"Button","child":"shareButtonText","action":{"event":{"name":"sharePost"}},"variant":"borderless"},{"id":"shareButtonText","component":"Text","text":"Share"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Card","child":"postColumn"},{"id":"postColumn","component":"Column","children":["userInfoRow","postContentText","postImage","actionsRow"]},{"id":"userInfoRow","component":"Row","children":["userAvatar","usernameText"],"align":"center"},{"id":"userAvatar","component":"Image","url":"https://a2ui.org/img/avatar.png","variant":"avatar"},{"id":"usernameText","component":"Text","text":"user123","variant":"h5"},{"id":"postContentText","component":"Text","text":"Enjoying the beautiful weather today!","variant":"body"},{"id":"postImage","component":"Image","url":"https://a2ui.org/img/placeholder.png","variant":"mediumFeature","fit":"cover"},{"id":"actionsRow","component":"Row","children":["likeButton","commentButton","shareButton"],"justify":"spaceBetween"},{"id":"likeButton","component":"Button","child":"likeButtonText","action":{"event":{"name":"likePost"}},"variant":"borderless"},{"id":"likeButtonText","component":"Text","text":"Like"},{"id":"commentButton","component":"Button","child":"commentButtonText","action":{"event":{"name":"commentPost"}},"variant":"borderless"},{"id":"commentButtonText","component":"Text","text":"Comment"},{"id":"shareButton","component":"Button","child":"shareButtonText","action":{"event":{"name":"sharePost"}},"variant":"borderless"},{"id":"shareButtonText","component":"Text","text":"Share"}]}} diff --git a/dev_tools/catalog_gallery/samples/standardFunctions.sample b/dev_tools/catalog_gallery/samples/standardFunctions.sample index f75eaa784..efda614b5 100644 --- a/dev_tools/catalog_gallery/samples/standardFunctions.sample +++ b/dev_tools/catalog_gallery/samples/standardFunctions.sample @@ -10,6 +10,6 @@ prompt: | 'one': "One item" 'other': "${/cart/count} items" --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["cartSummaryText"]},{"id":"cartSummaryText","component":"Text","text":{"call":"pluralize","args":{"count":{"path":"/cart/count"},"zero":"No items","one":"One item","other":"${/cart/count} items"},"returnType":"string"}}]}} -{"version":"v0.9","updateDataModel":{"surfaceId":"main","path":"/cart/count","value":2}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["cartSummaryText"]},{"id":"cartSummaryText","component":"Text","text":{"call":"pluralize","args":{"count":{"path":"/cart/count"},"zero":"No items","one":"One item","other":"${/cart/count} items"},"returnType":"string"}}]}} +{"version":"v1.0","updateDataModel":{"surfaceId":"main","path":"/cart/count","value":2}} diff --git a/dev_tools/catalog_gallery/samples/stockWatchlist.sample b/dev_tools/catalog_gallery/samples/stockWatchlist.sample index db538826f..07b8aa583 100644 --- a/dev_tools/catalog_gallery/samples/stockWatchlist.sample +++ b/dev_tools/catalog_gallery/samples/stockWatchlist.sample @@ -7,5 +7,5 @@ prompt: | - Row 2: 'Text' "GOOGL", 'Text' "$2800.00", 'Text' "-0.5%". - Row 3: 'Text' "AMZN", 'Text' "$3400.00", 'Text' "+0.8%". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["marketWatchTitle","stockList"]},{"id":"marketWatchTitle","component":"Text","text":"Market Watch","variant":"h1"},{"id":"stockList","component":"List","children":["aaplRow","googlRow","amznRow"]},{"id":"aaplRow","component":"Row","children":["aaplSymbol","aaplPrice","aaplChange"],"justify":"spaceBetween"},{"id":"aaplSymbol","component":"Text","text":"AAPL"},{"id":"aaplPrice","component":"Text","text":"$150.00"},{"id":"aaplChange","component":"Text","text":"+1.2%"},{"id":"googlRow","component":"Row","children":["googlSymbol","googlPrice","googlChange"],"justify":"spaceBetween"},{"id":"googlSymbol","component":"Text","text":"GOOGL"},{"id":"googlPrice","component":"Text","text":"$2800.00"},{"id":"googlChange","component":"Text","text":"-0.5%"},{"id":"amznRow","component":"Row","children":["amznSymbol","amznPrice","amznChange"],"justify":"spaceBetween"},{"id":"amznSymbol","component":"Text","text":"AMZN"},{"id":"amznPrice","component":"Text","text":"$3400.00"},{"id":"amznChange","component":"Text","text":"+0.8%"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["marketWatchTitle","stockList"]},{"id":"marketWatchTitle","component":"Text","text":"Market Watch","variant":"h1"},{"id":"stockList","component":"List","children":["aaplRow","googlRow","amznRow"]},{"id":"aaplRow","component":"Row","children":["aaplSymbol","aaplPrice","aaplChange"],"justify":"spaceBetween"},{"id":"aaplSymbol","component":"Text","text":"AAPL"},{"id":"aaplPrice","component":"Text","text":"$150.00"},{"id":"aaplChange","component":"Text","text":"+1.2%"},{"id":"googlRow","component":"Row","children":["googlSymbol","googlPrice","googlChange"],"justify":"spaceBetween"},{"id":"googlSymbol","component":"Text","text":"GOOGL"},{"id":"googlPrice","component":"Text","text":"$2800.00"},{"id":"googlChange","component":"Text","text":"-0.5%"},{"id":"amznRow","component":"Row","children":["amznSymbol","amznPrice","amznChange"],"justify":"spaceBetween"},{"id":"amznSymbol","component":"Text","text":"AMZN"},{"id":"amznPrice","component":"Text","text":"$3400.00"},{"id":"amznChange","component":"Text","text":"+0.8%"}]}} diff --git a/dev_tools/catalog_gallery/samples/surveyForm.sample b/dev_tools/catalog_gallery/samples/surveyForm.sample index f5c6d7001..6b31e8cad 100644 --- a/dev_tools/catalog_gallery/samples/surveyForm.sample +++ b/dev_tools/catalog_gallery/samples/surveyForm.sample @@ -4,6 +4,6 @@ name: surveyForm prompt: | Create a customer feedback survey form. It should have a 'Text' (variant 'h1') "Customer Feedback". Then a 'ChoicePicker' (variant 'mutuallyExclusive') with label "How would you rate our service?" and options "Excellent", "Good", "Average", "Poor". Then a 'ChoicePicker' (variant 'multipleSelection') with label "What did you like?" and options "Product Quality", "Price", "Customer Support". Finally, a 'TextField' with the label "Any other comments?" and a 'Button' labeled "Submit Feedback". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateDataModel":{"surfaceId":"main","value":{"serviceRating":"","likedItems":[],"comments":""}}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["customerFeedbackTitle","serviceRatingPicker","likedItemsPicker","commentsTextField","submitButton"]},{"id":"customerFeedbackTitle","component":"Text","text":"Customer Feedback","variant":"h1"},{"id":"serviceRatingPicker","component":"ChoicePicker","label":"How would you rate our service?","variant":"mutuallyExclusive","options":[{"label":"Excellent","value":"excellent"},{"label":"Good","value":"good"},{"label":"Average","value":"average"},{"label":"Poor","value":"poor"}],"value":{"path":"/serviceRating"}},{"id":"likedItemsPicker","component":"ChoicePicker","label":"What did you like?","variant":"multipleSelection","options":[{"label":"Product Quality","value":"product_quality"},{"label":"Price","value":"price"},{"label":"Customer Support","value":"customer_support"}],"value":{"path":"/likedItems"}},{"id":"commentsTextField","component":"TextField","label":"Any other comments?","variant":"longText","value":{"path":"/comments"}},{"id":"submitButton","component":"Button","child":"submitButtonText","action":{"event":{"name":"submitFeedback","context":{"serviceRating":{"path":"/serviceRating"},"likedItems":{"path":"/likedItems"},"comments":{"path":"/comments"}}}}},{"id":"submitButtonText","component":"Text","text":"Submit Feedback"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateDataModel":{"surfaceId":"main","value":{"serviceRating":"","likedItems":[],"comments":""}}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["customerFeedbackTitle","serviceRatingPicker","likedItemsPicker","commentsTextField","submitButton"]},{"id":"customerFeedbackTitle","component":"Text","text":"Customer Feedback","variant":"h1"},{"id":"serviceRatingPicker","component":"ChoicePicker","label":"How would you rate our service?","variant":"mutuallyExclusive","options":[{"label":"Excellent","value":"excellent"},{"label":"Good","value":"good"},{"label":"Average","value":"average"},{"label":"Poor","value":"poor"}],"value":{"path":"/serviceRating"}},{"id":"likedItemsPicker","component":"ChoicePicker","label":"What did you like?","variant":"multipleSelection","options":[{"label":"Product Quality","value":"product_quality"},{"label":"Price","value":"price"},{"label":"Customer Support","value":"customer_support"}],"value":{"path":"/likedItems"}},{"id":"commentsTextField","component":"TextField","label":"Any other comments?","variant":"longText","value":{"path":"/comments"}},{"id":"submitButton","component":"Button","child":"submitButtonText","action":{"event":{"name":"submitFeedback","context":{"serviceRating":{"path":"/serviceRating"},"likedItems":{"path":"/likedItems"},"comments":{"path":"/comments"}}}}},{"id":"submitButtonText","component":"Text","text":"Submit Feedback"}]}} diff --git a/dev_tools/catalog_gallery/samples/travelItinerary.sample b/dev_tools/catalog_gallery/samples/travelItinerary.sample index 0d54d519c..ed912fde3 100644 --- a/dev_tools/catalog_gallery/samples/travelItinerary.sample +++ b/dev_tools/catalog_gallery/samples/travelItinerary.sample @@ -10,5 +10,5 @@ prompt: | - The third 'Card' (Day 3) should contain a 'Text' (variant 'h2') "Day 3: Art & Departure", and a 'List' of activities: "Visit Musée d'Orsay", "Explore Montmartre", "Depart from CDG". Each activity in the inner lists should be a 'Row' containing a 'CheckBox' (to mark as complete, with an empty label '') and a 'Text' component with the activity description. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["parisAdventureTitle","itineraryList"]},{"id":"parisAdventureTitle","component":"Text","text":"Paris Adventure","variant":"h1"},{"id":"itineraryList","component":"List","children":["day1Card","day2Card","day3Card"],"direction":"vertical"},{"id":"day1Card","component":"Card","child":"day1Content"},{"id":"day1Content","component":"Column","children":["day1Title","day1Activities"]},{"id":"day1Title","component":"Text","text":"Day 1: Arrival & Eiffel Tower","variant":"h2"},{"id":"day1Activities","component":"List","children":["day1Activity1","day1Activity2","day1Activity3"],"direction":"vertical"},{"id":"day1Activity1","component":"Row","children":["day1Checkbox1","day1Text1"]},{"id":"day1Checkbox1","component":"CheckBox","label":"","value":false},{"id":"day1Text1","component":"Text","text":"Check into hotel"},{"id":"day1Activity2","component":"Row","children":["day1Checkbox2","day1Text2"]},{"id":"day1Checkbox2","component":"CheckBox","label":"","value":false},{"id":"day1Text2","component":"Text","text":"Lunch at a cafe"},{"id":"day1Activity3","component":"Row","children":["day1Checkbox3","day1Text3"]},{"id":"day1Checkbox3","component":"CheckBox","label":"","value":false},{"id":"day1Text3","component":"Text","text":"Visit the Eiffel Tower"},{"id":"day2Card","component":"Card","child":"day2Content"},{"id":"day2Content","component":"Column","children":["day2Title","day2Activities"]},{"id":"day2Title","component":"Text","text":"Day 2: Museums & Culture","variant":"h2"},{"id":"day2Activities","component":"List","children":["day2Activity1","day2Activity2","day2Activity3"],"direction":"vertical"},{"id":"day2Activity1","component":"Row","children":["day2Checkbox1","day2Text1"]},{"id":"day2Checkbox1","component":"CheckBox","label":"","value":false},{"id":"day2Text1","component":"Text","text":"Visit the Louvre Museum"},{"id":"day2Activity2","component":"Row","children":["day2Checkbox2","day2Text2"]},{"id":"day2Checkbox2","component":"CheckBox","label":"","value":false},{"id":"day2Text2","component":"Text","text":"Walk through Tuileries Garden"},{"id":"day2Activity3","component":"Row","children":["day2Checkbox3","day2Text3"]},{"id":"day2Checkbox3","component":"CheckBox","label":"","value":false},{"id":"day2Text3","component":"Text","text":"See the Arc de Triomphe"},{"id":"day3Card","component":"Card","child":"day3Content"},{"id":"day3Content","component":"Column","children":["day3Title","day3Activities"]},{"id":"day3Title","component":"Text","text":"Day 3: Art & Departure","variant":"h2"},{"id":"day3Activities","component":"List","children":["day3Activity1","day3Activity2","day3Activity3"],"direction":"vertical"},{"id":"day3Activity1","component":"Row","children":["day3Checkbox1","day3Text1"]},{"id":"day3Checkbox1","component":"CheckBox","label":"","value":false},{"id":"day3Text1","component":"Text","text":"Visit Musée d'Orsay"},{"id":"day3Activity2","component":"Row","children":["day3Checkbox2","day3Text2"]},{"id":"day3Checkbox2","component":"CheckBox","label":"","value":false},{"id":"day3Text2","component":"Text","text":"Explore Montmartre"},{"id":"day3Activity3","component":"Row","children":["day3Checkbox3","day3Text3"]},{"id":"day3Checkbox3","component":"CheckBox","label":"","value":false},{"id":"day3Text3","component":"Text","text":"Depart from CDG"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["parisAdventureTitle","itineraryList"]},{"id":"parisAdventureTitle","component":"Text","text":"Paris Adventure","variant":"h1"},{"id":"itineraryList","component":"List","children":["day1Card","day2Card","day3Card"],"direction":"vertical"},{"id":"day1Card","component":"Card","child":"day1Content"},{"id":"day1Content","component":"Column","children":["day1Title","day1Activities"]},{"id":"day1Title","component":"Text","text":"Day 1: Arrival & Eiffel Tower","variant":"h2"},{"id":"day1Activities","component":"List","children":["day1Activity1","day1Activity2","day1Activity3"],"direction":"vertical"},{"id":"day1Activity1","component":"Row","children":["day1Checkbox1","day1Text1"]},{"id":"day1Checkbox1","component":"CheckBox","label":"","value":false},{"id":"day1Text1","component":"Text","text":"Check into hotel"},{"id":"day1Activity2","component":"Row","children":["day1Checkbox2","day1Text2"]},{"id":"day1Checkbox2","component":"CheckBox","label":"","value":false},{"id":"day1Text2","component":"Text","text":"Lunch at a cafe"},{"id":"day1Activity3","component":"Row","children":["day1Checkbox3","day1Text3"]},{"id":"day1Checkbox3","component":"CheckBox","label":"","value":false},{"id":"day1Text3","component":"Text","text":"Visit the Eiffel Tower"},{"id":"day2Card","component":"Card","child":"day2Content"},{"id":"day2Content","component":"Column","children":["day2Title","day2Activities"]},{"id":"day2Title","component":"Text","text":"Day 2: Museums & Culture","variant":"h2"},{"id":"day2Activities","component":"List","children":["day2Activity1","day2Activity2","day2Activity3"],"direction":"vertical"},{"id":"day2Activity1","component":"Row","children":["day2Checkbox1","day2Text1"]},{"id":"day2Checkbox1","component":"CheckBox","label":"","value":false},{"id":"day2Text1","component":"Text","text":"Visit the Louvre Museum"},{"id":"day2Activity2","component":"Row","children":["day2Checkbox2","day2Text2"]},{"id":"day2Checkbox2","component":"CheckBox","label":"","value":false},{"id":"day2Text2","component":"Text","text":"Walk through Tuileries Garden"},{"id":"day2Activity3","component":"Row","children":["day2Checkbox3","day2Text3"]},{"id":"day2Checkbox3","component":"CheckBox","label":"","value":false},{"id":"day2Text3","component":"Text","text":"See the Arc de Triomphe"},{"id":"day3Card","component":"Card","child":"day3Content"},{"id":"day3Content","component":"Column","children":["day3Title","day3Activities"]},{"id":"day3Title","component":"Text","text":"Day 3: Art & Departure","variant":"h2"},{"id":"day3Activities","component":"List","children":["day3Activity1","day3Activity2","day3Activity3"],"direction":"vertical"},{"id":"day3Activity1","component":"Row","children":["day3Checkbox1","day3Text1"]},{"id":"day3Checkbox1","component":"CheckBox","label":"","value":false},{"id":"day3Text1","component":"Text","text":"Visit Musée d'Orsay"},{"id":"day3Activity2","component":"Row","children":["day3Checkbox2","day3Text2"]},{"id":"day3Checkbox2","component":"CheckBox","label":"","value":false},{"id":"day3Text2","component":"Text","text":"Explore Montmartre"},{"id":"day3Activity3","component":"Row","children":["day3Checkbox3","day3Text3"]},{"id":"day3Checkbox3","component":"CheckBox","label":"","value":false},{"id":"day3Text3","component":"Text","text":"Depart from CDG"}]}} diff --git a/dev_tools/catalog_gallery/samples/triviaQuiz.sample b/dev_tools/catalog_gallery/samples/triviaQuiz.sample index 337148232..a875f1ab9 100644 --- a/dev_tools/catalog_gallery/samples/triviaQuiz.sample +++ b/dev_tools/catalog_gallery/samples/triviaQuiz.sample @@ -4,6 +4,6 @@ name: triviaQuiz prompt: | Create a trivia quiz. It should have a 'Text' (variant 'h1') "Question 1". A 'Text' "What is the capital of France?". A 'ChoicePicker' (variant 'mutuallyExclusive') for answers (options: "Paris", "London", "Berlin", "Madrid"). A 'Button' "Submit Answer". --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["question1Title","question1Text","answerChoicePicker","submitButton"]},{"id":"question1Title","component":"Text","variant":"h1","text":"Question 1"},{"id":"question1Text","component":"Text","text":"What is the capital of France?"},{"id":"answerChoicePicker","component":"ChoicePicker","label":"Select your answer","variant":"mutuallyExclusive","options":[{"label":"Paris","value":"Paris"},{"label":"London","value":"London"},{"label":"Berlin","value":"Berlin"},{"label":"Madrid","value":"Madrid"}],"value":{"path":"/selectedAnswer"}},{"id":"submitButton","component":"Button","child":"submitButtonText","action":{"event":{"name":"submitAnswer","context":{"answer":{"path":"/selectedAnswer"}}}}},{"id":"submitButtonText","component":"Text","text":"Submit Answer"}]}} -{"version":"v0.9","updateDataModel":{"surfaceId":"main","path":"/","value":{"selectedAnswer":[]}}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["question1Title","question1Text","answerChoicePicker","submitButton"]},{"id":"question1Title","component":"Text","variant":"h1","text":"Question 1"},{"id":"question1Text","component":"Text","text":"What is the capital of France?"},{"id":"answerChoicePicker","component":"ChoicePicker","label":"Select your answer","variant":"mutuallyExclusive","options":[{"label":"Paris","value":"Paris"},{"label":"London","value":"London"},{"label":"Berlin","value":"Berlin"},{"label":"Madrid","value":"Madrid"}],"value":{"path":"/selectedAnswer"}},{"id":"submitButton","component":"Button","child":"submitButtonText","action":{"event":{"name":"submitAnswer","context":{"answer":{"path":"/selectedAnswer"}}}}},{"id":"submitButtonText","component":"Text","text":"Submit Answer"}]}} +{"version":"v1.0","updateDataModel":{"surfaceId":"main","path":"/","value":{"selectedAnswer":[]}}} diff --git a/dev_tools/catalog_gallery/samples/videoCallInterface.sample b/dev_tools/catalog_gallery/samples/videoCallInterface.sample index 13b17c396..b13c26d17 100644 --- a/dev_tools/catalog_gallery/samples/videoCallInterface.sample +++ b/dev_tools/catalog_gallery/samples/videoCallInterface.sample @@ -4,5 +4,5 @@ name: videoCallInterface prompt: | Create a video call interface. It should have a 'Text' (variant 'h1') "Video Call". A 'Video' component with a valid placeholder URL (e.g. 'https://example.com/video.mp4'). Below that, a 'Row' with three 'Button's, each with a child 'Text' component with the text "Mute", "Camera", and "End Call" respectively. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["videoCallTitle","videoDisplay","callControls"],"justify":"spaceBetween","align":"center"},{"id":"videoCallTitle","component":"Text","text":"Video Call","variant":"h1"},{"id":"videoDisplay","component":"Video","url":"https://example.com/video.mp4"},{"id":"callControls","component":"Row","children":["muteButton","cameraButton","endCallButton"],"justify":"spaceEvenly","align":"center"},{"id":"muteButton","component":"Button","child":"muteButtonText","action":{"event":{"name":"muteToggle"}}},{"id":"muteButtonText","component":"Text","text":"Mute"},{"id":"cameraButton","component":"Button","child":"cameraButtonText","action":{"event":{"name":"cameraToggle"}}},{"id":"cameraButtonText","component":"Text","text":"Camera"},{"id":"endCallButton","component":"Button","child":"endCallButtonText","action":{"event":{"name":"endCall"}}},{"id":"endCallButtonText","component":"Text","text":"End Call"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["videoCallTitle","videoDisplay","callControls"],"justify":"spaceBetween","align":"center"},{"id":"videoCallTitle","component":"Text","text":"Video Call","variant":"h1"},{"id":"videoDisplay","component":"Video","url":"https://example.com/video.mp4"},{"id":"callControls","component":"Row","children":["muteButton","cameraButton","endCallButton"],"justify":"spaceEvenly","align":"center"},{"id":"muteButton","component":"Button","child":"muteButtonText","action":{"event":{"name":"muteToggle"}}},{"id":"muteButtonText","component":"Text","text":"Mute"},{"id":"cameraButton","component":"Button","child":"cameraButtonText","action":{"event":{"name":"cameraToggle"}}},{"id":"cameraButtonText","component":"Text","text":"Camera"},{"id":"endCallButton","component":"Button","child":"endCallButtonText","action":{"event":{"name":"endCall"}}},{"id":"endCallButtonText","component":"Text","text":"End Call"}]}} diff --git a/dev_tools/catalog_gallery/samples/videoPlayer.sample b/dev_tools/catalog_gallery/samples/videoPlayer.sample index b706817bd..9f6d0b55d 100644 --- a/dev_tools/catalog_gallery/samples/videoPlayer.sample +++ b/dev_tools/catalog_gallery/samples/videoPlayer.sample @@ -4,5 +4,5 @@ name: videoPlayer prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a Video component that plays a short video clip. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["videoComponent"]},{"id":"videoComponent","component":"Video","url":"https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["videoComponent"]},{"id":"videoComponent","component":"Video","url":"https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4"}]}} diff --git a/dev_tools/catalog_gallery/samples/weatherForecast.sample b/dev_tools/catalog_gallery/samples/weatherForecast.sample index 6f59f969f..214bd79c5 100644 --- a/dev_tools/catalog_gallery/samples/weatherForecast.sample +++ b/dev_tools/catalog_gallery/samples/weatherForecast.sample @@ -4,5 +4,5 @@ name: weatherForecast prompt: | Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a weather forecast UI. It should have a 'Text' (variant 'h1') with the city name, "New York". Below it, a 'Row' with the current temperature as a 'Text' component ("68°F") and an 'Image' for the weather icon (e.g., a sun). Below that, a 'Divider'. Then, a 'List' component to display the 5-day forecast. Each item in the list should be a 'Row' with the day, an icon, and high/low temperatures. --- -{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}} -{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["cityName","currentWeatherRow","forecastDivider","forecastList"]},{"id":"cityName","component":"Text","variant":"h1","text":"New York"},{"id":"currentWeatherRow","component":"Row","align":"center","children":["currentTemperature","weatherIcon"]},{"id":"currentTemperature","component":"Text","text":"68°F"},{"id":"weatherIcon","component":"Image","url":"https://a2ui.org/img/sun.png","variant":"icon"},{"id":"forecastDivider","component":"Divider","axis":"horizontal"},{"id":"forecastList","component":"List","direction":"vertical","children":["day1Forecast","day2Forecast","day3Forecast","day4Forecast","day5Forecast"]},{"id":"day1Forecast","component":"Row","align":"center","justify":"spaceBetween","children":["day1Text","day1Icon","day1Temp"]},{"id":"day1Text","component":"Text","text":"Mon"},{"id":"day1Icon","component":"Icon","name":"cloud"},{"id":"day1Temp","component":"Text","text":"H:72° L:58°"},{"id":"day2Forecast","component":"Row","align":"center","justify":"spaceBetween","children":["day2Text","day2Icon","day2Temp"]},{"id":"day2Text","component":"Text","text":"Tue"},{"id":"day2Icon","component":"Icon","name":"wbSunny"},{"id":"day2Temp","component":"Text","text":"H:75° L:60°"},{"id":"day3Forecast","component":"Row","align":"center","justify":"spaceBetween","children":["day3Text","day3Icon","day3Temp"]},{"id":"day3Text","component":"Text","text":"Wed"},{"id":"day3Icon","component":"Icon","name":"rainy"},{"id":"day3Temp","component":"Text","text":"H:65° L:55°"},{"id":"day4Forecast","component":"Row","align":"center","justify":"spaceBetween","children":["day4Text","day4Icon","day4Temp"]},{"id":"day4Text","component":"Text","text":"Thu"},{"id":"day4Icon","component":"Icon","name":"cloud"},{"id":"day4Temp","component":"Text","text":"H:68° L:57°"},{"id":"day5Forecast","component":"Row","align":"center","justify":"spaceBetween","children":["day5Text","day5Icon","day5Temp"]},{"id":"day5Text","component":"Text","text":"Fri"},{"id":"day5Icon","component":"Icon","name":"wbSunny"},{"id":"day5Temp","component":"Text","text":"H:70° L:62°"}]}} +{"version":"v1.0","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} +{"version":"v1.0","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["cityName","currentWeatherRow","forecastDivider","forecastList"]},{"id":"cityName","component":"Text","variant":"h1","text":"New York"},{"id":"currentWeatherRow","component":"Row","align":"center","children":["currentTemperature","weatherIcon"]},{"id":"currentTemperature","component":"Text","text":"68°F"},{"id":"weatherIcon","component":"Image","url":"https://a2ui.org/img/sun.png","variant":"icon"},{"id":"forecastDivider","component":"Divider","axis":"horizontal"},{"id":"forecastList","component":"List","direction":"vertical","children":["day1Forecast","day2Forecast","day3Forecast","day4Forecast","day5Forecast"]},{"id":"day1Forecast","component":"Row","align":"center","justify":"spaceBetween","children":["day1Text","day1Icon","day1Temp"]},{"id":"day1Text","component":"Text","text":"Mon"},{"id":"day1Icon","component":"Icon","name":"cloud"},{"id":"day1Temp","component":"Text","text":"H:72° L:58°"},{"id":"day2Forecast","component":"Row","align":"center","justify":"spaceBetween","children":["day2Text","day2Icon","day2Temp"]},{"id":"day2Text","component":"Text","text":"Tue"},{"id":"day2Icon","component":"Icon","name":"wbSunny"},{"id":"day2Temp","component":"Text","text":"H:75° L:60°"},{"id":"day3Forecast","component":"Row","align":"center","justify":"spaceBetween","children":["day3Text","day3Icon","day3Temp"]},{"id":"day3Text","component":"Text","text":"Wed"},{"id":"day3Icon","component":"Icon","name":"rainy"},{"id":"day3Temp","component":"Text","text":"H:65° L:55°"},{"id":"day4Forecast","component":"Row","align":"center","justify":"spaceBetween","children":["day4Text","day4Icon","day4Temp"]},{"id":"day4Text","component":"Text","text":"Thu"},{"id":"day4Icon","component":"Icon","name":"cloud"},{"id":"day4Temp","component":"Text","text":"H:68° L:57°"},{"id":"day5Forecast","component":"Row","align":"center","justify":"spaceBetween","children":["day5Text","day5Icon","day5Temp"]},{"id":"day5Text","component":"Text","text":"Fri"},{"id":"day5Icon","component":"Icon","name":"wbSunny"},{"id":"day5Temp","component":"Text","text":"H:70° L:62°"}]}} diff --git a/dev_tools/catalog_gallery/test/layout_test.dart b/dev_tools/catalog_gallery/test/layout_test.dart index 911fedebf..91721ee1d 100644 --- a/dev_tools/catalog_gallery/test/layout_test.dart +++ b/dev_tools/catalog_gallery/test/layout_test.dart @@ -48,7 +48,7 @@ void main() { messageToProcess = core.CreateSurfaceMessage( surfaceId: message.surfaceId, catalogId: basicCatalogId, - theme: message.theme, + surfaceProperties: message.surfaceProperties, sendDataModel: message.sendDataModel, ); } diff --git a/dev_tools/catalog_gallery/test/sample_parser_test.dart b/dev_tools/catalog_gallery/test/sample_parser_test.dart index 22c83e577..07398510f 100644 --- a/dev_tools/catalog_gallery/test/sample_parser_test.dart +++ b/dev_tools/catalog_gallery/test/sample_parser_test.dart @@ -12,8 +12,8 @@ void main() { name: Test Sample description: A test description --- -{"version": "v0.9", "updateComponents": {"surfaceId": "default", "components": [{"id": "text1", "component": "Text", "text": "Hello"}]}} -{"version": "v0.9", "createSurface": {"surfaceId": "default", "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json"}} +{"version": "v1.0", "updateComponents": {"surfaceId": "default", "components": [{"id": "text1", "component": "Text", "text": "Hello"}]}} +{"version": "v1.0", "createSurface": {"surfaceId": "default", "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}} '''; final Sample sample = SampleParser.parseString(sampleContent); @@ -43,7 +43,7 @@ description: A test description name: Frontmatter Sample description: A description --- -{"version": "v0.9", "createSurface": {"surfaceId": "default", "catalogId": "test"}} +{"version": "v1.0", "createSurface": {"surfaceId": "default", "catalogId": "test"}} '''; final Sample sample = SampleParser.parseString(sampleContent); expect(sample.name, 'Frontmatter Sample'); @@ -56,7 +56,7 @@ description: A description const sampleContent = ''' --- --- -{"version": "v0.9", "createSurface": {"surfaceId": "default", "catalogId": "test"}} +{"version": "v1.0", "createSurface": {"surfaceId": "default", "catalogId": "test"}} '''; final Sample sample = SampleParser.parseString(sampleContent); expect(sample.name, 'Untitled Sample'); diff --git a/dev_tools/composer/lib/surface_utils.dart b/dev_tools/composer/lib/surface_utils.dart index c0bc9d7ad..781295700 100644 --- a/dev_tools/composer/lib/surface_utils.dart +++ b/dev_tools/composer/lib/surface_utils.dart @@ -12,7 +12,7 @@ import 'sample_parser.dart'; final _logger = Logger('SurfaceUtils'); -const kProtocolVersion = 'v0.9'; +const kProtocolVersion = 'v1.0'; Map> mergeComponentsById( List components, [ diff --git a/docs/contributing/design.md b/docs/contributing/design.md index 7b1ccffcf..3966c65e5 100644 --- a/docs/contributing/design.md +++ b/docs/contributing/design.md @@ -578,7 +578,7 @@ Measure the "A2UI Compliance Rate" of different models when given standard promp * Capture the output stream. 3. **Validation Metrics:** * **Syntax Validity:** Is the output valid JSON? - * **Protocol Compliance:** Does it adhere to the A2UI schema (correct message types, `version: "v0.9"`)? + * **Protocol Compliance:** Does it adhere to the A2UI schema (correct message types, `version: "v1.0"`)? * **Logic Correctness:** Does the generated UI match the intent? (e.g., does the login form actually have a password field?) * **Round-Trip Validity:** Can the generated output be successfully parsed by `A2uiMessage.fromJson` without throwing? diff --git a/docs/usage/migration/migration_a2ui_v0.9_to_v1.0.md b/docs/usage/migration/migration_a2ui_v0.9_to_v1.0.md new file mode 100644 index 000000000..96602aa24 --- /dev/null +++ b/docs/usage/migration/migration_a2ui_v0.9_to_v1.0.md @@ -0,0 +1,108 @@ +# Migration Guide: A2UI v0.9 to v1.0 + +The packages in this repository now implement +[A2UI protocol v1.0](https://a2ui.org/specification/v1.0-evolution-guide/) +instead of v0.9/v0.9.1. Apps that only use the default AI/transport flow and +the basic catalog widgets keep working, but any server, agent prompt, or test +fixture that produces or consumes raw A2UI JSON must be updated. + +## Wire format changes + +### Version envelopes + +Every streamed JSON envelope must use `"version": "v1.0"`. Messages with any +other version are rejected with a validation error. + +### `createSurface` + +- The `theme` field is renamed to `surfaceProperties`, and `primaryColor` is + removed. Surface properties (e.g. `agentDisplayName`, `iconUrl`) are + validated against the catalog's `surfaceProperties` schema. +- Initial `components` and `dataModel` can now be passed directly inside the + `createSurface` payload, so an entire UI can be created in one message. +- `surfaceId` must be unique for the renderer's lifetime; re-creating an + existing surface without deleting it first is an error. + +```json +// Before (v0.9) +{"version": "v0.9", "createSurface": {"surfaceId": "s", "catalogId": "c", + "theme": {"primaryColor": "#FF0000"}}} + +// After (v1.0) +{"version": "v1.0", "createSurface": {"surfaceId": "s", "catalogId": "c", + "surfaceProperties": {"agentDisplayName": "My Agent"}, + "components": [{"id": "root", "component": "Text", "text": "Hi"}], + "dataModel": {"user": {"name": "Alice"}}}} +``` + +### `updateDataModel` + +Deletion is now explicit: set the path's `value` to `null` to delete the key +at that path. Omitting keys no longer indicates deletion. +`UpdateDataModelMessage.toJson()` serializes `"value": null` accordingly. + +### Function calls + +Wire-level `FunctionCall` objects no longer carry `returnType` (or +`callableFrom`); both are static metadata in catalog function definitions and +are enforced at runtime. + +## New messages + +### Server → client + +- `callFunction` (with a top-level `functionCallId` and optional + `wantResponse`) executes a catalog function on the client. Functions must be + registered with `callableFrom` of `remoteOnly` or `clientOrRemote`; + otherwise the client replies with an `INVALID_FUNCTION_CALL` error. +- `actionResponse` (with a top-level `actionId`) answers a client action that + set `wantResponse: true`, carrying exactly one of `value` or `error`. The + client writes `value` into the data model at the action's `responsePath`. + +### Client → server + +- `functionResponse` returns the result of a `callFunction` invocation, + echoing `functionCallId` and `call`. +- `action` messages may carry `wantResponse` and a generated `actionId`. +- `error` messages may carry `functionCallId` instead of `surfaceId` (exactly + one of the two) when reporting function execution failures. + +## Catalog changes + +- The `functions` property is a map keyed by function name (previously an + array). Each definition validates the wire `FunctionCall` and carries + static `returnType` and `callableFrom` metadata. +- `$defs/theme` is replaced by `$defs/surfaceProperties`; inline catalogs also + advertise `$defs/anyComponent` and `$defs/anyFunction`. +- An optional `instructions` field embeds Markdown design guidelines directly + in the catalog (genui serializes `Catalog.systemPromptFragments` there). +- Catalog entity names (components, functions) must conform to UAX #31 + identifier rules; invalid names throw at catalog construction. +- The basic catalog id is now + `https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json` + (`basicCatalogId`). + +## Component and evaluation changes + +- `TextField` supports `placeholder`, `Slider` supports `steps`, and `Video` + supports `posterUrl`. +- The `@index` built-in function returns the 0-based iteration index inside + template instantiation loops (optionally shifted by an `offset` argument). + The `@` prefix is reserved for system functions; `@index` outside a template + loop is an evaluation error. + +## Dart API renames + +| Before | After | +| --- | --- | +| `CreateSurfaceMessage.theme` | `CreateSurfaceMessage.surfaceProperties` | +| `SurfaceModel.theme` | `SurfaceModel.surfaceProperties` | +| `SurfaceDefinition.theme` | `SurfaceDefinition.surfaceProperties` | +| `core.Catalog.themeSchema` | `core.Catalog.surfacePropertiesSchema` | + +## Transport metadata + +- The A2A extension URI is now `https://a2ui.org/a2a-extension/a2ui/v1.0`. +- Client capabilities and client data-model envelopes are namespaced under + `v1.0` (previously `v0.9`). +- The standardized MIME type for A2UI payloads is `application/a2ui+json`. diff --git a/examples/simple_chat/integration_test/samples/sample_1_hello.json b/examples/simple_chat/integration_test/samples/sample_1_hello.json index 68168ce1d..d4eee3384 100644 --- a/examples/simple_chat/integration_test/samples/sample_1_hello.json +++ b/examples/simple_chat/integration_test/samples/sample_1_hello.json @@ -1,13 +1,13 @@ [ { - "version": "v0.9", + "version": "v1.0", "createSurface": { "surfaceId": "sample_1_hello", - "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json" + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json" } }, { - "version": "v0.9", + "version": "v1.0", "updateComponents": { "surfaceId": "sample_1_hello", "components": [ diff --git a/examples/simple_chat/integration_test/samples/sample_2_button.json b/examples/simple_chat/integration_test/samples/sample_2_button.json index c0b0b01cd..91ea7ed83 100644 --- a/examples/simple_chat/integration_test/samples/sample_2_button.json +++ b/examples/simple_chat/integration_test/samples/sample_2_button.json @@ -1,13 +1,13 @@ [ { - "version": "v0.9", + "version": "v1.0", "createSurface": { "surfaceId": "sample_2_button", - "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json" + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json" } }, { - "version": "v0.9", + "version": "v1.0", "updateComponents": { "surfaceId": "sample_2_button", "components": [ diff --git a/examples/simple_chat/integration_test/samples/sample_4_form.json b/examples/simple_chat/integration_test/samples/sample_4_form.json index f4687de47..34f4acff4 100644 --- a/examples/simple_chat/integration_test/samples/sample_4_form.json +++ b/examples/simple_chat/integration_test/samples/sample_4_form.json @@ -1,13 +1,13 @@ [ { - "version": "v0.9", + "version": "v1.0", "createSurface": { "surfaceId": "sample_4_form", - "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json" + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json" } }, { - "version": "v0.9", + "version": "v1.0", "updateComponents": { "surfaceId": "sample_4_form", "components": [ diff --git a/examples/simple_chat/integration_test/samples/sample_5_mixed.json b/examples/simple_chat/integration_test/samples/sample_5_mixed.json index 39fc6eb79..7e9197454 100644 --- a/examples/simple_chat/integration_test/samples/sample_5_mixed.json +++ b/examples/simple_chat/integration_test/samples/sample_5_mixed.json @@ -1,13 +1,13 @@ [ { - "version": "v0.9", + "version": "v1.0", "createSurface": { "surfaceId": "sample_5_mixed", - "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json" + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json" } }, { - "version": "v0.9", + "version": "v1.0", "updateComponents": { "surfaceId": "sample_5_mixed", "components": [ diff --git a/packages/a2ui_core/CHANGELOG.md b/packages/a2ui_core/CHANGELOG.md index 055eb9d9b..654be32db 100644 --- a/packages/a2ui_core/CHANGELOG.md +++ b/packages/a2ui_core/CHANGELOG.md @@ -1,5 +1,27 @@ # `a2ui_core` Changelog +## Next + +- **BREAKING**: Migrated to A2UI protocol v1.0. Message envelopes now use + `version: "v1.0"`; `CreateSurfaceMessage.theme` is renamed to + `surfaceProperties` and supports inline `components` and `dataModel`; + `updateDataModel` serializes an explicit `null` to delete keys; wire-level + `returnType` is removed from `FunctionCall`. +- **Feature**: Server-initiated function calls (`CallFunctionMessage`) with + `callableFrom` runtime boundary checks and `A2uiFunctionResponse` replies. +- **Feature**: Synchronous action responses: `A2uiClientAction` gains + `wantResponse`/`actionId`, and `ActionResponseMessage` values are written to + the data model at the action's `responsePath`. +- **Feature**: `@index` system function (with optional `offset`) inside + template instantiation loops; the `@` prefix is reserved. +- **Feature**: Catalogs support an optional `instructions` field and are + serialized with `functions` as a map plus `$defs` + (`anyComponent`/`anyFunction`/`surfaceProperties`). +- **BREAKING**: Catalog entity names are validated against UAX #31 + identifier rules; `Catalog.themeSchema` is renamed to + `surfacePropertiesSchema`; `A2uiClientError` supports `functionCallId` + (mutually exclusive with `surfaceId`). + ## 0.0.1-wip002 - **Feature**: Export `effect`. diff --git a/packages/a2ui_core/lib/src/core/messages.dart b/packages/a2ui_core/lib/src/core/messages.dart index a4b04a91c..266cd998b 100644 --- a/packages/a2ui_core/lib/src/core/messages.dart +++ b/packages/a2ui_core/lib/src/core/messages.dart @@ -57,8 +57,7 @@ abstract class A2uiMessage { catalogId: body['catalogId'] as String, surfaceProperties: body['surfaceProperties'] as Map?, sendDataModel: body['sendDataModel'] as bool? ?? false, - components: (body['components'] as List?) - ?.cast>(), + components: (body['components'] as List?)?.cast>(), dataModel: body['dataModel'] as Map?, ); } diff --git a/packages/genui/CHANGELOG.md b/packages/genui/CHANGELOG.md index cd8f84d5c..4025cb2c6 100644 --- a/packages/genui/CHANGELOG.md +++ b/packages/genui/CHANGELOG.md @@ -1,5 +1,26 @@ # `genui` Changelog +## Next + +- **BREAKING**: Migrated to A2UI protocol v1.0 (see the + [evolution guide](https://a2ui.org/specification/v1.0-evolution-guide/)). + Envelopes use `version: "v1.0"`, `SurfaceDefinition.theme` is renamed to + `surfaceProperties`, and `basicCatalogId` points at the v1.0 basic catalog. +- **Feature**: Message schemas include v1.0 `callFunction` and + `actionResponse` messages, and `createSurface` accepts inline `components` + and `dataModel`. +- **Feature**: `SurfaceController` sends `functionResponse` messages and + `INVALID_FUNCTION_CALL` errors for server-initiated function calls; + `UserActionEvent` carries `wantResponse`/`actionId`. +- **Feature**: `ClientFunction.callableFrom` static metadata (defaults to + `clientOnly`); catalog capabilities serialize `functions` as a map with + `returnType`/`callableFrom` and embed `systemPromptFragments` as catalog + `instructions`. +- **Feature**: `TextField.placeholder`, `Slider.steps`, and `Video.posterUrl` + properties. +- **Feature**: `@index` system function inside `List`/`Row`/`Column` + template loops. + ## 0.10.0 (in progress) - **Refactor**: genui now runs on `package:a2ui_core`. See diff --git a/packages/genui/lib/src/model/a2ui_schemas.dart b/packages/genui/lib/src/model/a2ui_schemas.dart index a7c7b4de0..d5a03fe19 100644 --- a/packages/genui/lib/src/model/a2ui_schemas.dart +++ b/packages/genui/lib/src/model/a2ui_schemas.dart @@ -303,10 +303,7 @@ abstract final class A2uiSchemas { properties: { 'value': S.any(description: 'The return value of the action.'), 'error': S.object( - properties: { - 'code': S.string(), - 'message': S.string(), - }, + properties: {'code': S.string(), 'message': S.string()}, required: ['code', 'message'], additionalProperties: false, ), diff --git a/packages/genui_a2a/CHANGELOG.md b/packages/genui_a2a/CHANGELOG.md index 8198638f0..6efbc9ab1 100644 --- a/packages/genui_a2a/CHANGELOG.md +++ b/packages/genui_a2a/CHANGELOG.md @@ -1,5 +1,14 @@ # `genui_a2a` Changelog +## Next + +- **BREAKING**: Migrated to A2UI protocol v1.0: the A2A extension URI is now + `https://a2ui.org/a2a-extension/a2ui/v1.0` and client events use + `version: "v1.0"` with optional `actionId`/`wantResponse`. +- **Feature**: Incoming `callFunction` and `actionResponse` data parts are + routed to the message stream; added the `application/a2ui+json` MIME type + constant. + ## 0.10.0 (in progress) - **BREAKING**: `A2uiAgentConnector.stream` now emits `package:a2ui_core`