Skip to content

⚡ Bolt: Optimize validation error lookup in VisualEditor#952

Open
anchapin wants to merge 1 commit intomainfrom
jules-bolt-visual-editor-opt-287371400512358371
Open

⚡ Bolt: Optimize validation error lookup in VisualEditor#952
anchapin wants to merge 1 commit intomainfrom
jules-bolt-visual-editor-opt-287371400512358371

Conversation

@anchapin
Copy link
Copy Markdown
Owner

@anchapin anchapin commented Apr 2, 2026

Replaced an O(N*M) array filtering loop in getFieldErrors callback with a single-pass O(M) useMemo map lookup in VisualEditor.tsx, reducing render blocking on large forms. Added corresponding learning into .jules/bolt.md.


PR created automatically by Jules for task 287371400512358371 started by @anchapin

Summary by Sourcery

Optimize validation error lookup in VisualEditor to reduce render-time complexity on large forms and document the performance pattern in Bolt notes.

Enhancements:

  • Precompute a map of validation errors by field name in VisualEditor to avoid repeated O(N*M) filtering during field rendering.

Documentation:

  • Add Bolt documentation entries describing patterns for avoiding O(N*M) lookups and expensive array method chains in React components.

Replaced O(N*M) array filtering with O(M) useMemo map and O(1) lookups to reduce render overhead when dealing with numerous form fields and validation rules.

Co-authored-by: anchapin <6326294+anchapin@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copilot AI review requested due to automatic review settings April 2, 2026 23:46
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 2, 2026

Reviewer's Guide

Optimizes how validation errors are looked up in VisualEditor by memoizing a per-field error map and updates the Bolt engineering notes with the associated performance learnings.

Class diagram for VisualEditor validation error handling

classDiagram
  class ValidationRule {
    +string field
    +string message
  }

  class VisualEditor {
    +VisualEditorProps props
    +ValidationRule[] validationErrors
    +Record~string, ValidationRule[]~ fieldErrorsMap
    +ValidationRule[] getFieldErrors(fieldName string)
  }

  VisualEditor --> ValidationRule
Loading

Flow diagram for optimized validation error lookup in VisualEditor

flowchart TD
  A["validationErrors: ValidationRule[]"] --> B["useMemo builds fieldErrorsMap in a single O(M) pass"]
  B --> C["fieldErrorsMap: Record<string, ValidationRule[]>"]
  C --> D["getFieldErrors(fieldName) uses O(1) lookup"]
  D --> E["Return fieldErrorsMap[fieldName] or []"]
  E --> F["Child components (e.g. FormBuilder) render fields with pre-grouped errors"]
Loading

File-Level Changes

Change Details Files
Optimize validation error lookup in VisualEditor to avoid O(N*M) filtering per render.
  • Introduce a memoized map from field name to validation errors using a single-pass reduction over the validationErrors array.
  • Refactor getFieldErrors callback to read from the memoized map with O(1) lookups and adjust its dependency list accordingly.
  • Ensure fallback to an empty array when a field has no associated validation errors to avoid undefined returns.
frontend/src/components/BehaviorEditor/VisualEditor/VisualEditor.tsx
Document performance learnings in Bolt notes related to React list rendering and array operations.
  • Add a note about avoiding reduce and repeated some calls in hot loops within React useMemo hooks in favor of simple for loops.
  • Add a note about avoiding O(N*M) lookups by replacing per-item array filters with a single-pass map construction for O(1) access in child components.
.jules/bolt.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • Given the new guideline in .jules/bolt.md about preferring simple loops over reduce in hot paths, consider rewriting fieldErrorsMap’s useMemo to use a for loop to avoid callback allocations and slightly reduce overhead on very large validationErrors arrays.
  • When building fieldErrorsMap, consider initializing the accumulator with Object.create(null) instead of a plain object to avoid potential prototype key collisions and clarify that it’s being used purely as a hash map.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Given the new guideline in `.jules/bolt.md` about preferring simple loops over `reduce` in hot paths, consider rewriting `fieldErrorsMap`’s `useMemo` to use a `for` loop to avoid callback allocations and slightly reduce overhead on very large `validationErrors` arrays.
- When building `fieldErrorsMap`, consider initializing the accumulator with `Object.create(null)` instead of a plain object to avoid potential prototype key collisions and clarify that it’s being used purely as a hash map.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Optimizes VisualEditor field-level validation error lookup to reduce render-time overhead on large forms, and documents the performance learning in the Bolt log.

Changes:

  • Precomputes a fieldErrorsMap via useMemo to avoid repeated validationErrors.filter(...) calls per field.
  • Updates getFieldErrors to perform O(1) lookups instead of O(M) filtering.
  • Adds Bolt learnings about avoiding O(N*M) lookups in list renders.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
frontend/src/components/BehaviorEditor/VisualEditor/VisualEditor.tsx Adds memoized grouping of validation errors by field and switches lookups to map-based access.
.jules/bolt.md Documents the performance optimization pattern and related learnings.

}
acc[error.field].push(error);
return acc;
}, {} as Record<string, ValidationRule[]>);
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Using a plain object literal as a lookup table can break (and can be security-sensitive) when error.field matches an Object prototype key (e.g. __proto__, constructor, toString). In those cases acc[error.field] may not be an array and .push can throw or mutate the prototype. Consider using Object.create(null) for the accumulator (or a Map) so arbitrary field names are safe.

Suggested change
}, {} as Record<string, ValidationRule[]>);
}, Object.create(null) as Record<string, ValidationRule[]>);

Copilot uses AI. Check for mistakes.
Comment on lines 152 to +156
const getFieldErrors = useCallback(
(fieldName: string): ValidationRule[] => {
return validationErrors.filter((error) => error.field === fieldName);
return fieldErrorsMap[fieldName] || [];
},
[validationErrors]
[fieldErrorsMap]
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

getFieldErrors falls back to a new empty array literal on every call (|| []). On large forms where most fields have no errors, this still allocates many arrays per render (and FormBuilder calls getFieldErrors multiple times per field). Consider returning a shared EMPTY_ERRORS constant (or using nullish coalescing with a memoized empty array) to avoid unnecessary allocations.

Copilot uses AI. Check for mistakes.
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.

2 participants