diff --git a/renderers/angular/a2ui_explorer/src/app/tests/v0_8/5_complex_layout.spec.ts b/renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_complex-layout.spec.ts similarity index 88% rename from renderers/angular/a2ui_explorer/src/app/tests/v0_8/5_complex_layout.spec.ts rename to renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_complex-layout.spec.ts index ce9ca9e39..5551e278a 100644 --- a/renderers/angular/a2ui_explorer/src/app/tests/v0_8/5_complex_layout.spec.ts +++ b/renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_complex-layout.spec.ts @@ -16,11 +16,11 @@ import {Version, getCanvas, loadExample} from '../utils/test_utils'; -describe('Example: Complex Layout (minimal) (v0.8)', () => { +describe('Example: Complex Layout (v0.8)', () => { let textContent: string; beforeEach(async () => { - await loadExample('Complex Layout (minimal)', Version.V0_8); + await loadExample('Complex Layout', Version.V0_8); textContent = getCanvas().textContent; }); diff --git a/renderers/angular/a2ui_explorer/src/app/tests/v0_8/3_interactive_button.spec.ts b/renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_interactive-button.spec.ts similarity index 92% rename from renderers/angular/a2ui_explorer/src/app/tests/v0_8/3_interactive_button.spec.ts rename to renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_interactive-button.spec.ts index 412c8c864..58146499a 100644 --- a/renderers/angular/a2ui_explorer/src/app/tests/v0_8/3_interactive_button.spec.ts +++ b/renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_interactive-button.spec.ts @@ -18,12 +18,12 @@ import {ComponentFixture} from '@angular/core/testing'; import {DemoComponent} from '../../demo.component'; import {Version, getCanvas, loadExample, wait} from '../utils/test_utils'; -describe('Example: Interactive Button (minimal) (v0.8)', () => { +describe('Example: Interactive Button (v0.8)', () => { let textContent: string; let fixture: ComponentFixture; beforeEach(async () => { - fixture = await loadExample('Interactive Button (minimal)', Version.V0_8); + fixture = await loadExample('Interactive Button', Version.V0_8); textContent = getCanvas().textContent; }); diff --git a/renderers/angular/a2ui_explorer/src/app/tests/v0_8/2_row_layout.spec.ts b/renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_row-layout.spec.ts similarity index 88% rename from renderers/angular/a2ui_explorer/src/app/tests/v0_8/2_row_layout.spec.ts rename to renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_row-layout.spec.ts index 434935228..aad383ce1 100644 --- a/renderers/angular/a2ui_explorer/src/app/tests/v0_8/2_row_layout.spec.ts +++ b/renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_row-layout.spec.ts @@ -16,11 +16,11 @@ import {Version, getCanvas, loadExample} from '../utils/test_utils'; -describe('Example: Row Layout (minimal) (v0.8)', () => { +describe('Example: Row Layout (v0.8)', () => { let textContent: string; beforeEach(async () => { - await loadExample('Row Layout (minimal)', Version.V0_8); + await loadExample('Row Layout', Version.V0_8); textContent = getCanvas().textContent; }); diff --git a/renderers/angular/a2ui_explorer/src/app/tests/v0_8/4_login_form.spec.ts b/renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_simple-login-form.spec.ts similarity index 94% rename from renderers/angular/a2ui_explorer/src/app/tests/v0_8/4_login_form.spec.ts rename to renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_simple-login-form.spec.ts index 9252a0c82..ffcecd5fb 100644 --- a/renderers/angular/a2ui_explorer/src/app/tests/v0_8/4_login_form.spec.ts +++ b/renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_simple-login-form.spec.ts @@ -18,12 +18,12 @@ import {ComponentFixture} from '@angular/core/testing'; import {DemoComponent} from '../../demo.component'; import {Version, getCanvas, loadExample, wait} from '../utils/test_utils'; -describe('Example: Login Form (minimal) (v0.8)', () => { +describe('Example: Simple Login Form (v0.8)', () => { let textContent: string; let fixture: ComponentFixture; beforeEach(async () => { - fixture = await loadExample('Login Form (minimal)', Version.V0_8); + fixture = await loadExample('Simple Login Form', Version.V0_8); textContent = getCanvas().textContent; }); diff --git a/renderers/angular/a2ui_explorer/src/app/tests/v0_8/1_simple_text.spec.ts b/renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_simple-text.spec.ts similarity index 88% rename from renderers/angular/a2ui_explorer/src/app/tests/v0_8/1_simple_text.spec.ts rename to renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_simple-text.spec.ts index 5fb610da3..5556fa574 100644 --- a/renderers/angular/a2ui_explorer/src/app/tests/v0_8/1_simple_text.spec.ts +++ b/renderers/angular/a2ui_explorer/src/app/tests/v0_8/00_simple-text.spec.ts @@ -16,11 +16,11 @@ import {Version, getCanvas, loadExample} from '../utils/test_utils'; -describe('Example: Simple Text (minimal) (v0.8)', () => { +describe('Example: Simple Text (v0.8)', () => { let textContent: string; beforeEach(async () => { - await loadExample('Simple Text (minimal)', Version.V0_8); + await loadExample('Simple Text', Version.V0_8); textContent = getCanvas().textContent; }); diff --git a/renderers/angular/scripts/generate-examples.mjs b/renderers/angular/scripts/generate-examples.mjs index 6b3a6efe8..522fd4ec7 100644 --- a/renderers/angular/scripts/generate-examples.mjs +++ b/renderers/angular/scripts/generate-examples.mjs @@ -26,7 +26,7 @@ const DEFAULT_OUT_FILE = 'a2ui_explorer/src/app/generated/examples-bundle.ts'; /** * The default catalogs to generate examples for if none are specified. */ -const DEFAULT_CATALOGS = ['minimal', 'basic']; +const DEFAULT_CATALOGS = ['basic']; /** * The options that this script accepts. @@ -35,7 +35,6 @@ const options = { help: {type: 'boolean', short: 'h'}, 'out-file': {type: 'string', short: 'o', default: DEFAULT_OUT_FILE}, catalog: {type: 'string', short: 'c', multiple: true, default: DEFAULT_CATALOGS}, - 'override-minimal-catalog-id': {type: 'boolean', default: true}, }; /** @@ -46,35 +45,13 @@ const HELP_MESSAGE = `Usage: node generate-examples.mjs [options] Options: -o, --out-file Output file path (default: ${DEFAULT_OUT_FILE}) -c, --catalog Catalog names to include (can be specified multiple times) (default: ${DEFAULT_CATALOGS.join(', ')}) - --no-override-minimal-catalog-id Do not override catalog ID for minimal catalog -h, --help Show this help message `; -/** - * Overrides the catalog ID for minimal catalog to use basic catalog instead, - * preserving the version in the path. - */ -function overrideMessagesCatalogId(messages) { - const overrideCatalogId = catalogId => { - return catalogId.replace('catalogs/minimal/catalog.json', 'catalogs/basic/catalog.json'); - }; - for (const msg of messages) { - if (msg.createSurface && msg.createSurface.catalogId) { - // For v0.9 (and up?) - msg.createSurface.catalogId = overrideCatalogId(msg.createSurface.catalogId); - } - // The minimal catalog examples in 0.8 contain a catalogId (but not the basic - // catalog ones). That's probably copy-pasta from when catalogIds were - // introduced later, as the v0.8 renderers didn't use catalogIds. We don't - // need to handle the overrides of the catalogId for the beginRendering - // messages from the v0.8 spec. - } -} - /** * Reads examples for a given version and catalogs. */ -function readExamples(specPath, catalogs, overrideCatalogId, version) { +function readExamples(specPath, catalogs, version) { const examples = []; for (const catalog of catalogs) { @@ -117,10 +94,6 @@ function readExamples(specPath, catalogs, overrideCatalogId, version) { }; } - if (catalog === 'minimal' && overrideCatalogId) { - overrideMessagesCatalogId(example.messages); - } - examples.push(example); } catch (e) { throw new Error(`Error parsing ${filePath}`, {cause: e}); @@ -145,7 +118,6 @@ async function main() { const outPath = values['out-file']; const outDir = path.dirname(outPath); - const overrideCatalogId = values['override-minimal-catalog-id']; if (!fs.existsSync(outDir)) { fs.mkdirSync(outDir, {recursive: true}); @@ -153,18 +125,8 @@ async function main() { const catalogs = values.catalog; - const examplesV08 = readExamples( - '../../specification/v0_8/json/catalogs', - catalogs, - overrideCatalogId, - '0.8', - ); - const examplesV09 = readExamples( - '../../specification/v0_9/catalogs', - catalogs, - overrideCatalogId, - '0.9', - ); + const examplesV08 = readExamples('../../specification/v0_8/json/catalogs', catalogs, '0.8'); + const examplesV09 = readExamples('../../specification/v0_9/catalogs', catalogs, '0.9'); // Generate the file now! const tsContent = `/** diff --git a/renderers/lit/a2ui_explorer/README.md b/renderers/lit/a2ui_explorer/README.md index b27c86614..fa8a6d39c 100644 --- a/renderers/lit/a2ui_explorer/README.md +++ b/renderers/lit/a2ui_explorer/README.md @@ -1,6 +1,6 @@ -# A2UI Local Gallery (Minimal v0.8) +# A2UI Local Gallery (Basic v0.9) -This is a standalone, agentless web application designed to render the A2UI v0.8 minimal examples directly from static JSON files. It serves as a focused environment for testing renderer subset compatibility and protocol compliance. +This is a standalone, agentless web application designed to render the A2UI v0.9 basic examples directly from static JSON files. It serves as a focused environment for testing renderer compatibility and protocol compliance. ## Prerequisites @@ -34,13 +34,12 @@ For more details on building the renderers, see: yarn dev ``` This command will: - - Sync all JSON examples from `specification/v0_8/json/catalogs/minimal/examples/`. - - Generate a manifest file (`index.json`) for dynamic discovery. + - Load all JSON examples from `specification/v0_9/catalogs/basic/examples/`. - Start the Vite server at `http://localhost:5173`. ## Architecture - **Agentless**: Unlike other samples, this does not require a running Python agent. It simulates agent responses locally for interactive components (like the Login Form). -- **Dynamic Loading**: The app automatically discovers and loads _all_ `.json` files present in the v0.8 minimal specification folder at build time. To add a new test case, simply drop a JSON file into that specification folder and restart the dev server. +- **Dynamic Loading**: The app automatically discovers and loads _all_ `.json` files present in the v0.9 basic specification folder at build time. To add a new test case, simply drop a JSON file into that specification folder and restart the dev server. - **Surface Isolation**: Each example is rendered into its own independent `a2ui-surface` with a unique ID derived from the filename. - **Mock Agent Console**: All user interactions (button clicks, form submissions) are intercepted and logged to a sidebar, demonstrating how the renderer resolves actions and contexts. diff --git a/renderers/react/tests/a2ui_explorer/examples.test.tsx b/renderers/react/tests/a2ui_explorer/examples.test.tsx index a49c803f2..6e2c0f3f1 100644 --- a/renderers/react/tests/a2ui_explorer/examples.test.tsx +++ b/renderers/react/tests/a2ui_explorer/examples.test.tsx @@ -21,13 +21,13 @@ describe('examples.ts (Sample Loading Logic)', () => { describe('processExampleModules', () => { it('should correctly parse and sort modules', () => { const mockModules = { - '../../../../specification/v0_9/catalogs/minimal/examples/1_simple.json': { + '../../../../specification/v0_9/catalogs/custom/examples/1_simple.json': { name: 'Simple', }, '../../../../specification/v0_9/catalogs/basic/examples/01_card.json': { default: {name: 'Card'}, }, - '../../../../specification/v0_9/catalogs/minimal/examples/2_row.json': {name: 'Row'}, + '../../../../specification/v0_9/catalogs/custom/examples/2_row.json': {name: 'Row'}, }; const result = processExampleModules(mockModules); @@ -39,12 +39,12 @@ describe('examples.ts (Sample Loading Logic)', () => { expect(result[0]!.key).toBe('basic_01_card'); expect(result[0]!.data).toEqual({name: 'Card'}); - expect(result[1]!.catalog).toBe('Minimal'); - expect(result[1]!.key).toBe('minimal_1_simple'); + expect(result[1]!.catalog).toBe('Custom'); + expect(result[1]!.key).toBe('custom_1_simple'); expect(result[1]!.data).toEqual({name: 'Simple'}); - expect(result[2]!.catalog).toBe('Minimal'); - expect(result[2]!.key).toBe('minimal_2_row'); + expect(result[2]!.catalog).toBe('Custom'); + expect(result[2]!.key).toBe('custom_2_row'); expect(result[2]!.data).toEqual({name: 'Row'}); }); diff --git a/specification/v0_8/json/catalogs/basic/examples/00_complex-layout.json b/specification/v0_8/json/catalogs/basic/examples/00_complex-layout.json new file mode 100644 index 000000000..70dabf8dc --- /dev/null +++ b/specification/v0_8/json/catalogs/basic/examples/00_complex-layout.json @@ -0,0 +1,89 @@ +[ + { + "surfaceUpdate": { + "surfaceId": "gallery-complex-layout", + "components": [ + { + "id": "root", + "component": { + "Column": { + "children": { + "explicitList": ["header", "form_row", "footer"] + }, + "distribution": "spaceBetween", + "alignment": "stretch" + } + } + }, + { + "id": "header", + "component": { + "Text": { + "text": { + "literalString": "User Profile Form" + }, + "usageHint": "h1" + } + } + }, + { + "id": "form_row", + "component": { + "Row": { + "children": { + "explicitList": ["first_name", "last_name"] + }, + "distribution": "start", + "alignment": "start" + } + } + }, + { + "id": "first_name", + "weight": 1, + "component": { + "TextField": { + "label": { + "literalString": "First Name" + }, + "text": { + "path": "/firstName" + } + } + } + }, + { + "id": "last_name", + "weight": 1, + "component": { + "TextField": { + "label": { + "literalString": "Last Name" + }, + "text": { + "path": "/lastName" + } + } + } + }, + { + "id": "footer", + "component": { + "Text": { + "text": { + "literalString": "Please fill out all fields." + }, + "usageHint": "caption" + } + } + } + ] + } + }, + { + "beginRendering": { + "surfaceId": "gallery-complex-layout", + "root": "root" + } + } +] diff --git a/specification/v0_8/json/catalogs/basic/examples/00_interactive-button.json b/specification/v0_8/json/catalogs/basic/examples/00_interactive-button.json new file mode 100644 index 000000000..93adc6e6f --- /dev/null +++ b/specification/v0_8/json/catalogs/basic/examples/00_interactive-button.json @@ -0,0 +1,60 @@ +[ + { + "surfaceUpdate": { + "surfaceId": "gallery-interactive-button", + "components": [ + { + "id": "root", + "component": { + "Column": { + "children": { + "explicitList": ["title", "action_button"] + }, + "distribution": "center", + "alignment": "center" + } + } + }, + { + "id": "title", + "component": { + "Text": { + "text": { + "literalString": "Click the button below" + }, + "usageHint": "body" + } + } + }, + { + "id": "action_button", + "component": { + "Button": { + "child": "button_label", + "primary": true, + "action": { + "name": "button_clicked" + } + } + } + }, + { + "id": "button_label", + "component": { + "Text": { + "text": { + "literalString": "Click Me" + } + } + } + } + ] + } + }, + { + "beginRendering": { + "surfaceId": "gallery-interactive-button", + "root": "root" + } + } +] diff --git a/specification/v0_8/json/catalogs/basic/examples/00_row-layout.json b/specification/v0_8/json/catalogs/basic/examples/00_row-layout.json new file mode 100644 index 000000000..2d55b4ef8 --- /dev/null +++ b/specification/v0_8/json/catalogs/basic/examples/00_row-layout.json @@ -0,0 +1,49 @@ +[ + { + "surfaceUpdate": { + "surfaceId": "gallery-row-layout", + "components": [ + { + "id": "root", + "component": { + "Row": { + "children": { + "explicitList": ["left_text", "right_text"] + }, + "distribution": "spaceBetween", + "alignment": "center" + } + } + }, + { + "id": "left_text", + "component": { + "Text": { + "text": { + "literalString": "Left Content" + }, + "usageHint": "body" + } + } + }, + { + "id": "right_text", + "component": { + "Text": { + "text": { + "literalString": "Right Content" + }, + "usageHint": "caption" + } + } + } + ] + } + }, + { + "beginRendering": { + "surfaceId": "gallery-row-layout", + "root": "root" + } + } +] diff --git a/specification/v0_8/json/catalogs/basic/examples/00_simple-login-form.json b/specification/v0_8/json/catalogs/basic/examples/00_simple-login-form.json new file mode 100644 index 000000000..d1b016d82 --- /dev/null +++ b/specification/v0_8/json/catalogs/basic/examples/00_simple-login-form.json @@ -0,0 +1,118 @@ +[ + { + "dataModelUpdate": { + "surfaceId": "gallery-simple-login-form", + "path": "/", + "contents": [ + { + "key": "username", + "valueString": "" + }, + { + "key": "password", + "valueString": "" + } + ] + } + }, + { + "surfaceUpdate": { + "surfaceId": "gallery-simple-login-form", + "components": [ + { + "id": "root", + "component": { + "Column": { + "children": { + "explicitList": ["form_title", "username_field", "password_field", "submit_button"] + }, + "distribution": "start", + "alignment": "stretch" + } + } + }, + { + "id": "form_title", + "component": { + "Text": { + "text": { + "literalString": "Login" + }, + "usageHint": "h2" + } + } + }, + { + "id": "username_field", + "component": { + "TextField": { + "label": { + "literalString": "Username" + }, + "text": { + "path": "/username" + }, + "textFieldType": "shortText" + } + } + }, + { + "id": "password_field", + "component": { + "TextField": { + "label": { + "literalString": "Password" + }, + "text": { + "path": "/password" + }, + "textFieldType": "obscured" + } + } + }, + { + "id": "submit_button", + "component": { + "Button": { + "child": "submit_label", + "primary": true, + "action": { + "name": "login_submitted", + "context": [ + { + "key": "user", + "value": { + "path": "/username" + } + }, + { + "key": "pass", + "value": { + "path": "/password" + } + } + ] + } + } + } + }, + { + "id": "submit_label", + "component": { + "Text": { + "text": { + "literalString": "Sign In" + } + } + } + } + ] + } + }, + { + "beginRendering": { + "surfaceId": "gallery-simple-login-form", + "root": "root" + } + } +] diff --git a/specification/v0_8/json/catalogs/basic/examples/00_simple-text.json b/specification/v0_8/json/catalogs/basic/examples/00_simple-text.json new file mode 100644 index 000000000..9670e5b2e --- /dev/null +++ b/specification/v0_8/json/catalogs/basic/examples/00_simple-text.json @@ -0,0 +1,26 @@ +[ + { + "surfaceUpdate": { + "surfaceId": "gallery-simple-text", + "components": [ + { + "id": "root", + "component": { + "Text": { + "text": { + "literalString": "Hello, Minimal Catalog!" + }, + "usageHint": "h1" + } + } + } + ] + } + }, + { + "beginRendering": { + "surfaceId": "gallery-simple-text", + "root": "root" + } + } +] diff --git a/specification/v0_9/catalogs/basic/catalog.json b/specification/v0_9/catalogs/basic/catalog.json index cefc2b98b..5e57459b8 100644 --- a/specification/v0_9/catalogs/basic/catalog.json +++ b/specification/v0_9/catalogs/basic/catalog.json @@ -1240,6 +1240,30 @@ }, "required": ["call", "args"], "unevaluatedProperties": false + }, + "capitalize": { + "type": "object", + "description": "Converts an input string to a capitalized version.", + "properties": { + "call": { + "const": "capitalize" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "https://a2ui.org/specification/v0_9/common_types.json#/$defs/DynamicString" + } + }, + "required": ["value"], + "unevaluatedProperties": false + }, + "returnType": { + "const": "string" + } + }, + "required": ["call", "args"], + "unevaluatedProperties": false } }, "$defs": { @@ -1376,6 +1400,9 @@ }, { "$ref": "#/functions/not" + }, + { + "$ref": "#/functions/capitalize" } ] } diff --git a/specification/v0_9/catalogs/basic/examples/00_capitalized-text.json b/specification/v0_9/catalogs/basic/examples/00_capitalized-text.json new file mode 100644 index 000000000..0b3de42f7 --- /dev/null +++ b/specification/v0_9/catalogs/basic/examples/00_capitalized-text.json @@ -0,0 +1,58 @@ +{ + "name": "Capitalized Text", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "gallery-capitalized-text", + "catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json", + "sendDataModel": true + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "gallery-capitalized-text", + "components": [ + { + "id": "root", + "component": "Column", + "children": ["input_field", "result_label", "result_text"], + "justify": "start", + "align": "stretch" + }, + { + "id": "input_field", + "component": "TextField", + "label": "Type something in lowercase:", + "value": { + "path": "/inputValue" + }, + "variant": "shortText" + }, + { + "id": "result_label", + "component": "Text", + "text": "Capitalized output:", + "variant": "caption" + }, + { + "id": "result_text", + "component": "Text", + "text": { + "call": "capitalize", + "args": { + "value": { + "path": "/inputValue" + } + }, + "returnType": "string" + }, + "variant": "h2" + } + ] + } + } + ] +} diff --git a/specification/v0_9/catalogs/basic/examples/00_complex-layout.json b/specification/v0_9/catalogs/basic/examples/00_complex-layout.json new file mode 100644 index 000000000..2f36f8a67 --- /dev/null +++ b/specification/v0_9/catalogs/basic/examples/00_complex-layout.json @@ -0,0 +1,65 @@ +{ + "name": "Complex Layout", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "gallery-complex-layout", + "catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json" + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "gallery-complex-layout", + "components": [ + { + "id": "root", + "component": "Column", + "children": ["header", "form_row", "footer"], + "justify": "spaceBetween", + "align": "stretch" + }, + { + "id": "header", + "component": "Text", + "text": "User Profile Form", + "variant": "h1" + }, + { + "id": "form_row", + "component": "Row", + "children": ["first_name", "last_name"], + "justify": "start", + "align": "start" + }, + { + "id": "first_name", + "component": "TextField", + "label": "First Name", + "value": { + "path": "/firstName" + }, + "weight": 1 + }, + { + "id": "last_name", + "component": "TextField", + "label": "Last Name", + "value": { + "path": "/lastName" + }, + "weight": 1 + }, + { + "id": "footer", + "component": "Text", + "text": "Please fill out all fields.", + "variant": "caption" + } + ] + } + } + ] +} diff --git a/specification/v0_9/catalogs/basic/examples/00_incremental.json b/specification/v0_9/catalogs/basic/examples/00_incremental.json new file mode 100644 index 000000000..c6e121dca --- /dev/null +++ b/specification/v0_9/catalogs/basic/examples/00_incremental.json @@ -0,0 +1,134 @@ +{ + "name": "Incremental", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "gallery-incremental", + "catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json" + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "gallery-incremental", + "path": "/", + "value": { + "restaurants": [ + { + "title": "The Golden Fork", + "subtitle": "Fine Dining & Spirits", + "address": "123 Gastronomy Lane" + }, + { + "title": "Ocean's Bounty", + "subtitle": "Fresh Daily Seafood", + "address": "456 Shoreline Dr" + }, + { + "title": "Pizzeria Roma", + "subtitle": "Authentic Wood-Fired Pizza", + "address": "789 Napoli Way" + } + ] + } + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "gallery-incremental", + "components": [ + { + "id": "root", + "component": "Column", + "children": { + "path": "/restaurants", + "componentId": "restaurant_card" + } + } + ] + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "gallery-incremental", + "components": [ + { + "id": "restaurant_card", + "component": "Column", + "children": ["rc_title", "rc_subtitle", "rc_address"] + }, + { + "id": "rc_title", + "component": "Text", + "text": { + "path": "title" + } + }, + { + "id": "rc_subtitle", + "component": "Text", + "text": { + "path": "subtitle" + } + }, + { + "id": "rc_address", + "component": "Text", + "text": { + "path": "address" + } + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "gallery-incremental", + "path": "/restaurants/3", + "value": { + "title": "Spice Route", + "subtitle": "Exotic Flavors from the East", + "address": "101 Silk Road St" + } + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "gallery-incremental", + "components": [ + { + "id": "restaurant_card", + "component": "Column", + "children": ["rc_title", "rc_subtitle", "rc_address", "rc_button"] + }, + { + "id": "rc_button", + "component": "Button", + "child": "rc_button_label", + "action": { + "event": { + "name": "book_now", + "context": { + "restaurantName": { + "path": "title" + } + } + } + } + }, + { + "id": "rc_button_label", + "component": "Text", + "text": "Book now" + } + ] + } + } + ] +} diff --git a/specification/v0_9/catalogs/basic/examples/00_interactive-button.json b/specification/v0_9/catalogs/basic/examples/00_interactive-button.json new file mode 100644 index 000000000..d1eec8b2c --- /dev/null +++ b/specification/v0_9/catalogs/basic/examples/00_interactive-button.json @@ -0,0 +1,51 @@ +{ + "name": "Interactive Button", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "gallery-interactive-button", + "catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json" + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "gallery-interactive-button", + "components": [ + { + "id": "root", + "component": "Column", + "children": ["title", "action_button"], + "justify": "center", + "align": "center" + }, + { + "id": "title", + "component": "Text", + "text": "Click the button below", + "variant": "body" + }, + { + "id": "action_button", + "component": "Button", + "child": "button_label", + "variant": "primary", + "action": { + "event": { + "name": "button_clicked", + "context": {} + } + } + }, + { + "id": "button_label", + "component": "Text", + "text": "Click Me" + } + ] + } + } + ] +} diff --git a/specification/v0_9/catalogs/basic/examples/00_row-layout.json b/specification/v0_9/catalogs/basic/examples/00_row-layout.json new file mode 100644 index 000000000..4e4332498 --- /dev/null +++ b/specification/v0_9/catalogs/basic/examples/00_row-layout.json @@ -0,0 +1,40 @@ +{ + "name": "Row Layout", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "gallery-row-layout", + "catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json" + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "gallery-row-layout", + "components": [ + { + "id": "root", + "component": "Row", + "children": ["left_text", "right_text"], + "justify": "spaceBetween", + "align": "center" + }, + { + "id": "left_text", + "component": "Text", + "text": "Left Content", + "variant": "body" + }, + { + "id": "right_text", + "component": "Text", + "text": "Right Content", + "variant": "caption" + } + ] + } + } + ] +} diff --git a/specification/v0_9/catalogs/basic/examples/00_simple-login-form.json b/specification/v0_9/catalogs/basic/examples/00_simple-login-form.json new file mode 100644 index 000000000..aab65a76f --- /dev/null +++ b/specification/v0_9/catalogs/basic/examples/00_simple-login-form.json @@ -0,0 +1,77 @@ +{ + "name": "Simple Login Form", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "gallery-simple-login-form", + "catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json", + "sendDataModel": true + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "gallery-simple-login-form", + "components": [ + { + "id": "root", + "component": "Column", + "children": ["form_title", "username_field", "password_field", "submit_button"], + "justify": "start", + "align": "stretch" + }, + { + "id": "form_title", + "component": "Text", + "text": "Login", + "variant": "h2" + }, + { + "id": "username_field", + "component": "TextField", + "label": "Username", + "value": { + "path": "/username" + }, + "variant": "shortText" + }, + { + "id": "password_field", + "component": "TextField", + "label": "Password", + "value": { + "path": "/password" + }, + "variant": "obscured" + }, + { + "id": "submit_button", + "component": "Button", + "child": "submit_label", + "variant": "primary", + "action": { + "event": { + "name": "login_submitted", + "context": { + "user": { + "path": "/username" + }, + "pass": { + "path": "/password" + } + } + } + } + }, + { + "id": "submit_label", + "component": "Text", + "text": "Sign In" + } + ] + } + } + ] +} diff --git a/specification/v0_9/catalogs/basic/examples/00_simple-text.json b/specification/v0_9/catalogs/basic/examples/00_simple-text.json new file mode 100644 index 000000000..81cab829f --- /dev/null +++ b/specification/v0_9/catalogs/basic/examples/00_simple-text.json @@ -0,0 +1,27 @@ +{ + "name": "Simple Text", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "gallery-simple-text", + "catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json" + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "gallery-simple-text", + "components": [ + { + "id": "root", + "component": "Text", + "text": "Hello, Minimal Catalog!", + "variant": "h1" + } + ] + } + } + ] +} diff --git a/specification/v0_9/docs/renderer_guide.md b/specification/v0_9/docs/renderer_guide.md index 86d5f0ed0..468440850 100644 --- a/specification/v0_9/docs/renderer_guide.md +++ b/specification/v0_9/docs/renderer_guide.md @@ -743,8 +743,8 @@ Thoroughly review: - `specification/v0_9/docs/a2ui_protocol.md` (protocol rules) - `specification/v0_9/json/common_types.json` (dynamic binding types) - `specification/v0_9/json/server_to_client.json` (message envelopes) -- `specification/v0_9/json/catalogs/minimal/minimal_catalog.json` (your initial target) -- `specification/v0_9/docs/basic_catalog_implementation_guide.md` (for rendering and spacing rules for when you get to the basic catalog) +- `specification/v0_9/catalogs/basic/catalog.json` (your target) +- `specification/v0_9/docs/basic_catalog_implementation_guide.md` (for rendering and spacing rules) ### 2. Key Architecture Decisions (Write a Plan Document) @@ -776,13 +776,13 @@ Implement the bridge between models and native UI (Section 5 & 6). - Implement the `Surface` view/widget that recurses through components. - Implement subscription lifecycle management (lazy mounting, unmounting disposal). -### 5. Minimal Catalog Support +### 5. Initial Basic Catalog Support -Target the `minimal_catalog.json` first. +Target a foundational subset of components in `basic/catalog.json` first to bootstrap your implementation. -- Implement the pure API schemas for `Text`, `Row`, `Column`, `Button`, `TextField`. +- Implement the pure API schemas for `Text`, `Row`, `Column`, `Button`, `TextField` (which are part of the standard Basic Catalog). - Implement the specific native UI rendering components for these. -- Implement the `capitalize` function. +- Implement the `formatString` function (which is required for basic text rendering, see Section 7). - Bundle these into a `Catalog`. - **Action**: Write unit tests verifying that properties update reactively when data changes. @@ -790,16 +790,15 @@ Target the `minimal_catalog.json` first. Build the Gallery App following the requirements in **Section 8**. -- Load JSON samples from `specification/v0_9/json/catalogs/minimal/examples/`. +- Load JSON samples from `specification/v0_9/catalogs/basic/examples/` (focusing on the simpler ones first, such as those using only the bootstrapped components). - Verify progressive rendering and reactivity. - **STOP HERE. Ask the user for approval of the architecture and gallery application before proceeding to step 7.** -### 7. Basic Catalog Support +### 7. Complete Basic Catalog Support -Once the minimal architecture is proven robust, refer to the [Basic Catalog Implementation Guide](basic_catalog_implementation_guide.md) and: +Once the initial architecture is proven robust, complete the implementation of the Basic Catalog: -- **Core Library**: Implement the full suite of basic functions. It is crucial to note that string interpolation and expression parsing should ONLY happen within the `formatString` function. Do not attempt to add global string interpolation to all strings. +- **Core Library**: Implement the remaining basic functions. It is crucial to note that string interpolation and expression parsing should ONLY happen within the `formatString` function. Do not attempt to add global string interpolation to all strings. - **Core Library**: Create definitions/binders for the remaining Basic Catalog components. - **Framework Library**: Implement all remaining UI widgets. - **Tests**: Look at existing reference implementations (e.g., `web_core`) to formulate and run comprehensive unit and integration test cases for data coercion and function logic. -- Update the Gallery App to load samples from `specification/v0_9/json/catalogs/basic/examples/`. diff --git a/specification/v1_0/catalogs/basic/catalog.json b/specification/v1_0/catalogs/basic/catalog.json index 2ecb0d9e5..63caaaf84 100644 --- a/specification/v1_0/catalogs/basic/catalog.json +++ b/specification/v1_0/catalogs/basic/catalog.json @@ -1219,6 +1219,28 @@ }, "required": ["call", "args"], "unevaluatedProperties": false + }, + "capitalize": { + "type": "object", + "description": "Converts an input string to a capitalized version.", + "returnType": "string", + "properties": { + "call": { + "const": "capitalize" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString" + } + }, + "required": ["value"], + "unevaluatedProperties": false + } + }, + "required": ["call", "args"], + "unevaluatedProperties": false } }, "$defs": { @@ -1350,6 +1372,9 @@ }, { "$ref": "#/functions/not" + }, + { + "$ref": "#/functions/capitalize" } ] } diff --git a/specification/v1_0/catalogs/basic/examples/00_capitalized-text.json b/specification/v1_0/catalogs/basic/examples/00_capitalized-text.json new file mode 100644 index 000000000..e8a955dbc --- /dev/null +++ b/specification/v1_0/catalogs/basic/examples/00_capitalized-text.json @@ -0,0 +1,57 @@ +{ + "name": "Capitalized Text", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v1.0", + "createSurface": { + "surfaceId": "gallery-capitalized-text", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", + "sendDataModel": true + } + }, + { + "version": "v1.0", + "updateComponents": { + "surfaceId": "gallery-capitalized-text", + "components": [ + { + "id": "root", + "component": "Column", + "children": ["input_field", "result_label", "result_text"], + "justify": "start", + "align": "stretch" + }, + { + "id": "input_field", + "component": "TextField", + "label": "Type something in lowercase:", + "value": { + "path": "/inputValue" + }, + "variant": "shortText" + }, + { + "id": "result_label", + "component": "Text", + "text": "Capitalized output:", + "variant": "caption" + }, + { + "id": "result_text", + "component": "Text", + "text": { + "call": "capitalize", + "args": { + "value": { + "path": "/inputValue" + } + } + }, + "variant": "h2" + } + ] + } + } + ] +} diff --git a/specification/v1_0/catalogs/basic/examples/00_complex-layout.json b/specification/v1_0/catalogs/basic/examples/00_complex-layout.json new file mode 100644 index 000000000..7fc131198 --- /dev/null +++ b/specification/v1_0/catalogs/basic/examples/00_complex-layout.json @@ -0,0 +1,65 @@ +{ + "name": "Complex Layout", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v1.0", + "createSurface": { + "surfaceId": "gallery-complex-layout", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json" + } + }, + { + "version": "v1.0", + "updateComponents": { + "surfaceId": "gallery-complex-layout", + "components": [ + { + "id": "root", + "component": "Column", + "children": ["header", "form_row", "footer"], + "justify": "spaceBetween", + "align": "stretch" + }, + { + "id": "header", + "component": "Text", + "text": "User Profile Form", + "variant": "h1" + }, + { + "id": "form_row", + "component": "Row", + "children": ["first_name", "last_name"], + "justify": "start", + "align": "start" + }, + { + "id": "first_name", + "component": "TextField", + "label": "First Name", + "value": { + "path": "/firstName" + }, + "weight": 1 + }, + { + "id": "last_name", + "component": "TextField", + "label": "Last Name", + "value": { + "path": "/lastName" + }, + "weight": 1 + }, + { + "id": "footer", + "component": "Text", + "text": "Please fill out all fields.", + "variant": "caption" + } + ] + } + } + ] +} diff --git a/specification/v1_0/catalogs/basic/examples/00_incremental.json b/specification/v1_0/catalogs/basic/examples/00_incremental.json new file mode 100644 index 000000000..86c11dbbc --- /dev/null +++ b/specification/v1_0/catalogs/basic/examples/00_incremental.json @@ -0,0 +1,134 @@ +{ + "name": "Incremental", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v1.0", + "createSurface": { + "surfaceId": "gallery-incremental", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json" + } + }, + { + "version": "v1.0", + "updateDataModel": { + "surfaceId": "gallery-incremental", + "path": "/", + "value": { + "restaurants": [ + { + "title": "The Golden Fork", + "subtitle": "Fine Dining & Spirits", + "address": "123 Gastronomy Lane" + }, + { + "title": "Ocean's Bounty", + "subtitle": "Fresh Daily Seafood", + "address": "456 Shoreline Dr" + }, + { + "title": "Pizzeria Roma", + "subtitle": "Authentic Wood-Fired Pizza", + "address": "789 Napoli Way" + } + ] + } + } + }, + { + "version": "v1.0", + "updateComponents": { + "surfaceId": "gallery-incremental", + "components": [ + { + "id": "root", + "component": "Column", + "children": { + "path": "/restaurants", + "componentId": "restaurant_card" + } + } + ] + } + }, + { + "version": "v1.0", + "updateComponents": { + "surfaceId": "gallery-incremental", + "components": [ + { + "id": "restaurant_card", + "component": "Column", + "children": ["rc_title", "rc_subtitle", "rc_address"] + }, + { + "id": "rc_title", + "component": "Text", + "text": { + "path": "title" + } + }, + { + "id": "rc_subtitle", + "component": "Text", + "text": { + "path": "subtitle" + } + }, + { + "id": "rc_address", + "component": "Text", + "text": { + "path": "address" + } + } + ] + } + }, + { + "version": "v1.0", + "updateDataModel": { + "surfaceId": "gallery-incremental", + "path": "/restaurants/3", + "value": { + "title": "Spice Route", + "subtitle": "Exotic Flavors from the East", + "address": "101 Silk Road St" + } + } + }, + { + "version": "v1.0", + "updateComponents": { + "surfaceId": "gallery-incremental", + "components": [ + { + "id": "restaurant_card", + "component": "Column", + "children": ["rc_title", "rc_subtitle", "rc_address", "rc_button"] + }, + { + "id": "rc_button", + "component": "Button", + "child": "rc_button_label", + "action": { + "event": { + "name": "book_now", + "context": { + "restaurantName": { + "path": "title" + } + } + } + } + }, + { + "id": "rc_button_label", + "component": "Text", + "text": "Book now" + } + ] + } + } + ] +} diff --git a/specification/v1_0/catalogs/basic/examples/00_interactive-button.json b/specification/v1_0/catalogs/basic/examples/00_interactive-button.json new file mode 100644 index 000000000..c565fc1c8 --- /dev/null +++ b/specification/v1_0/catalogs/basic/examples/00_interactive-button.json @@ -0,0 +1,51 @@ +{ + "name": "Interactive Button", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v1.0", + "createSurface": { + "surfaceId": "gallery-interactive-button", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json" + } + }, + { + "version": "v1.0", + "updateComponents": { + "surfaceId": "gallery-interactive-button", + "components": [ + { + "id": "root", + "component": "Column", + "children": ["title", "action_button"], + "justify": "center", + "align": "center" + }, + { + "id": "title", + "component": "Text", + "text": "Click the button below", + "variant": "body" + }, + { + "id": "action_button", + "component": "Button", + "child": "button_label", + "variant": "primary", + "action": { + "event": { + "name": "button_clicked", + "context": {} + } + } + }, + { + "id": "button_label", + "component": "Text", + "text": "Click Me" + } + ] + } + } + ] +} diff --git a/specification/v1_0/catalogs/basic/examples/00_row-layout.json b/specification/v1_0/catalogs/basic/examples/00_row-layout.json new file mode 100644 index 000000000..dfb878ff8 --- /dev/null +++ b/specification/v1_0/catalogs/basic/examples/00_row-layout.json @@ -0,0 +1,40 @@ +{ + "name": "Row Layout", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v1.0", + "createSurface": { + "surfaceId": "gallery-row-layout", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json" + } + }, + { + "version": "v1.0", + "updateComponents": { + "surfaceId": "gallery-row-layout", + "components": [ + { + "id": "root", + "component": "Row", + "children": ["left_text", "right_text"], + "justify": "spaceBetween", + "align": "center" + }, + { + "id": "left_text", + "component": "Text", + "text": "Left Content", + "variant": "body" + }, + { + "id": "right_text", + "component": "Text", + "text": "Right Content", + "variant": "caption" + } + ] + } + } + ] +} diff --git a/specification/v1_0/catalogs/basic/examples/00_simple-login-form.json b/specification/v1_0/catalogs/basic/examples/00_simple-login-form.json new file mode 100644 index 000000000..7c998ded2 --- /dev/null +++ b/specification/v1_0/catalogs/basic/examples/00_simple-login-form.json @@ -0,0 +1,77 @@ +{ + "name": "Simple Login Form", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v1.0", + "createSurface": { + "surfaceId": "gallery-simple-login-form", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", + "sendDataModel": true + } + }, + { + "version": "v1.0", + "updateComponents": { + "surfaceId": "gallery-simple-login-form", + "components": [ + { + "id": "root", + "component": "Column", + "children": ["form_title", "username_field", "password_field", "submit_button"], + "justify": "start", + "align": "stretch" + }, + { + "id": "form_title", + "component": "Text", + "text": "Login", + "variant": "h2" + }, + { + "id": "username_field", + "component": "TextField", + "label": "Username", + "value": { + "path": "/username" + }, + "variant": "shortText" + }, + { + "id": "password_field", + "component": "TextField", + "label": "Password", + "value": { + "path": "/password" + }, + "variant": "obscured" + }, + { + "id": "submit_button", + "component": "Button", + "child": "submit_label", + "variant": "primary", + "action": { + "event": { + "name": "login_submitted", + "context": { + "user": { + "path": "/username" + }, + "pass": { + "path": "/password" + } + } + } + } + }, + { + "id": "submit_label", + "component": "Text", + "text": "Sign In" + } + ] + } + } + ] +} diff --git a/specification/v1_0/catalogs/basic/examples/00_simple-text.json b/specification/v1_0/catalogs/basic/examples/00_simple-text.json new file mode 100644 index 000000000..d93d49c16 --- /dev/null +++ b/specification/v1_0/catalogs/basic/examples/00_simple-text.json @@ -0,0 +1,27 @@ +{ + "name": "Simple Text", + "description": "Simple example demonstrating basic catalog components.", + "messages": [ + { + "version": "v1.0", + "createSurface": { + "surfaceId": "gallery-simple-text", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json" + } + }, + { + "version": "v1.0", + "updateComponents": { + "surfaceId": "gallery-simple-text", + "components": [ + { + "id": "root", + "component": "Text", + "text": "Hello, Minimal Catalog!", + "variant": "h1" + } + ] + } + } + ] +}