Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,11 @@
## 2026-03-18 - O(N) aggregation over O(N*M) filters
**Learning:** In React components with dynamically generated filter lists, using `.filter().length` inside a `.map()` results in O(N*M) time complexity, leading to sluggish renders with larger datasets.
**Action:** Use `.reduce()` or a single loop inside `useMemo` to pre-calculate category counts in an O(N) pass instead.

## 2026-03-19 - [Avoid reduce and some for hot loops in React]
**Learning:** Using chained array methods like `.reduce` and multiple `.some` calls inside `useMemo` hooks can cause unnecessary allocations and performance degradation due to closures and object creations, especially when checking nested or multiple arrays.
**Action:** Use simple `for` loops inside `useMemo` to combine multiple conditions, breaking early and avoiding callback allocations for faster single-pass validation or aggregation.

## 2026-03-19 - [Avoid O(N*M) lookups inside list renders]
**Learning:** Passing a callback that performs `.filter` on a full array down to child components that iterate over lists (like `FormBuilder` iterating over `fields` and calling `getFieldErrors(field.name)`) results in O(N*M) time complexity.
**Action:** Replace callback filters with a single pass O(M) `.reduce` or loop inside a `useMemo` to construct a grouped hash map by ID, turning child component lookups into O(1).
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,24 @@ export const VisualEditor: React.FC<VisualEditorProps> = ({
[fields, categories]
);

// ⚡ Bolt optimization: Group validation errors by field in a single O(M) pass
// instead of filtering the array inside a callback for every single field O(N*M).
const fieldErrorsMap = useMemo(() => {
return validationErrors.reduce((acc, error) => {
if (!acc[error.field]) {
acc[error.field] = [];
}
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.
}, [validationErrors]);

// Get field errors
const getFieldErrors = useCallback(
(fieldName: string): ValidationRule[] => {
return validationErrors.filter((error) => error.field === fieldName);
return fieldErrorsMap[fieldName] || [];
},
[validationErrors]
[fieldErrorsMap]
Comment on lines 152 to +156
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.
);

// Check if form has unsaved changes
Expand Down
Loading