Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions specification/v0_9/docs/a2ui_protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The previous version of this paragraph included an important note about progressive rendering: "Note that components may reference children or data bindings that do not yet exist; clients should handle this gracefully by rendering placeholders (progressive rendering)."

While this concept is mentioned elsewhere in the documentation, it is highly relevant in the updateComponents section. Its removal could cause confusion for developers implementing a client. I recommend re-adding a concise version of this note to ensure client implementation details are clear in this context.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should keep the part describing progressive rendering.


**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).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the adjacency list description? I think we should keep that.


**Example:**

Expand All @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why wouldn't it work to just exclude component?

{
"id": "old_component_id"
}

updateDataModel overwrites instead of merging, and it treats null as remove key, so it should have the same effect, no?

}
Comment on lines 181 to 184
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The updated example is a bit confusing. The root component (defined on lines 172-174) references a component with id: "user_title" in its children array. However, this change removes the user_title component and replaces it with a deletion request for old_component_id. This leaves a dangling reference in root and it's not clear what old_component_id refers to.

To make the example clearer and self-consistent, I recommend showing the deletion of an existing component from the example, like user_title. You would also need to update the root component to remove user_title from its children array for a fully consistent state.

Here is a suggestion to make the intent clearer, though the root component would still need to be updated separately.

Suggested change
{
"id": "user_title",
"component": "Text",
"text": "Software Engineer"
"id": "old_component_id",
"component": null
}
"id": "user_title",
"component": null

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a valid issue don't remove user_title: it's referenced.

]
}
Expand Down
1 change: 1 addition & 0 deletions specification/v0_9/eval/src/generation_flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions specification/v0_9/eval/src/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions specification/v0_9/eval/src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
21 changes: 20 additions & 1 deletion specification/v0_9/json/server_to_client.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
}
}
},
Expand Down
70 changes: 70 additions & 0 deletions specification/v0_9/test/cases/component_deletion.json
Original file line number Diff line number Diff line change
@@ -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
}
]
}
}
}
]
}
Loading