From 2ca38f1dfdcb3f15d1aabb6d57316569d257f87d Mon Sep 17 00:00:00 2001 From: AashishH15 <121846193+AashishH15@users.noreply.github.com> Date: Sat, 6 Jun 2026 11:57:28 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20VaultSidebar=20t?= =?UTF-8?q?ree=20building=20and=20search=20complexity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 3 +++ ui/components/VaultSidebar.tsx | 30 +++++++++++++----------------- 2 files changed, 16 insertions(+), 17 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..57c5b4e --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-06-06 - VaultSidebar.tsx Tree Building and Search O(N*V) Bottleneck +**Learning:** React `useMemo` blocks handling dual responsibilities (e.g. static tree grouping + dynamic search filtering) force expensive tree-rebuilding on every keystroke. Furthermore, using `array.find()` inside a search loop creates an O(N * V) complexity trap that drastically degrades typing performance in large vaults. +**Action:** Always split large `useMemo` blocks by caching frequency (e.g., separate tree-shaping from search-filtering). Ensure lookups inside search loops utilize pre-computed hash maps (like `vaultById`) instead of linear array scans to maintain O(1) time complexity per node. diff --git a/ui/components/VaultSidebar.tsx b/ui/components/VaultSidebar.tsx index 3ccc521..fdf4d31 100644 --- a/ui/components/VaultSidebar.tsx +++ b/ui/components/VaultSidebar.tsx @@ -665,15 +665,8 @@ function VaultSidebar({ // --------------------------------------------------------------------------- // Build structured tree data with search-match metadata // --------------------------------------------------------------------------- - const { - topLevelVaults, - childrenByParent, - nodesByVaultId, - matchingVaultIds, - matchingSubVaultIds, - matchingNodeIds, - resultCount, - } = useMemo(() => { + // ⚡ Bolt: Split tree building from search filtering to avoid rebuilding the tree on every keystroke + const { topLevelVaults, childrenByParent, nodesByVaultId } = useMemo(() => { const childMap = new Map(); for (const vault of vaults) { if (!vault.parentVaultId) { @@ -701,12 +694,18 @@ function VaultSidebar({ nodeMap.set(parentKey, existing); } + return { + topLevelVaults: roots, + childrenByParent: childMap, + nodesByVaultId: nodeMap, + }; + }, [allNodes, vaults]); + + // ⚡ Bolt: Isolated search filtering into its own useMemo and replaced O(N) array.find() with O(1) hash map lookup + const { matchingVaultIds, matchingSubVaultIds, matchingNodeIds, resultCount } = useMemo(() => { // Without a search query, show everything normally if (!normalizedQuery) { return { - topLevelVaults: roots, - childrenByParent: childMap, - nodesByVaultId: nodeMap, matchingVaultIds: new Set(), matchingSubVaultIds: new Set(), matchingNodeIds: new Set(), @@ -731,7 +730,7 @@ function VaultSidebar({ if (node.subVaultId) { matchSubVaults.add(node.subVaultId); // Also find the root vault for this sub-vault - const subVault = vaults.find((v) => v.id === node.subVaultId); + const subVault = vaultById[node.subVaultId]; if (subVault?.parentVaultId) { matchVaults.add(subVault.parentVaultId); } @@ -754,15 +753,12 @@ function VaultSidebar({ } return { - topLevelVaults: roots, - childrenByParent: childMap, - nodesByVaultId: nodeMap, matchingVaultIds: matchVaults, matchingSubVaultIds: matchSubVaults, matchingNodeIds: matchNodes, resultCount: count, }; - }, [allNodes, normalizedQuery, vaults]); + }, [allNodes, normalizedQuery, vaults, vaultById]); // --------------------------------------------------------------------------- // Helpers for determining match/dim state