Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
0ed51f3
feat(a2ui_core): tighten protocol parsing, expose effect, copy mutabl…
andrewkolos May 26, 2026
63ff2ef
refactor(genui): rebase data substrate on a2ui_core.DataModel (compat…
andrewkolos May 26, 2026
7e26a0b
refactor(genui): live a2ui_core surface state behind SurfaceDefinitio…
andrewkolos May 26, 2026
62bd418
refactor(genui): delegate SurfaceController to a2ui_core.MessageProce…
andrewkolos May 26, 2026
967e146
docs(migration): add migration guide for genui -> a2ui_core swap
andrewkolos May 26, 2026
5e6c5fd
chore: trim comments
andrewkolos May 28, 2026
af0bb83
refactor(genui): preserve pre-create dataModel access; reduce core le…
andrewkolos May 28, 2026
3758fe3
refactor(genui): address reviewer pass on substrate migration
andrewkolos May 28, 2026
df4695a
refactor(genui): preserve pre-migration SurfaceRegistry public API
andrewkolos May 28, 2026
0d3b9d3
refactor(genui): preserve registry event public constructors
andrewkolos May 28, 2026
186a8c4
refactor(genui): preserve public SurfaceUpdate constructor signatures
andrewkolos May 28, 2026
4427332
chore(genui): const on public SurfaceUpdate ctors; CHANGELOG note for…
andrewkolos May 28, 2026
70ab86a
docs(genui): clarify CHANGELOG wording on addSurface/notifyUpdated
andrewkolos May 28, 2026
482a71a
refactor(genui): break ui_models/schema_validation import cycle
andrewkolos Jun 4, 2026
cd6e937
fix(genui): correct user-action detection and empty-surfaceId rejection
andrewkolos Jun 4, 2026
fb94089
test(genui): cover live rebuild + duplicate-create, re-baseline coverage
andrewkolos Jun 4, 2026
9f879bb
refactor(genui): render surfaces from the definition snapshot only
andrewkolos Jun 4, 2026
5a4709e
refactor(genui): consume a2ui_core message types directly
andrewkolos Jun 4, 2026
5b79216
refactor(genui_a2a,catalog_gallery,verdure): consume a2ui_core messag…
andrewkolos Jun 4, 2026
325955b
refactor(genui): remove the DataModelStore facade
andrewkolos Jun 4, 2026
dfbd5bc
docs(genui,genui_a2a): correct CHANGELOG for the a2ui_core message mi…
andrewkolos Jun 4, 2026
93af987
docs(genui): rewrite the a2ui_core migration guide to match reality
andrewkolos Jun 4, 2026
8c76afa
refactor(genui): restore DataPath typing on ExecutionContext/DataContext
andrewkolos Jun 4, 2026
44e65f1
refactor(genui): remove dead granular-rebuild plumbing
andrewkolos Jun 4, 2026
7ebb12b
docs(genui): trim migration guide and CHANGELOG to user-facing changes
andrewkolos Jun 4, 2026
65fab55
fix(simple_chat,composer): migrate to a2ui_core message types
andrewkolos Jun 4, 2026
fa9f087
docs(genui): tighten a2ui_core migration guide
andrewkolos Jun 16, 2026
b4cb2cc
refactor(a2ui_core): drop unused UpdateDataModelMessage hasValue
andrewkolos Jun 16, 2026
7a3c568
docs: trim changelogs to consumer-facing changes
andrewkolos Jun 16, 2026
e10ac97
docs(genui): name the a2ui_core migration guide by version
andrewkolos Jun 16, 2026
a13a660
refactor(genui): drop the signal layer from BoundValue
andrewkolos Jun 16, 2026
46e6731
refactor(genui): revert CatalogItemContext to a flat field bag
andrewkolos Jun 16, 2026
a703c40
refactor(genui): render Surface via ValueListenableBuilder
andrewkolos Jun 16, 2026
fc1d9d1
refactor(genui): registry events carry only the surface model
andrewkolos Jun 16, 2026
9626aab
docs: drop migration-narrative comments left in the code
andrewkolos Jun 16, 2026
1379e2f
refactor(genui): remove dead Component.toCoreJson
andrewkolos Jun 16, 2026
2da7493
refactor(genui): restore _buildWidget method name
andrewkolos Jun 16, 2026
c223688
refactor(genui): drop redundant watchDefinition alias, keep watchSurface
andrewkolos Jun 16, 2026
327042c
refactor(genui): restore data_model.dart layout, inline bindExternalS…
andrewkolos Jun 16, 2026
4e85e6d
refactor(genui): dispose the per-id notifier on removeSurface
andrewkolos Jun 16, 2026
bb50a98
refactor(genui): key built widgets in catalog.buildWidget
andrewkolos Jun 16, 2026
014397b
chore(a2ui_core): drop changes not required by the genui migration
andrewkolos Jun 16, 2026
c8bb06f
chore(a2ui_core): revert messages.dart to main (toJson/doc not needed…
andrewkolos Jun 16, 2026
419fcad
chore(genui): revert format_string to main (gratuitous dependency-set…
andrewkolos Jun 16, 2026
a9c31da
chore(genui): revert UiPart.create to a SurfaceDefinition param (Obje…
andrewkolos Jun 16, 2026
af31e9c
docs(genui): drop "substrate" jargon from comments
andrewkolos Jun 16, 2026
860a867
docs(genui): "migrate"->"copy" in surface_controller data-model comments
andrewkolos Jun 16, 2026
77fd2cb
fix(genui): only treat {path: <String>} as a binding in BoundValue
andrewkolos Jun 16, 2026
a1301dc
fix(genui): report the bound path in DataModelTypeException
andrewkolos Jun 16, 2026
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
2 changes: 1 addition & 1 deletion coverage_baseline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ dev_tools/catalog_gallery: 52.91
dev_tools/composer: 20.49
packages/a2ui_core: 76.30
packages/genai_primitives: 100.00
packages/genui: 79.71
packages/genui: 79.38
packages/genui_a2a: 91.37
packages/json_schema_builder: 79.09
tool/e2e: 100.00
Expand Down
8 changes: 4 additions & 4 deletions dev_tools/catalog_gallery/lib/sample_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

import 'dart:convert';

import 'package:a2ui_core/a2ui_core.dart' as core;
import 'package:file/file.dart';
import 'package:genui/genui.dart';
import 'package:yaml/yaml.dart';

class Sample {
final String name;
final String description;
final Stream<A2uiMessage> messages;
final Stream<core.A2uiMessage> messages;

Sample({
required this.name,
Expand Down Expand Up @@ -52,14 +52,14 @@ class SampleParser {
final String name = header['name'] as String? ?? 'Untitled Sample';
final String description = header['description'] as String? ?? '';

final Stream<A2uiMessage> messages = Stream.fromIterable(
final Stream<core.A2uiMessage> messages = Stream.fromIterable(
const LineSplitter()
.convert(jsonlBody)
.where((line) => line.trim().isNotEmpty)
.map((line) {
final dynamic json = jsonDecode(line);
if (json is Map<String, dynamic>) {
return A2uiMessage.fromJson(json);
return core.A2uiMessage.fromJson(json);
}
throw FormatException('Invalid JSON line: $line');
}),
Expand Down
3 changes: 2 additions & 1 deletion dev_tools/catalog_gallery/lib/samples_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:async';

import 'package:a2ui_core/a2ui_core.dart' as core;
import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -35,7 +36,7 @@ class _SamplesViewState extends State<SamplesView> {
final List<String> _surfaceIds = [];
int _currentSurfaceIndex = 0;
StreamSubscription<SurfaceUpdate>? _surfaceSubscription;
StreamSubscription<A2uiMessage>? _messageSubscription;
StreamSubscription<core.A2uiMessage>? _messageSubscription;

@override
void initState() {
Expand Down
1 change: 1 addition & 0 deletions dev_tools/catalog_gallery/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ environment:
resolution: workspace

dependencies:
a2ui_core: ^0.0.1-wip002
args: ^2.7.0
file: ^7.0.1
flutter:
Expand Down
9 changes: 5 additions & 4 deletions dev_tools/catalog_gallery/test/layout_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:io';

import 'package:a2ui_core/a2ui_core.dart' as core;
import 'package:catalog_gallery/sample_parser.dart';
import 'package:file/file.dart' as file_pkg;
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -38,13 +39,13 @@ void main() {
catalogs: [BasicCatalogItems.asCatalog()],
);

await for (final A2uiMessage message in sample.messages) {
await for (final core.A2uiMessage message in sample.messages) {
var messageToProcess = message;
if (message is CreateSurface) {
if (message is core.CreateSurfaceMessage) {
// We manually inject the basic catalog since createSurface might ref
// external URL in this test environment, we just assume the basic
// catalog is available
messageToProcess = CreateSurface(
messageToProcess = core.CreateSurfaceMessage(
surfaceId: message.surfaceId,
catalogId: basicCatalogId,
theme: message.theme,
Expand Down Expand Up @@ -81,7 +82,7 @@ void main() {
catalogs: [BasicCatalogItems.asCatalog()],
);

await for (final A2uiMessage message in sample.messages) {
await for (final core.A2uiMessage message in sample.messages) {
controller.handleMessage(message);
}

Expand Down
20 changes: 10 additions & 10 deletions dev_tools/catalog_gallery/test/sample_parser_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:a2ui_core/a2ui_core.dart' as core;
import 'package:catalog_gallery/sample_parser.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:genui/genui.dart';

void main() {
test('SampleParser parses valid sample string', () async {
Expand All @@ -21,19 +21,19 @@ description: A test description
expect(sample.name, 'Test Sample');
expect(sample.description, 'A test description');

final List<A2uiMessage> messages = await sample.messages.toList();
final List<core.A2uiMessage> messages = await sample.messages.toList();
expect(messages.length, 2);
expect(messages.first, isA<UpdateComponents>());
expect(messages.last, isA<CreateSurface>());
expect(messages.first, isA<core.UpdateComponentsMessage>());
expect(messages.last, isA<core.CreateSurfaceMessage>());

final update = messages.first as UpdateComponents;
final update = messages.first as core.UpdateComponentsMessage;
expect(update.surfaceId, 'default');
expect(update.components.length, 1);
expect(update.components.first.type, 'Text');
expect(update.components.first['component'], 'Text');

final begin = messages.last as CreateSurface;
final begin = messages.last as core.CreateSurfaceMessage;
expect(begin.surfaceId, 'default');
// begin.root check removed as it doesn't exist in CreateSurface
// begin.root check removed as it doesn't exist in CreateSurfaceMessage
});

test(
Expand All @@ -48,7 +48,7 @@ description: A description
''';
final Sample sample = SampleParser.parseString(sampleContent);
expect(sample.name, 'Frontmatter Sample');
final List<A2uiMessage> messages = await sample.messages.toList();
final List<core.A2uiMessage> messages = await sample.messages.toList();
expect(messages.length, 1);
},
);
Expand All @@ -61,7 +61,7 @@ description: A description
''';
final Sample sample = SampleParser.parseString(sampleContent);
expect(sample.name, 'Untitled Sample');
final List<A2uiMessage> messages = await sample.messages.toList();
final List<core.A2uiMessage> messages = await sample.messages.toList();
expect(messages.length, 1);
});

Expand Down
3 changes: 2 additions & 1 deletion dev_tools/catalog_gallery/test/samples_rendering_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:io';

import 'package:a2ui_core/a2ui_core.dart' as core;
import 'package:catalog_gallery/sample_parser.dart';
import 'package:file/file.dart';
import 'package:file/local.dart';
Expand Down Expand Up @@ -78,7 +79,7 @@ void main() {
);

try {
await for (final A2uiMessage message in sample.messages) {
await for (final core.A2uiMessage message in sample.messages) {
controller.handleMessage(message);
await tester.pump();
}
Expand Down
3 changes: 2 additions & 1 deletion dev_tools/composer/lib/ai_client_transport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:async';

import 'package:a2ui_core/a2ui_core.dart' as core;
import 'package:dartantic_ai/dartantic_ai.dart' as dartantic;
import 'package:genui/genui.dart';
import 'package:logging/logging.dart';
Expand All @@ -20,7 +21,7 @@ class AiClientTransport implements Transport {
final Logger _logger = Logger('AiClientTransport');

@override
Stream<A2uiMessage> get incomingMessages => _adapter.incomingMessages;
Stream<core.A2uiMessage> get incomingMessages => _adapter.incomingMessages;

@override
Stream<String> get incomingText => _adapter.incomingText;
Expand Down
8 changes: 4 additions & 4 deletions dev_tools/composer/lib/sample_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

import 'dart:convert';

import 'package:genui/genui.dart';
import 'package:a2ui_core/a2ui_core.dart' as core;
import 'package:yaml/yaml.dart';

/// A parsed sample containing metadata and a stream of A2UI messages.
class Sample {
final String name;
final String description;
final String rawJsonl;
final Stream<A2uiMessage> messages;
final Stream<core.A2uiMessage> messages;

Sample({
required this.name,
Expand Down Expand Up @@ -50,14 +50,14 @@ class SampleParser {
final String name = header['name'] as String? ?? 'Untitled Sample';
final String description = header['description'] as String? ?? '';

final Stream<A2uiMessage> messages = Stream.fromIterable(
final Stream<core.A2uiMessage> messages = Stream.fromIterable(
const LineSplitter()
.convert(jsonlBody)
.where((line) => line.trim().isNotEmpty)
.map((line) {
final Object? json = jsonDecode(line);
if (json is Map<String, Object?>) {
return A2uiMessage.fromJson(json);
return core.A2uiMessage.fromJson(json);
}
throw FormatException('Invalid JSON line: $line');
}),
Expand Down
11 changes: 7 additions & 4 deletions dev_tools/composer/lib/surface_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'dart:async';
import 'dart:convert';

import 'package:a2ui_core/a2ui_core.dart' as core;
import 'package:flutter/material.dart';
import 'package:flutter_code_editor/flutter_code_editor.dart';
import 'package:flutter_highlight/themes/vs.dart';
Expand Down Expand Up @@ -150,7 +151,9 @@ class _SurfaceEditorViewState extends State<SurfaceEditorView> {
_dataModelNotifier?.removeListener(_onDataModelChanged);
if (_surfaceIds.isEmpty) return;

final dataModel = _surfaceController.store.getDataModel(_surfaceIds.first);
final dataModel = _surfaceController
.contextFor(_surfaceIds.first)
.dataModel;
_dataModelNotifier = dataModel.subscribe<Object?>(DataPath.root);
_dataModelNotifier!.addListener(_onDataModelChanged);
}
Expand All @@ -164,7 +167,7 @@ class _SurfaceEditorViewState extends State<SurfaceEditorView> {
if (_surfaceIds.isEmpty) return;

final surfaceId = _surfaceIds.first;
final dataModel = _surfaceController.store.getDataModel(surfaceId);
final dataModel = _surfaceController.contextFor(surfaceId).dataModel;
final data = dataModel.getValue<Object?>(DataPath.root);
final dataJson = const JsonEncoder.withIndent(' ').convert(data);

Expand Down Expand Up @@ -202,7 +205,7 @@ class _SurfaceEditorViewState extends State<SurfaceEditorView> {

final obj = jsonDecode(trimmedChunk);
if (obj is Map<String, Object?>) {
final message = A2uiMessage.fromJson(obj);
final message = core.A2uiMessage.fromJson(obj);
_surfaceController.handleMessage(message);
}
}
Expand All @@ -227,7 +230,7 @@ class _SurfaceEditorViewState extends State<SurfaceEditorView> {
if (parsed is Map<String, Object?>) {
final surfaceId = _surfaceIds.first;
_surfaceController.handleMessage(
A2uiMessage.fromJson({
core.A2uiMessage.fromJson({
'version': kProtocolVersion,
'updateDataModel': {
'surfaceId': surfaceId,
Expand Down
1 change: 1 addition & 0 deletions dev_tools/composer/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ environment:
resolution: workspace

dependencies:
a2ui_core: ^0.0.1-wip002
dartantic_ai: ^3.2.0
flutter:
sdk: flutter
Expand Down
72 changes: 72 additions & 0 deletions docs/usage/migration/migration_0.9.1_to_0.10.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Migration Guide: 0.9.1 to 0.10.0

`package:genui` now runs on the shared `package:a2ui_core` runtime (#811). The
only customer-facing change is for code that **implements a custom `Transport` or
constructs/parses A2UI messages directly** — those message types moved to
`a2ui_core`. The default AI/transport flow, catalog widgets, and data-binding code
are unaffected.

## What you have to change

### A2UI messages are now `a2ui_core` types

The genui message classes (`A2uiMessage`, `CreateSurface`, `UpdateComponents`,
`UpdateDataModel`, `DeleteSurface`) are removed. Add `a2ui_core` to your
dependencies and use its message types. They don't collide with anything genui
exports, so import them unprefixed with a `show` list:

```dart
// Before
controller.handleMessage(
UpdateComponents(surfaceId: 's', components: [
Component(id: 'root', type: 'Text', properties: {'text': 'Hi'}),
]),
);
controller.handleMessage(CreateSurface(surfaceId: 's', catalogId: 'demo'));

// After
import 'package:a2ui_core/a2ui_core.dart'
show CreateSurfaceMessage, UpdateComponentsMessage;

controller.handleMessage(
UpdateComponentsMessage(surfaceId: 's', components: [
{'id': 'root', 'component': 'Text', 'text': 'Hi'},
]),
);
controller.handleMessage(
CreateSurfaceMessage(surfaceId: 's', catalogId: 'demo'),
);
```

- **Custom transport:** `Transport.incomingMessages` and
`SurfaceController.handleMessage` now use `a2ui_core`'s `A2uiMessage`. Update
those signatures if you implement `Transport` or drive the controller directly.
- **Building messages:** `UpdateComponentsMessage` takes raw component JSON maps
(`{'id': ..., 'component': ..., ...props}`), not `Component` objects.
- **Parsing raw JSON:** use `A2uiMessage.fromJson(json)`.

### `SurfaceController.store` is removed

Read a surface's data model via `SurfaceController.contextFor(id).dataModel`
(writable, and usable before the surface is created).

## Behavior you may notice

- **`DataModel` writes are stricter.** Some writes that used to silently do
nothing now throw, e.g. writing through a path whose intermediate value isn't a
map or list.
- **Malformed messages are rejected more consistently** (missing or wrong
version, or more than one action key in a single message).
- **A duplicate `createSurface` for an active surface id is now an error** rather
than silently reusing the existing surface.
- **`updateDataModel` with `value: null` removes the key**, the same as omitting
the value. Distinguishing the two is pending flutter/genui#938.

## What does not change

Your catalog widgets and data-binding code are untouched: `CatalogItemContext`,
`dataContext`, the `DataModel` / `DataPath` API, the `Bound*` widgets, and the
`SurfaceDefinition` / `Component` snapshots all keep their current shape.

A follow-up (#801) will unify these with the `a2ui_core` models once the upstream
Node Layer (A2UI#1282) lands.
3 changes: 2 additions & 1 deletion examples/simple_chat/lib/a2ui_transport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:async';

import 'package:a2ui_core/a2ui_core.dart' as core;
import 'package:genui/genui.dart';

import 'agent/agent.dart';
Expand All @@ -22,7 +23,7 @@ class SimpleChatA2aTransport implements Transport {
final A2uiTransportAdapter _adapter = A2uiTransportAdapter();

@override
Stream<A2uiMessage> get incomingMessages => _adapter.incomingMessages;
Stream<core.A2uiMessage> get incomingMessages => _adapter.incomingMessages;

@override
Stream<String> get incomingText => _adapter.incomingText;
Expand Down
3 changes: 2 additions & 1 deletion examples/simple_chat/lib/chat_session.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:async';

import 'package:a2ui_core/a2ui_core.dart' as core;
import 'package:flutter/foundation.dart';
import 'package:genui/genui.dart';
import 'package:logging/logging.dart';
Expand Down Expand Up @@ -190,7 +191,7 @@ class A2uiChatSession extends ChatSession {
@override
SurfaceController get surfaceController => _surfaceController;

late final StreamSubscription<A2uiMessage> _messageSub;
late final StreamSubscription<core.A2uiMessage> _messageSub;
late final StreamSubscription<String> _textSub;
late final StreamSubscription<ChatMessage> _submitSub;
late final StreamSubscription<SurfaceUpdate> _surfaceSub;
Expand Down
1 change: 1 addition & 0 deletions examples/simple_chat/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ environment:
resolution: workspace

dependencies:
a2ui_core: ^0.0.1-wip002
dartantic_ai: ^3.2.0
flutter:
sdk: flutter
Expand Down
Loading
Loading