diff --git a/.jules/bolt.md b/.jules/bolt.md index b0f22698..73e5c8b8 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -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). diff --git a/frontend/src/components/BehaviorEditor/VisualEditor/VisualEditor.tsx b/frontend/src/components/BehaviorEditor/VisualEditor/VisualEditor.tsx index 5b931d4c..cb811fa4 100644 --- a/frontend/src/components/BehaviorEditor/VisualEditor/VisualEditor.tsx +++ b/frontend/src/components/BehaviorEditor/VisualEditor/VisualEditor.tsx @@ -136,12 +136,24 @@ export const VisualEditor: React.FC = ({ [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); + }, [validationErrors]); + // Get field errors const getFieldErrors = useCallback( (fieldName: string): ValidationRule[] => { - return validationErrors.filter((error) => error.field === fieldName); + return fieldErrorsMap[fieldName] || []; }, - [validationErrors] + [fieldErrorsMap] ); // Check if form has unsaved changes