Skip to content

Conversation

jacobsimionato
Copy link
Collaborator

This PR implements incremental surface updates by introducing update and replace actions for the addOrUpdateSurface tool.

Changes:

  • Renamed SurfaceUpdated to SurfaceChanged.
  • Implemented update action in GenUiManager to upsert widgets.
  • Renamed existing update action to replace.
  • Updated AddOrUpdateSurfaceTool schema and descriptions.
  • Updated example app prompts to reflect the changes.

Fixes #283

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an excellent feature for incremental surface updates by adding update and replace actions. The implementation is well-structured and includes corresponding updates to documentation, examples, and tests. My review includes a few suggestions to enhance robustness and maintainability, fix a potential bug related to newline character escaping, and acknowledges a TODO regarding orphaned widgets which is important for long-term performance.

final stateValue = valueStore.forSurface(event.surfaceId);
final eventString =
'Action: ${jsonEncode(event.value)}\n'
'Action: ${jsonEncode(event.value)}\\n'

Choose a reason for hiding this comment

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

high

The change from \n to \\n appears to be a mistake. The double backslash escapes the newline character, which means a literal \n string will be sent to the model instead of a newline. This is unlikely to be interpreted as a line break by the AI. The original single backslash \n was correct for creating a newline character within the string.

Suggested change
'Action: ${jsonEncode(event.value)}\\n'
'Action: ${jsonEncode(event.value)}\n'

Comment on lines +195 to +200
final newWidgetsList = definition['widgets'] as List<Object?>;
final newWidgetsMap = <String, JsonMap>{};
for (final widget in newWidgetsList) {
final typedWidget = widget as JsonMap;
newWidgetsMap[typedWidget['id'] as String] = typedWidget;
}

Choose a reason for hiding this comment

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

medium

The code in this block uses several direct casts (as) on the definition map, which is populated from AI-generated content. This could lead to runtime exceptions if the AI model provides a malformed response that doesn't adhere to the expected schema. To make the code more robust, consider using type checks (is) and conditional casts (as?) to gracefully handle potential data inconsistencies.

For example:

final newWidgetsList = definition['widgets'];
if (newWidgetsList is! List) {
  // Handle error, e.g., throw an ArgumentError or log a warning.
  return;
}
for (final widget in newWidgetsList) {
  if (widget is! JsonMap) {
    // Handle error
    continue;
  }
  final id = widget['id'];
  if (id is! String) {
    // Handle error
    continue;
  }
  newWidgetsMap[id] = widget;
}

...newWidgetsMap,
};

// TODO(andrewkolb): Prune orphaned widgets.

Choose a reason for hiding this comment

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

medium

The TODO comment highlights a significant issue. The current implementation for the update action merges new widgets but does not remove widgets that are no longer referenced in the widget tree. This will cause an accumulation of "orphaned" widgets in the UiDefinition, leading to memory bloat and potential performance degradation over time. This should be addressed to ensure the long-term stability of the application.

final surfaceId = args['surfaceId'] as String;
final definition = args['definition'] as JsonMap;
final action = args['action'] as String;
definition['action'] = action;

Choose a reason for hiding this comment

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

medium

This line mutates the definition map, which is part of the input args. While it may work here, modifying input parameters is generally considered bad practice as it can lead to unexpected side effects and make the code harder to reason about. A cleaner approach would be to create a new map that includes the action property, leaving the original definition map unchanged.

For example:

    final newDefinition = {
      ...definition,
      'action': action,
    };
    onAddOrUpdate(surfaceId, newDefinition);

@jacobsimionato jacobsimionato changed the title feat: Support incremental surface updates Support incremental surface updates Sep 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support incremental surface updates
1 participant