From 86797144115f92811bcdbbdefc82d799e7b354ca Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 29 Aug 2026 21:01:21 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EC=B5=9C=EC=A0=81=ED=99=94?= =?UTF-8?q?:=20NetworkGraph=EC=9D=98=20O(N)=20=EB=B0=B0=EC=97=B4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit πŸ’‘ What: `NetworkGraph.tsx`의 `relationshipOptions`, `nodeOptions`, `nodeLabels` `useMemo` λΈ”λ‘μ—μ„œ `Array.from(map.values()).slice().map()` νŒ¨ν„΄μ„ μ‘°κΈ° μ’…λ£Œ(early break)λ₯Ό ν¬ν•¨ν•œ `for...of` λ£¨ν”„λ‘œ κ΅μ²΄ν–ˆμŠ΅λ‹ˆλ‹€. 🎯 Why: κΈ°μ‘΄ μ½”λ“œλŠ” μ†Œμˆ˜μ˜ ν•­λͺ©(5~8개)만 ν•„μš”ν•¨μ—λ„ λΆˆκ΅¬ν•˜κ³  수천 개의 λ…Έλ“œλ‚˜ 관계λ₯Ό κ°€μ§ˆ 수 μžˆλŠ” λ§΅ 전체λ₯Ό λ©”λͺ¨λ¦¬μ— λ°°μ—΄λ‘œ λ³€ν™˜ν•˜λŠ” O(N) 연산을 μˆ˜ν–‰ν•˜μ—¬ 메인 μŠ€λ ˆλ“œλ₯Ό λΈ”λ‘œν‚Ήν•  μœ„ν—˜μ΄ μžˆμ—ˆμŠ΅λ‹ˆλ‹€. πŸ“Š Impact: κ·Έλž˜ν”„ λ Œλ”λ§ μ‹œ λΆˆν•„μš”ν•œ 전체 λ§΅ μˆœνšŒμ™€ κ±°λŒ€ν•œ 쀑간 λ°°μ—΄ 생성을 λ°©μ§€ν•˜μ—¬, λŒ€μš©λŸ‰ κ·Έλž˜ν”„ ν™˜κ²½μ—μ„œ λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰μ„ 쀄이고 μ»΄ν¬λ„ŒνŠΈ λ Œλ”λ§ 지연을 λ°©μ§€ν•©λ‹ˆλ‹€(O(N) -> O(1) λ°”μš΄λ“œ). πŸ”¬ Measurement: `frontend` 폴더 λ‚΄μ—μ„œ `pnpm test` 및 `pnpm lint`λ₯Ό μ‹€ν–‰ν•˜μ—¬ λ Œλ”λ§ 둜직의 무결성을 검증할 수 μžˆμŠ΅λ‹ˆλ‹€. --- frontend/.Jules/bolt.md | 3 ++ frontend/src/components/NetworkGraph.tsx | 49 +++++++++++++++++------- 2 files changed, 38 insertions(+), 14 deletions(-) create mode 100644 frontend/.Jules/bolt.md diff --git a/frontend/.Jules/bolt.md b/frontend/.Jules/bolt.md new file mode 100644 index 000000000..6b77a6b25 --- /dev/null +++ b/frontend/.Jules/bolt.md @@ -0,0 +1,3 @@ +## 2025-05-24 - NetworkGraph O(N) array creation bounds +**Learning:** Using `Array.from(map.values()).slice(0, N)` creates an array of the entire map in memory (O(N) operations) before truncating it. This can block the main thread and consume significant memory for large datasets in React components when we only need a few items. +**Action:** Replace `Array.from(iterable).slice(0, N)` with a bounded `for...of` loop that breaks early when the desired number of items is reached, keeping the complexity O(1). diff --git a/frontend/src/components/NetworkGraph.tsx b/frontend/src/components/NetworkGraph.tsx index f9eb61c71..20baf3ab7 100644 --- a/frontend/src/components/NetworkGraph.tsx +++ b/frontend/src/components/NetworkGraph.tsx @@ -277,28 +277,49 @@ export default function NetworkGraph() { } }, [nodes, edges, nodeMap, edgeMap]); + // ⚑ Bolt: Replace O(N) array mapping and filtering with O(1) bounded iteration + // 🎯 Why: Mapping and filtering the entire node array blocks the main thread for large graphs when we only need 5 labels const nodeLabels = useMemo(() => { - return nodes - .map((node) => String(node.label ?? node.id)) - .filter(Boolean) - .slice(0, 5); + const labels = []; + for (const node of nodes) { + if (labels.length >= 5) break; + const label = String(node.label ?? node.id); + if (label) labels.push(label); + } + return labels; }, [nodes]); const firstEdge = edges[0] ?? null; + // ⚑ Bolt: Replace O(N) Array.from().slice() with O(1) bounded iteration + // 🎯 Why: Converting the entire map to an array blocks the main thread for large graphs when we only need 5 items const relationshipOptions = useMemo(() => { - return Array.from(edgeMap.values()).slice(0, 5).map((edge, index) => ({ - edge, - id: String(edge.id), - label: `관계 ${index + 1}: ${describeEdge(edge, nodeMap)}`, - })); + const options = []; + let index = 0; + for (const edge of edgeMap.values()) { + if (options.length >= 5) break; + options.push({ + edge, + id: String(edge.id), + label: `관계 ${index + 1}: ${describeEdge(edge, nodeMap)}`, + }); + index++; + } + return options; }, [edgeMap, nodeMap]); + // ⚑ Bolt: Replace O(N) Array.from().slice() with O(1) bounded iteration + // 🎯 Why: Converting the entire map to an array blocks the main thread for large graphs when we only need 8 items const nodeOptions = useMemo(() => { - return Array.from(nodeInstanceMap.values()).slice(0, 8).map((node) => ({ - id: String(node.id), - label: `λ…Έλ“œ: ${String(node.label ?? node.id)}`, - node, - })); + const options = []; + for (const node of nodeInstanceMap.values()) { + if (options.length >= 8) break; + options.push({ + id: String(node.id), + label: `λ…Έλ“œ: ${String(node.label ?? node.id)}`, + node, + }); + } + return options; }, [nodeInstanceMap]); const selectRelationship = (edge: Edge, status: string) => {