Skip to content

Commit 8b65981

Browse files
Fix surface creation and update notification bugs (#556)
1 parent 2d28ae9 commit 8b65981

File tree

3 files changed

+48
-37
lines changed

3 files changed

+48
-37
lines changed

packages/genui/lib/src/core/genui_manager.dart

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,11 @@ class GenUiManager implements GenUiHost {
158158
void handleMessage(A2uiMessage message) {
159159
switch (message) {
160160
case SurfaceUpdate():
161-
// No need for SurfaceAdded here because A2uiMessage will never generate
162-
// those. We decide here if the surface is new or not, and generate a
163-
// SurfaceAdded event if so.
164161
final String surfaceId = message.surfaceId;
165162
final ValueNotifier<UiDefinition?> notifier = getSurfaceNotifier(
166163
surfaceId,
167164
);
168-
final isNew = notifier.value == null;
165+
169166
UiDefinition uiDefinition =
170167
notifier.value ?? UiDefinition(surfaceId: surfaceId);
171168
final Map<String, Component> newComponents = Map.of(
@@ -176,26 +173,33 @@ class GenUiManager implements GenUiHost {
176173
}
177174
uiDefinition = uiDefinition.copyWith(components: newComponents);
178175
notifier.value = uiDefinition;
179-
if (isNew) {
180-
genUiLogger.info('Adding surface $surfaceId');
181-
_surfaceUpdates.add(SurfaceAdded(surfaceId, uiDefinition));
182-
} else {
176+
177+
// Notify UI ONLY if rendering has begun (i.e., rootComponentId is set)
178+
if (uiDefinition.rootComponentId != null) {
183179
genUiLogger.info('Updating surface $surfaceId');
184180
_surfaceUpdates.add(SurfaceUpdated(surfaceId, uiDefinition));
181+
} else {
182+
genUiLogger.info(
183+
'Caching components for surface $surfaceId (pre-rendering)',
184+
);
185185
}
186186
case BeginRendering():
187-
dataModelForSurface(message.surfaceId);
187+
final String surfaceId = message.surfaceId;
188+
dataModelForSurface(surfaceId);
188189
final ValueNotifier<UiDefinition?> notifier = getSurfaceNotifier(
189-
message.surfaceId,
190+
surfaceId,
190191
);
192+
193+
// Update the definition with the root component
191194
final UiDefinition uiDefinition =
192-
notifier.value ?? UiDefinition(surfaceId: message.surfaceId);
195+
notifier.value ?? UiDefinition(surfaceId: surfaceId);
193196
final UiDefinition newUiDefinition = uiDefinition.copyWith(
194197
rootComponentId: message.root,
195198
);
196199
notifier.value = newUiDefinition;
197-
genUiLogger.info('Started rendering ${message.surfaceId}');
198-
_surfaceUpdates.add(SurfaceUpdated(message.surfaceId, newUiDefinition));
200+
201+
genUiLogger.info('Creating and rendering surface $surfaceId');
202+
_surfaceUpdates.add(SurfaceAdded(surfaceId, newUiDefinition));
199203
case DataModelUpdate():
200204
final String path = message.path ?? '/';
201205
genUiLogger.info(
@@ -205,6 +209,15 @@ class GenUiManager implements GenUiHost {
205209
);
206210
final DataModel dataModel = dataModelForSurface(message.surfaceId);
207211
dataModel.update(DataPath(path), message.contents);
212+
213+
// Notify UI of an update if the surface is already rendering
214+
final ValueNotifier<UiDefinition?> notifier = getSurfaceNotifier(
215+
message.surfaceId,
216+
);
217+
final UiDefinition? uiDefinition = notifier.value;
218+
if (uiDefinition != null && uiDefinition.rootComponentId != null) {
219+
_surfaceUpdates.add(SurfaceUpdated(message.surfaceId, uiDefinition));
220+
}
208221
case SurfaceDeletion():
209222
final String surfaceId = message.surfaceId;
210223
if (_surfaces.containsKey(surfaceId)) {

packages/genui/test/core/genui_manager_test.dart

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,19 @@ void main() {
4141
),
4242
];
4343

44-
final Future<GenUiUpdate> futureAdded = manager.surfaceUpdates.first;
4544
manager.handleMessage(
4645
SurfaceUpdate(surfaceId: surfaceId, components: components),
4746
);
48-
final GenUiUpdate addedUpdate = await futureAdded;
49-
expect(addedUpdate, isA<SurfaceAdded>());
50-
expect(addedUpdate.surfaceId, surfaceId);
5147

52-
final Future<GenUiUpdate> futureUpdated = manager.surfaceUpdates.first;
48+
final Future<GenUiUpdate> futureUpdate = manager.surfaceUpdates.first;
5349
manager.handleMessage(
5450
const BeginRendering(surfaceId: surfaceId, root: 'root'),
5551
);
56-
final GenUiUpdate updatedUpdate = await futureUpdated;
52+
final GenUiUpdate update = await futureUpdate;
5753

58-
expect(updatedUpdate, isA<SurfaceUpdated>());
59-
expect(updatedUpdate.surfaceId, surfaceId);
60-
final UiDefinition definition =
61-
(updatedUpdate as SurfaceUpdated).definition;
54+
expect(update, isA<SurfaceAdded>());
55+
expect(update.surfaceId, surfaceId);
56+
final UiDefinition definition = (update as SurfaceAdded).definition;
6257
expect(definition, isNotNull);
6358
expect(definition.rootComponentId, 'root');
6459
expect(manager.surfaces[surfaceId]!.value, isNotNull);
@@ -77,10 +72,6 @@ void main() {
7772
},
7873
),
7974
];
80-
manager.handleMessage(
81-
SurfaceUpdate(surfaceId: surfaceId, components: oldComponents),
82-
);
83-
8475
final newComponents = [
8576
const Component(
8677
id: 'root',
@@ -90,18 +81,22 @@ void main() {
9081
),
9182
];
9283

93-
final Future<GenUiUpdate> futureUpdate = manager.surfaceUpdates.first;
84+
final Future<void> expectation = expectLater(
85+
manager.surfaceUpdates,
86+
emitsInOrder([isA<SurfaceAdded>(), isA<SurfaceUpdated>()]),
87+
);
88+
89+
manager.handleMessage(
90+
SurfaceUpdate(surfaceId: surfaceId, components: oldComponents),
91+
);
92+
manager.handleMessage(
93+
const BeginRendering(surfaceId: surfaceId, root: 'root'),
94+
);
9495
manager.handleMessage(
9596
SurfaceUpdate(surfaceId: surfaceId, components: newComponents),
9697
);
97-
final GenUiUpdate update = await futureUpdate;
98-
99-
expect(update, isA<SurfaceUpdated>());
100-
expect(update.surfaceId, surfaceId);
101-
final UiDefinition updatedDefinition =
102-
(update as SurfaceUpdated).definition;
103-
expect(updatedDefinition.components['root'], newComponents[0]);
104-
expect(manager.surfaces[surfaceId]!.value, updatedDefinition);
98+
99+
await expectation;
105100
},
106101
);
107102

packages/genui/test/ui_tools_test.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ void main() {
6464
);
6565

6666
await tool.invoke(args);
67+
genUiManager.handleMessage(
68+
const BeginRendering(surfaceId: 'testSurface', root: 'root'),
69+
);
6770

6871
await future;
6972
});
@@ -99,7 +102,7 @@ void main() {
99102
final Future<void> future = expectLater(
100103
genUiManager.surfaceUpdates,
101104
emits(
102-
isA<SurfaceUpdated>()
105+
isA<SurfaceAdded>()
103106
.having((e) => e.surfaceId, surfaceIdKey, 'testSurface')
104107
.having(
105108
(e) => e.definition.rootComponentId,

0 commit comments

Comments
 (0)