diff --git a/.jules/bolt.md b/.jules/bolt.md index b45f9caa..d62e7fc4 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -60,3 +60,7 @@ Optimized metric route processing to O(N) by creating a mapping of routes direct ## 2026-06-25 - Avoid Map allocations in frontend ERD loops and mutate asyncpg records in-place **Learning:** The frontend `snapshotToGraph` iterates over thousands of columns to generate the graph, so repeated lookups and redundant collection assignments increase GC pressure. Backend snapshot column dictionaries are freshly instantiated for the payload, so `add_column_examples` can safely fill missing fields in place. **Action:** Reuse existing collections while aggregating relational data, create `Map`/`Set` entries only on first use, and check for missing example fields before calling expensive inference helpers. + +## 2024-07-03 - [Frontend Search Optimization] +**Learning:** React Flow node filtering using array `.flatMap()`, `.join(" ")` and lowercase on the joined string causes significant memory allocations and garbage collection pressure on large graphs. +**Action:** Avoid mass array creation and string concatenations in hot paths like `useMemo` search filters. Use explicit `for` loops with short-circuiting (`break` and early returns) to minimize unnecessary object allocations and speed up rendering. diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e99700c5..bd7aaea9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -181,19 +181,29 @@ export default function App() { const searchMatchedNodeIds = useMemo(() => { if (!normalizedNodeSearch) return new Set(); const matches = new Set(); + + // ⚡ Bolt: Optimized search filtering + // Replaced expensive array spreading, flatMap(), and string joining with explicit loops. + // Early return (break) on first match avoids processing remaining fields. + // This reduces memory allocations and garbage collection overhead during search. for (const node of nodes) { - const haystack = [ - node.data.title, - node.data.comment ?? "", - ...node.data.columns.flatMap((column) => [ - column.column_name, - column.data_type, - column.column_comment ?? "", - ]), - ] - .join(" ") - .toLocaleLowerCase(); - if (haystack.includes(normalizedNodeSearch)) { + let isMatch = false; + const title = node.data.title.toLocaleLowerCase(); + if (title.includes(normalizedNodeSearch)) { + isMatch = true; + } else if (node.data.comment && node.data.comment.toLocaleLowerCase().includes(normalizedNodeSearch)) { + isMatch = true; + } else { + for (const column of node.data.columns) { + if (column.column_name.toLocaleLowerCase().includes(normalizedNodeSearch) || + column.data_type.toLocaleLowerCase().includes(normalizedNodeSearch) || + (column.column_comment && column.column_comment.toLocaleLowerCase().includes(normalizedNodeSearch))) { + isMatch = true; + break; + } + } + } + if (isMatch) { matches.add(node.id); } }