From 40c7756602706d1455d1517b7a4681b0ba4c3e09 Mon Sep 17 00:00:00 2001 From: Jacob Simionato Date: Thu, 22 Jan 2026 09:31:01 +1030 Subject: [PATCH] feat: Support component deletion via component: null Update server_to_client.json schema to allow component: null. Update protocol documentation. Add component_deletion.json test cases. Update evaluation framework to support and test this feature. --- specification/v0_9/docs/a2ui_protocol.md | 15 ++-- .../v0_9/eval/src/generation_flow.ts | 1 + specification/v0_9/eval/src/prompts.ts | 5 ++ specification/v0_9/eval/src/validator.ts | 6 ++ specification/v0_9/json/server_to_client.json | 21 +++++- .../v0_9/test/cases/component_deletion.json | 70 +++++++++++++++++++ 6 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 specification/v0_9/test/cases/component_deletion.json diff --git a/specification/v0_9/docs/a2ui_protocol.md b/specification/v0_9/docs/a2ui_protocol.md index a266a140..3f9758be 100644 --- a/specification/v0_9/docs/a2ui_protocol.md +++ b/specification/v0_9/docs/a2ui_protocol.md @@ -148,12 +148,18 @@ This message signals the client to create a new surface and begin rendering it. ### `updateComponents` -This message provides a list of UI components to be added to or updated within a specific surface. The components are provided as a flat list, and their relationships are defined by ID references in an adjacency list. This message may only be sent to a surface that has already been created. Note that components may reference children or data bindings that do not yet exist; clients should handle this gracefully by rendering placeholders (progressive rendering). +This message provides a list of UI components to be added, updated, or removed within a specific surface. The components are provided as a flat list, and their relationships are defined by ID references in an adjacency list. This message may only be sent to a surface that has already been created. + +**Adding or Updating Components:** +To add or update a component, provide the full component definition object. If a component with the same `id` already exists, it will be replaced. + +**Deleting Components:** +To delete a component, provide an object with the component's `id` and set the `component` property to `null`. This removes the component from the client's memory. **Properties:** - `surfaceId` (string, required): The unique identifier for the UI surface to be updated. This is typically a name with meaning (e.g. "user_profile_card"), and it has to be unique within the context of the GenUI session. -- `components` (array, required): A list of component objects. The components are provided as a flat list, and their relationships are defined by ID references in an adjacency list. +- `components` (array, required): A list of component objects (for updates) or tombstone objects (for deletions). **Example:** @@ -173,9 +179,8 @@ This message provides a list of UI components to be added to or updated within a "text": "John Doe" }, { - "id": "user_title", - "component": "Text", - "text": "Software Engineer" + "id": "old_component_id", + "component": null } ] } diff --git a/specification/v0_9/eval/src/generation_flow.ts b/specification/v0_9/eval/src/generation_flow.ts index a5d9018a..d369c680 100644 --- a/specification/v0_9/eval/src/generation_flow.ts +++ b/specification/v0_9/eval/src/generation_flow.ts @@ -56,6 +56,7 @@ Standard Instructions: 13. Do NOT use a 'style' property. Use standard properties like 'align', 'justify', 'variant', etc. 14. Do NOT invent properties that are not in the schema. Check the 'properties' list for each component type. 15. Use 'checks' property for validation rules if required. +16. To delete a component, set its 'component' property to null (e.g. { "id": "...", "component": null }). ${catalogRules ? `\nInstructions specific to this catalog:\n${catalogRules}` : ""} Schemas: diff --git a/specification/v0_9/eval/src/prompts.ts b/specification/v0_9/eval/src/prompts.ts index 6a533504..0027f521 100644 --- a/specification/v0_9/eval/src/prompts.ts +++ b/specification/v0_9/eval/src/prompts.ts @@ -26,6 +26,11 @@ export const prompts: TestPrompt[] = [ description: "A DeleteSurface message to remove a UI surface.", promptText: `Generate a JSON message containing a deleteSurface for the surface 'dashboard-surface-1'.`, }, + { + name: "deleteComponent", + description: "An updateComponents message to delete a component.", + promptText: `Generate an 'updateComponents' message for surfaceId 'main'. This message should delete the component with ID 'loading-spinner' and update the component with ID 'status-text' to have the text "Loaded".`, + }, { name: "dogBreedGenerator", description: diff --git a/specification/v0_9/eval/src/validator.ts b/specification/v0_9/eval/src/validator.ts index e98aae7d..9c676575 100644 --- a/specification/v0_9/eval/src/validator.ts +++ b/specification/v0_9/eval/src/validator.ts @@ -297,6 +297,12 @@ export class Validator { } const componentType = component.component; + + // Handle deletion (tombstone) + if (componentType === null) { + return; + } + if (!componentType || typeof componentType !== "string") { errors.push(`Component '${id}' is missing 'component' property.`); return; diff --git a/specification/v0_9/json/server_to_client.json b/specification/v0_9/json/server_to_client.json index 176136fc..ae11a822 100644 --- a/specification/v0_9/json/server_to_client.json +++ b/specification/v0_9/json/server_to_client.json @@ -60,7 +60,26 @@ "description": "A list containing all UI components for the surface.", "minItems": 1, "items": { - "$ref": "standard_catalog.json#/$defs/anyComponent" + "oneOf": [ + { + "$ref": "standard_catalog.json#/$defs/anyComponent" + }, + { + "type": "object", + "description": "A tombstone to remove a component.", + "properties": { + "id": { + "$ref": "common_types.json#/$defs/ComponentId" + }, + "component": { + "const": null, + "description": "Set to null to delete this component." + } + }, + "required": ["id", "component"], + "additionalProperties": false + } + ] } } }, diff --git a/specification/v0_9/test/cases/component_deletion.json b/specification/v0_9/test/cases/component_deletion.json new file mode 100644 index 00000000..a72f3abe --- /dev/null +++ b/specification/v0_9/test/cases/component_deletion.json @@ -0,0 +1,70 @@ +{ + "schema": "server_to_client.json", + "tests": [ + { + "description": "UpdateComponents with component deletion (tombstone)", + "valid": true, + "data": { + "updateComponents": { + "surfaceId": "test_surface", + "components": [ + { + "id": "item_to_delete", + "component": null + } + ] + } + } + }, + { + "description": "UpdateComponents mixing update and deletion", + "valid": true, + "data": { + "updateComponents": { + "surfaceId": "test_surface", + "components": [ + { + "id": "new_item", + "component": "Text", + "text": "Hello" + }, + { + "id": "old_item", + "component": null + } + ] + } + } + }, + { + "description": "Deletion with extra properties (should be invalid)", + "valid": false, + "data": { + "updateComponents": { + "surfaceId": "test_surface", + "components": [ + { + "id": "item_to_delete", + "component": null, + "text": "Should not be here" + } + ] + } + } + }, + { + "description": "Deletion missing ID (should be invalid)", + "valid": false, + "data": { + "updateComponents": { + "surfaceId": "test_surface", + "components": [ + { + "component": null + } + ] + } + } + } + ] +}