From 133685f0bb52cd331d4bc970109c2596ebc70e7e Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 14 May 2026 12:50:00 -0700 Subject: [PATCH 1/6] fix: surface catalog ID in PromptBuilder --- packages/genui/lib/src/facade/prompt_builder.dart | 2 ++ packages/genui/test/facade/prompt_builder_test.dart | 11 +++++++++++ .../all_operations_with_dataModel_false.txt | 4 ++++ .../all_operations_with_dataModel_true.txt | 4 ++++ .../create_and_update_with_dataModel_false.txt | 4 ++++ .../create_and_update_with_dataModel_true.txt | 4 ++++ .../create_only_with_dataModel_false.txt | 4 ++++ .../create_only_with_dataModel_true.txt | 4 ++++ .../update_only_with_dataModel_false.txt | 4 ++++ .../update_only_with_dataModel_true.txt | 4 ++++ 10 files changed, 45 insertions(+) diff --git a/packages/genui/lib/src/facade/prompt_builder.dart b/packages/genui/lib/src/facade/prompt_builder.dart index 77ecada7c..1ca4a586c 100644 --- a/packages/genui/lib/src/facade/prompt_builder.dart +++ b/packages/genui/lib/src/facade/prompt_builder.dart @@ -366,6 +366,8 @@ final class _BasicPromptBuilder extends PromptBuilder { final fragments = [ ...systemPromptFragments, 'Use the provided tools to respond to user using rich UI elements.', + if (catalog.catalogId != null) + 'The active catalog ID is: "${catalog.catalogId}".', ...technicalPossibilities.systemPromptFragment(), ...catalog.systemPromptFragments, ...allowedOperations.systemPromptFragments, diff --git a/packages/genui/test/facade/prompt_builder_test.dart b/packages/genui/test/facade/prompt_builder_test.dart index dc573fdc5..954ae8e78 100644 --- a/packages/genui/test/facade/prompt_builder_test.dart +++ b/packages/genui/test/facade/prompt_builder_test.dart @@ -123,4 +123,15 @@ void main() { }); } }); + + group('Catalog ID', () { + test('is surfaced in system prompt when provided', () { + final catalog = Catalog([ + BasicCatalogItems.text, + ], catalogId: 'my_custom_catalog'); + final builder = PromptBuilder.chat(catalog: catalog); + final String prompt = builder.systemPromptJoined(); + expect(prompt, contains('The active catalog ID is: "my_custom_catalog"')); + }); + }); } 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 55f3a5978..82a145d6c 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 @@ -16,6 +16,10 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- +The active catalog ID is: "test_catalog". + +------------------------------------- + IMPORTANT: You do not have the ability to execute code. If you need to perform calculations, do them yourself. ------------------------------------- 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 37e522f94..28d04fda5 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 @@ -16,6 +16,10 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- +The active catalog ID is: "test_catalog". + +------------------------------------- + IMPORTANT: You do not have the ability to execute code. If you need to perform calculations, do them yourself. ------------------------------------- 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 6dd36efd5..75f9e82d8 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 @@ -16,6 +16,10 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- +The active catalog ID is: "test_catalog". + +------------------------------------- + IMPORTANT: You do not have the ability to execute code. If you need to perform calculations, do them yourself. ------------------------------------- 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 cdc0a60ad..233fb54d3 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 @@ -16,6 +16,10 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- +The active catalog ID is: "test_catalog". + +------------------------------------- + IMPORTANT: You do not have the ability to execute code. If you need to perform calculations, do them yourself. ------------------------------------- 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 fda9dd04c..2cfdb9b42 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 @@ -16,6 +16,10 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- +The active catalog ID is: "test_catalog". + +------------------------------------- + IMPORTANT: You do not have the ability to execute code. If you need to perform calculations, do them yourself. ------------------------------------- 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 75f6319ff..ff26e08ad 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 @@ -16,6 +16,10 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- +The active catalog ID is: "test_catalog". + +------------------------------------- + IMPORTANT: You do not have the ability to execute code. If you need to perform calculations, do them yourself. ------------------------------------- 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 ca6bf4884..effed272a 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 @@ -16,6 +16,10 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- +The active catalog ID is: "test_catalog". + +------------------------------------- + IMPORTANT: You do not have the ability to execute code. If you need to perform calculations, do them yourself. ------------------------------------- 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 e04604e4f..a68662989 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 @@ -16,6 +16,10 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- +The active catalog ID is: "test_catalog". + +------------------------------------- + IMPORTANT: You do not have the ability to execute code. If you need to perform calculations, do them yourself. ------------------------------------- From 061a5bccc088839fb9e95e00e019d32ab15c4be6 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 14 May 2026 13:34:21 -0700 Subject: [PATCH 2/6] fix: sanitize catalog ID and add tests for null case in PromptBuilder --- .../genui/lib/src/facade/prompt_builder.dart | 2 +- .../test/facade/prompt_builder_test.dart | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/genui/lib/src/facade/prompt_builder.dart b/packages/genui/lib/src/facade/prompt_builder.dart index 1ca4a586c..9ddd6fa80 100644 --- a/packages/genui/lib/src/facade/prompt_builder.dart +++ b/packages/genui/lib/src/facade/prompt_builder.dart @@ -367,7 +367,7 @@ final class _BasicPromptBuilder extends PromptBuilder { ...systemPromptFragments, 'Use the provided tools to respond to user using rich UI elements.', if (catalog.catalogId != null) - 'The active catalog ID is: "${catalog.catalogId}".', + 'The active catalog ID is: "${catalog.catalogId!.replaceAll('"', '\\"').replaceAll('\n', '\\n')}".', ...technicalPossibilities.systemPromptFragment(), ...catalog.systemPromptFragments, ...allowedOperations.systemPromptFragments, diff --git a/packages/genui/test/facade/prompt_builder_test.dart b/packages/genui/test/facade/prompt_builder_test.dart index 954ae8e78..bb4933f07 100644 --- a/packages/genui/test/facade/prompt_builder_test.dart +++ b/packages/genui/test/facade/prompt_builder_test.dart @@ -133,5 +133,24 @@ void main() { final String prompt = builder.systemPromptJoined(); expect(prompt, contains('The active catalog ID is: "my_custom_catalog"')); }); + + test('is not surfaced in system prompt when not provided', () { + final catalog = Catalog([BasicCatalogItems.text]); + final builder = PromptBuilder.chat(catalog: catalog); + final String prompt = builder.systemPromptJoined(); + expect(prompt, isNot(contains('The active catalog ID is:'))); + }); + + test('is sanitized in system prompt', () { + final catalog = Catalog([ + BasicCatalogItems.text, + ], catalogId: 'my_custom_\ncatalog"'); + final builder = PromptBuilder.chat(catalog: catalog); + final String prompt = builder.systemPromptJoined(); + expect( + prompt, + contains('The active catalog ID is: "my_custom_\\ncatalog\\"".'), + ); + }); }); } From ff3caa6a78b70311437023b3714ebcb2d29cde1f Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 14 May 2026 18:00:13 -0700 Subject: [PATCH 3/6] Fix lint --- packages/genui/lib/src/facade/prompt_builder.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/genui/lib/src/facade/prompt_builder.dart b/packages/genui/lib/src/facade/prompt_builder.dart index 9ddd6fa80..2dd8d0f54 100644 --- a/packages/genui/lib/src/facade/prompt_builder.dart +++ b/packages/genui/lib/src/facade/prompt_builder.dart @@ -363,11 +363,15 @@ final class _BasicPromptBuilder extends PromptBuilder { catalog, ).toJson(indent: ' '); + String? activeCatalogId = catalog.catalogId + ?.replaceAll('"', '\\"') + .replaceAll('\n', '\\n'); + final fragments = [ ...systemPromptFragments, 'Use the provided tools to respond to user using rich UI elements.', - if (catalog.catalogId != null) - 'The active catalog ID is: "${catalog.catalogId!.replaceAll('"', '\\"').replaceAll('\n', '\\n')}".', + if (activeCatalogId != null) + 'The active catalog ID is: "$activeCatalogId".', ...technicalPossibilities.systemPromptFragment(), ...catalog.systemPromptFragments, ...allowedOperations.systemPromptFragments, From 5d1502f2c152a794f1bbbfee27bada385b08897a Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 15 May 2026 14:41:05 -0700 Subject: [PATCH 4/6] Fix formatting --- packages/genui/test/functions/format_string_test.dart | 1 + packages/genui/test/model/catalog_exception_test.dart | 1 + pubspec.yaml | 3 +-- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/genui/test/functions/format_string_test.dart b/packages/genui/test/functions/format_string_test.dart index 979af193d..144ffbf6c 100644 --- a/packages/genui/test/functions/format_string_test.dart +++ b/packages/genui/test/functions/format_string_test.dart @@ -8,6 +8,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:genui/src/catalog/basic_catalog.dart'; import 'package:genui/src/functions/format_string.dart'; import 'package:genui/src/model/data_model.dart'; + // import 'package:genui/src/primitives/simple_items.dart'; // Unused void main() { diff --git a/packages/genui/test/model/catalog_exception_test.dart b/packages/genui/test/model/catalog_exception_test.dart index 61a17fda1..98b982639 100644 --- a/packages/genui/test/model/catalog_exception_test.dart +++ b/packages/genui/test/model/catalog_exception_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:genui/genui.dart'; + // import 'package:genui/src/model/catalog.dart'; // Exceptions should be exported by genui.dart, but if not we might need this. // Assuming CatalogItemNotFoundException is exported or available. diff --git a/pubspec.yaml b/pubspec.yaml index 230463c2e..159ec24a7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,13 +15,12 @@ workspace: - examples/composer - examples/e2e - examples/simple_chat - - examples/travel_app - examples/verdure/client - packages/a2ui_core - packages/genui - packages/genui_a2a - - packages/json_schema_builder + - packages/json_schema_builder - tool/fix_copyright - tool/release From 2a5d091e51fabfc4e5b1c0567f2f5d62ed0adc12 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Mon, 18 May 2026 11:01:09 -0700 Subject: [PATCH 5/6] fix(genui): sanitize catalogId and align prompt instructions with nullable active catalog ID - Escape backslashes (`\`) and carriage returns (`\r`) inside `catalogId` in `prompt_builder.dart` to prevent quote escaping and line-break injections in system prompts. - Update `SurfaceOperations` documentation and prompt instructions to refer conditionally to the active catalog ID ("if provided"), resolving a logic inconsistency when `catalogId` is null. - Update test suite and regenerate prompt builder golden files to assert new sanitization patterns and conditional wording. --- packages/genui/lib/src/facade/prompt_builder.dart | 15 +++++++++------ .../genui/test/facade/prompt_builder_test.dart | 4 ++-- .../all_operations_with_dataModel_false.txt | 6 +++--- .../all_operations_with_dataModel_true.txt | 6 +++--- .../create_and_update_with_dataModel_false.txt | 6 +++--- .../create_and_update_with_dataModel_true.txt | 6 +++--- .../create_only_with_dataModel_false.txt | 6 +++--- .../create_only_with_dataModel_true.txt | 6 +++--- .../update_only_with_dataModel_false.txt | 2 +- .../update_only_with_dataModel_true.txt | 2 +- 10 files changed, 31 insertions(+), 28 deletions(-) diff --git a/packages/genui/lib/src/facade/prompt_builder.dart b/packages/genui/lib/src/facade/prompt_builder.dart index 2dd8d0f54..355861276 100644 --- a/packages/genui/lib/src/facade/prompt_builder.dart +++ b/packages/genui/lib/src/facade/prompt_builder.dart @@ -131,7 +131,7 @@ enum ProtocolMessages { explanation: 'Creates a new surface.', properties: ''' Requires `surfaceId` (you must always use a unique ID for each created surface), -`catalogId` (use the catalog ID provided in system instructions), +`catalogId` (use the active catalog ID if provided in system instructions), and `sendDataModel: true`. ''', // TODO: figure out why we instruct AI to always set sendDataModel: true, @@ -284,7 +284,7 @@ You can control the UI by outputting valid A2UI JSON messages wrapped in markdow if (create) ''' To create a new UI: -1. Output a ${ProtocolMessages.createSurface.tickedName} message with a unique `surfaceId` and `catalogId` (use the catalog ID provided in system instructions). +1. Output a ${ProtocolMessages.createSurface.tickedName} message with a unique `surfaceId` and `catalogId` (use the active catalog ID if provided in system instructions). 2. Output an ${ProtocolMessages.updateComponents.tickedName} message with the `surfaceId` and the component definitions. ''', if (!update) @@ -363,15 +363,18 @@ final class _BasicPromptBuilder extends PromptBuilder { catalog, ).toJson(indent: ' '); - String? activeCatalogId = catalog.catalogId - ?.replaceAll('"', '\\"') - .replaceAll('\n', '\\n'); + final String? activeCatalogId = catalog.catalogId + ?.replaceAll('\\', '\\\\') + .replaceAll('"', '\\"') + .replaceAll('\n', '\\n') + .replaceAll('\r', '\\r'); final fragments = [ ...systemPromptFragments, 'Use the provided tools to respond to user using rich UI elements.', if (activeCatalogId != null) - 'The active catalog ID is: "$activeCatalogId".', + 'The active catalog ID is: "$activeCatalogId". ' + 'You must use this catalog ID when creating surfaces.', ...technicalPossibilities.systemPromptFragment(), ...catalog.systemPromptFragments, ...allowedOperations.systemPromptFragments, diff --git a/packages/genui/test/facade/prompt_builder_test.dart b/packages/genui/test/facade/prompt_builder_test.dart index bb4933f07..9122c7a96 100644 --- a/packages/genui/test/facade/prompt_builder_test.dart +++ b/packages/genui/test/facade/prompt_builder_test.dart @@ -144,12 +144,12 @@ void main() { test('is sanitized in system prompt', () { final catalog = Catalog([ BasicCatalogItems.text, - ], catalogId: 'my_custom_\ncatalog"'); + ], catalogId: 'my_custom_\\\r\ncatalog"'); final builder = PromptBuilder.chat(catalog: catalog); final String prompt = builder.systemPromptJoined(); expect( prompt, - contains('The active catalog ID is: "my_custom_\\ncatalog\\"".'), + contains('The active catalog ID is: "my_custom_\\\\\\r\\ncatalog\\"".'), ); }); }); 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 82a145d6c..8e1a601d8 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 @@ -16,7 +16,7 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- -The active catalog ID is: "test_catalog". +The active catalog ID is: "test_catalog". You must use this catalog ID when creating surfaces. ------------------------------------- @@ -104,14 +104,14 @@ Supported messages are: `createSurface`, `updateComponents`, `deleteSurface`. Properties: - `createSurface`: Requires `surfaceId` (you must always use a unique ID for each created surface), -`catalogId` (use the catalog ID provided in system instructions), +`catalogId` (use the active catalog ID if provided in system instructions), and `sendDataModel: true`. - `updateComponents`: Requires `surfaceId` and a list of `components`. One component MUST have `id: "root"`. - `deleteSurface`: Requires `surfaceId`. To create a new UI: -1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the catalog ID provided in system instructions). +1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the active catalog ID if provided in system instructions). 2. Output an `updateComponents` message with the `surfaceId` and the component definitions. To update an existing UI: 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 28d04fda5..2a36a4df2 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 @@ -16,7 +16,7 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- -The active catalog ID is: "test_catalog". +The active catalog ID is: "test_catalog". You must use this catalog ID when creating surfaces. ------------------------------------- @@ -105,7 +105,7 @@ Supported messages are: `createSurface`, `updateComponents`, `deleteSurface`, `u Properties: - `createSurface`: Requires `surfaceId` (you must always use a unique ID for each created surface), -`catalogId` (use the catalog ID provided in system instructions), +`catalogId` (use the active catalog ID if provided in system instructions), and `sendDataModel: true`. - `updateComponents`: Requires `surfaceId` and a list of `components`. One component MUST have `id: "root"`. @@ -113,7 +113,7 @@ One component MUST have `id: "root"`. - `updateDataModel`: Requires `surfaceId`, `path` and `value`. To create a new UI: -1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the catalog ID provided in system instructions). +1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the active catalog ID if provided in system instructions). 2. Output an `updateComponents` message with the `surfaceId` and the component definitions. To update an existing UI: 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 75f9e82d8..b657c8cb9 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 @@ -16,7 +16,7 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- -The active catalog ID is: "test_catalog". +The active catalog ID is: "test_catalog". You must use this catalog ID when creating surfaces. ------------------------------------- @@ -103,13 +103,13 @@ Supported messages are: `createSurface`, `updateComponents`. Properties: - `createSurface`: Requires `surfaceId` (you must always use a unique ID for each created surface), -`catalogId` (use the catalog ID provided in system instructions), +`catalogId` (use the active catalog ID if provided in system instructions), and `sendDataModel: true`. - `updateComponents`: Requires `surfaceId` and a list of `components`. One component MUST have `id: "root"`. To create a new UI: -1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the catalog ID provided in system instructions). +1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the active catalog ID if provided in system instructions). 2. Output an `updateComponents` message with the `surfaceId` and the component definitions. To update an existing UI: 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 233fb54d3..0d696fa02 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 @@ -16,7 +16,7 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- -The active catalog ID is: "test_catalog". +The active catalog ID is: "test_catalog". You must use this catalog ID when creating surfaces. ------------------------------------- @@ -104,14 +104,14 @@ Supported messages are: `createSurface`, `updateComponents`, `updateDataModel`. Properties: - `createSurface`: Requires `surfaceId` (you must always use a unique ID for each created surface), -`catalogId` (use the catalog ID provided in system instructions), +`catalogId` (use the active catalog ID if provided in system instructions), and `sendDataModel: true`. - `updateComponents`: Requires `surfaceId` and a list of `components`. One component MUST have `id: "root"`. - `updateDataModel`: Requires `surfaceId`, `path` and `value`. To create a new UI: -1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the catalog ID provided in system instructions). +1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the active catalog ID if provided in system instructions). 2. Output an `updateComponents` message with the `surfaceId` and the component definitions. To update an existing UI: 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 2cfdb9b42..f7f56748c 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 @@ -16,7 +16,7 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- -The active catalog ID is: "test_catalog". +The active catalog ID is: "test_catalog". You must use this catalog ID when creating surfaces. ------------------------------------- @@ -103,13 +103,13 @@ Supported messages are: `createSurface`, `updateComponents`. Properties: - `createSurface`: Requires `surfaceId` (you must always use a unique ID for each created surface), -`catalogId` (use the catalog ID provided in system instructions), +`catalogId` (use the active catalog ID if provided in system instructions), and `sendDataModel: true`. - `updateComponents`: Requires `surfaceId` and a list of `components`. One component MUST have `id: "root"`. To create a new UI: -1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the catalog ID provided in system instructions). +1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the active catalog ID if provided in system instructions). 2. Output an `updateComponents` message with the `surfaceId` and the component definitions. IMPORTANT: DO NOT update or modify surfaces created in previous turns. If the UI needs to change, you MUST create a NEW surface with a new unique `surfaceId`. You may only use `updateComponents` to populate the components of a freshly created surface. 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 ff26e08ad..cb4b795d6 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 @@ -16,7 +16,7 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- -The active catalog ID is: "test_catalog". +The active catalog ID is: "test_catalog". You must use this catalog ID when creating surfaces. ------------------------------------- @@ -104,14 +104,14 @@ Supported messages are: `createSurface`, `updateComponents`, `updateDataModel`. Properties: - `createSurface`: Requires `surfaceId` (you must always use a unique ID for each created surface), -`catalogId` (use the catalog ID provided in system instructions), +`catalogId` (use the active catalog ID if provided in system instructions), and `sendDataModel: true`. - `updateComponents`: Requires `surfaceId` and a list of `components`. One component MUST have `id: "root"`. - `updateDataModel`: Requires `surfaceId`, `path` and `value`. To create a new UI: -1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the catalog ID provided in system instructions). +1. Output a `createSurface` message with a unique `surfaceId` and `catalogId` (use the active catalog ID if provided in system instructions). 2. Output an `updateComponents` message with the `surfaceId` and the component definitions. IMPORTANT: DO NOT update or modify surfaces created in previous turns. If the UI needs to change, you MUST create a NEW surface with a new unique `surfaceId`. You may only use `updateComponents` to populate the components of a freshly created surface. 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 effed272a..c535e9b4d 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 @@ -16,7 +16,7 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- -The active catalog ID is: "test_catalog". +The active catalog ID is: "test_catalog". You must use this catalog ID when creating surfaces. ------------------------------------- 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 a68662989..72224b23a 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 @@ -16,7 +16,7 @@ Use the provided tools to respond to user using rich UI elements. ------------------------------------- -The active catalog ID is: "test_catalog". +The active catalog ID is: "test_catalog". You must use this catalog ID when creating surfaces. ------------------------------------- From 5921e4374376043e7e4b2c9e7825a02c9f54e048 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 20 May 2026 16:56:15 -0700 Subject: [PATCH 6/6] fix(genui): treat catalogId as an arbitrary string instead of a URI --- .../linux/flutter/generated_plugins.cmake | 1 + .../macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- .../composer/linux/flutter/generated_plugins.cmake | 1 + .../macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- .../composer/windows/flutter/generated_plugins.cmake | 1 + .../linux/flutter/generated_plugins.cmake | 1 + .../macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- .../windows/flutter/generated_plugins.cmake | 1 + .../client/linux/flutter/generated_plugins.cmake | 1 + .../macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- packages/genui/lib/src/facade/prompt_builder.dart | 6 +----- packages/genui/lib/src/model/a2ui_schemas.dart | 7 ++++++- packages/genui/test/facade/prompt_builder_test.dart | 12 ------------ .../all_operations_with_dataModel_false.txt | 2 +- .../all_operations_with_dataModel_true.txt | 2 +- .../create_and_update_with_dataModel_false.txt | 2 +- .../create_and_update_with_dataModel_true.txt | 2 +- .../create_only_with_dataModel_false.txt | 2 +- .../create_only_with_dataModel_true.txt | 2 +- .../update_only_with_dataModel_false.txt | 2 +- .../update_only_with_dataModel_true.txt | 2 +- 21 files changed, 25 insertions(+), 30 deletions(-) diff --git a/dev_tools/catalog_gallery/linux/flutter/generated_plugins.cmake b/dev_tools/catalog_gallery/linux/flutter/generated_plugins.cmake index 8e2a1900c..a2eef970f 100644 --- a/dev_tools/catalog_gallery/linux/flutter/generated_plugins.cmake +++ b/dev_tools/catalog_gallery/linux/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/dev_tools/catalog_gallery/macos/Flutter/GeneratedPluginRegistrant.swift b/dev_tools/catalog_gallery/macos/Flutter/GeneratedPluginRegistrant.swift index ad1073ab5..7b265476e 100644 --- a/dev_tools/catalog_gallery/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/dev_tools/catalog_gallery/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,5 +12,5 @@ import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) + VideoPlayerPlugin.register(with: registry.registrar(forPlugin: "VideoPlayerPlugin")) } diff --git a/dev_tools/composer/linux/flutter/generated_plugins.cmake b/dev_tools/composer/linux/flutter/generated_plugins.cmake index c085ca836..a1bc1781f 100644 --- a/dev_tools/composer/linux/flutter/generated_plugins.cmake +++ b/dev_tools/composer/linux/flutter/generated_plugins.cmake @@ -10,6 +10,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/dev_tools/composer/macos/Flutter/GeneratedPluginRegistrant.swift b/dev_tools/composer/macos/Flutter/GeneratedPluginRegistrant.swift index 802a33c8d..4825c9db2 100644 --- a/dev_tools/composer/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/dev_tools/composer/macos/Flutter/GeneratedPluginRegistrant.swift @@ -15,6 +15,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) + VideoPlayerPlugin.register(with: registry.registrar(forPlugin: "VideoPlayerPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/dev_tools/composer/windows/flutter/generated_plugins.cmake b/dev_tools/composer/windows/flutter/generated_plugins.cmake index f74fccabd..79fabcfb8 100644 --- a/dev_tools/composer/windows/flutter/generated_plugins.cmake +++ b/dev_tools/composer/windows/flutter/generated_plugins.cmake @@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/examples/simple_chat/linux/flutter/generated_plugins.cmake b/examples/simple_chat/linux/flutter/generated_plugins.cmake index 8e2a1900c..a2eef970f 100644 --- a/examples/simple_chat/linux/flutter/generated_plugins.cmake +++ b/examples/simple_chat/linux/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/examples/simple_chat/macos/Flutter/GeneratedPluginRegistrant.swift b/examples/simple_chat/macos/Flutter/GeneratedPluginRegistrant.swift index ad1073ab5..7b265476e 100644 --- a/examples/simple_chat/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/examples/simple_chat/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,5 +12,5 @@ import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) + VideoPlayerPlugin.register(with: registry.registrar(forPlugin: "VideoPlayerPlugin")) } diff --git a/examples/simple_chat/windows/flutter/generated_plugins.cmake b/examples/simple_chat/windows/flutter/generated_plugins.cmake index 97b61367a..158064786 100644 --- a/examples/simple_chat/windows/flutter/generated_plugins.cmake +++ b/examples/simple_chat/windows/flutter/generated_plugins.cmake @@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/examples/verdure/client/linux/flutter/generated_plugins.cmake b/examples/verdure/client/linux/flutter/generated_plugins.cmake index 04f81f4b4..ac700e247 100644 --- a/examples/verdure/client/linux/flutter/generated_plugins.cmake +++ b/examples/verdure/client/linux/flutter/generated_plugins.cmake @@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/examples/verdure/client/macos/Flutter/GeneratedPluginRegistrant.swift b/examples/verdure/client/macos/Flutter/GeneratedPluginRegistrant.swift index 074b04b4c..46b142243 100644 --- a/examples/verdure/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/examples/verdure/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -16,5 +16,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) + VideoPlayerPlugin.register(with: registry.registrar(forPlugin: "VideoPlayerPlugin")) } diff --git a/packages/genui/lib/src/facade/prompt_builder.dart b/packages/genui/lib/src/facade/prompt_builder.dart index 355861276..9abb5211d 100644 --- a/packages/genui/lib/src/facade/prompt_builder.dart +++ b/packages/genui/lib/src/facade/prompt_builder.dart @@ -363,11 +363,7 @@ final class _BasicPromptBuilder extends PromptBuilder { catalog, ).toJson(indent: ' '); - final String? activeCatalogId = catalog.catalogId - ?.replaceAll('\\', '\\\\') - .replaceAll('"', '\\"') - .replaceAll('\n', '\\n') - .replaceAll('\r', '\\r'); + final String? activeCatalogId = catalog.catalogId; final fragments = [ ...systemPromptFragments, diff --git a/packages/genui/lib/src/model/a2ui_schemas.dart b/packages/genui/lib/src/model/a2ui_schemas.dart index 29ba6feca..dbfc4835b 100644 --- a/packages/genui/lib/src/model/a2ui_schemas.dart +++ b/packages/genui/lib/src/model/a2ui_schemas.dart @@ -436,7 +436,12 @@ abstract final class A2uiSchemas { 'the component tree.', properties: { surfaceIdKey: S.string(description: 'The unique ID for the surface.'), - 'catalogId': S.string(description: 'The URI of the component catalog.'), + 'catalogId': S.string( + description: + 'A string that uniquely identifies this catalog. This is typically ' + 'a URI, but the URI is never accessed, only used as a unique ' + 'identifier.', + ), 'theme': S.object( description: 'Theme parameters for the surface.', additionalProperties: true, diff --git a/packages/genui/test/facade/prompt_builder_test.dart b/packages/genui/test/facade/prompt_builder_test.dart index 9122c7a96..cbb04f1ef 100644 --- a/packages/genui/test/facade/prompt_builder_test.dart +++ b/packages/genui/test/facade/prompt_builder_test.dart @@ -140,17 +140,5 @@ void main() { final String prompt = builder.systemPromptJoined(); expect(prompt, isNot(contains('The active catalog ID is:'))); }); - - test('is sanitized in system prompt', () { - final catalog = Catalog([ - BasicCatalogItems.text, - ], catalogId: 'my_custom_\\\r\ncatalog"'); - final builder = PromptBuilder.chat(catalog: catalog); - final String prompt = builder.systemPromptJoined(); - expect( - prompt, - contains('The active catalog ID is: "my_custom_\\\\\\r\\ncatalog\\"".'), - ); - }); }); } 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 8e1a601d8..3d27339c3 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 @@ -151,7 +151,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one }, "catalogId": { "type": "string", - "description": "The URI of the component catalog." + "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": { "type": "object", 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 2a36a4df2..7661b3a97 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 @@ -153,7 +153,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one }, "catalogId": { "type": "string", - "description": "The URI of the component catalog." + "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": { "type": "object", 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 b657c8cb9..dd3adc395 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 @@ -149,7 +149,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one }, "catalogId": { "type": "string", - "description": "The URI of the component catalog." + "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": { "type": "object", 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 0d696fa02..c23b0e23b 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 @@ -151,7 +151,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one }, "catalogId": { "type": "string", - "description": "The URI of the component catalog." + "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": { "type": "object", 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 f7f56748c..d35089698 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 @@ -148,7 +148,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one }, "catalogId": { "type": "string", - "description": "The URI of the component catalog." + "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": { "type": "object", 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 cb4b795d6..0ae7dfeed 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 @@ -150,7 +150,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one }, "catalogId": { "type": "string", - "description": "The URI of the component catalog." + "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": { "type": "object", 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 c535e9b4d..6f63a173a 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 @@ -141,7 +141,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one }, "catalogId": { "type": "string", - "description": "The URI of the component catalog." + "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": { "type": "object", 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 72224b23a..d85d3f55f 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 @@ -143,7 +143,7 @@ When constructing UI, you must output a VALID A2UI JSON object representing one }, "catalogId": { "type": "string", - "description": "The URI of the component catalog." + "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": { "type": "object",