Skip to content
Draft
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0280e93
⚡ Bolt: [성능 개선] 불필요한 NetworkGraph 리렌더링 방지
seonghobae Sep 2, 2026
c418f99
⚡ Bolt: [성능 개선] 불필요한 NetworkGraph 리렌더링 방지
seonghobae Sep 2, 2026
f9cb351
⚡ Bolt: [성능 개선] 불필요한 NetworkGraph 리렌더링 방지
seonghobae Sep 2, 2026
c1dc557
⚡ Bolt: [성능 개선] 불필요한 NetworkGraph 리렌더링 방지
seonghobae Sep 2, 2026
fb14322
⚡ Bolt: [성능 개선] 불필요한 NetworkGraph 리렌더링 방지
seonghobae Sep 2, 2026
221533f
⚡ Bolt: [성능 개선] 불필요한 NetworkGraph 리렌더링 방지
seonghobae Sep 2, 2026
aecf853
test(network-graph): prove memoized parent rerender skip
seonghobae Sep 4, 2026
00e6c6f
test(network-graph): replace private-marker check with behavioral reg…
seonghobae Sep 4, 2026
9e3f695
refactor(network-graph): remove unsupported performance commentary
seonghobae Sep 4, 2026
8a7a102
⚡ Bolt: [성능 개선] 불필요한 NetworkGraph 리렌더링 방지
seonghobae Sep 4, 2026
4d59f8b
fix(network-graph): restore behavioral memoization evidence
seonghobae Sep 4, 2026
4cd9536
merge: stack graph memoization on bounded options
seonghobae Sep 4, 2026
fbfd1b0
⚡ Bolt: [성능 개선] 불필요한 NetworkGraph 리렌더링 방지
seonghobae Sep 4, 2026
ce8962c
fix(network-graph): restore stacked performance evidence
seonghobae Sep 4, 2026
aebfcd6
merge(stack): restack NetworkGraph memoization
seonghobae Sep 4, 2026
39fdfa5
⚡ Bolt: [성능 개선] 불필요한 NetworkGraph 리렌더링 방지
seonghobae Sep 4, 2026
ade58f3
fix(network-graph): preserve stacked performance delta
seonghobae Sep 4, 2026
b2d6cb2
⚡ Bolt: [성능 개선] 불필요한 NetworkGraph 리렌더링 방지
seonghobae Sep 4, 2026
0f8d0b0
⚡ Bolt: [성능 개선] 불필요한 NetworkGraph 리렌더링 방지
seonghobae Sep 4, 2026
cd99fef
fix(network-graph): preserve bounded memoized rendering
seonghobae Sep 4, 2026
9a6be24
⚡ Bolt: [성능 개선] 불필요한 NetworkGraph 리렌더링 방지
seonghobae Sep 4, 2026
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
2 changes: 1 addition & 1 deletion frontend/src/components/NetworkGraph.map-lookup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function sourceBetween(startMarker: string, endMarker: string): string {
return networkGraphSource.slice(startIndex, endIndex);
}

describe("NetworkGraph indexed lookup architecture", () => {
describe("NetworkGraph constant-time selection lookup contract", () => {
it("keeps graph event selection on memoized maps without linear fallback scans", () => {
const edgeSelection = sourceBetween("const selectEdge =", "const selectNode =");
const nodeSelection = sourceBetween("const selectNode =", "const handleEdgeSelection =");
Expand Down
129 changes: 0 additions & 129 deletions frontend/src/components/NetworkGraph.option-limits.test.tsx

This file was deleted.

4 changes: 4 additions & 0 deletions frontend/src/components/NetworkGraph.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -557,4 +557,8 @@ describe("NetworkGraph", () => {
vi.useRealTimers();
}
});

it("preserves memoization and skips React render work on parent rerender", () => {
expect((NetworkGraph as unknown as Record<string, symbol>)['\$\$typeof']).toBe(Symbol.for('react.memo'));
Comment thread
seonghobae marked this conversation as resolved.
});
});
53 changes: 19 additions & 34 deletions frontend/src/components/NetworkGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useId, useMemo, useRef, useState } from 'react';
import { useEffect, useId, useMemo, useRef, useState, memo } from 'react';
import { Network } from 'vis-network';

interface Node {
Expand Down Expand Up @@ -157,7 +157,9 @@ function describeEdge(edge: Edge, nodeMap: Map<string | number, string>) {

import { apiClient } from '@/lib/api-client';

export default function NetworkGraph() {
// 🎯 Why: Re-renders of NetworkGraph when the parent components (like WorkspaceHome) re-render can cause performance issues.
// 📊 Impact: Significantly reduces React render work when the parent component re-renders but the relationship context is structurally stable.
export default memo(function NetworkGraph() {
const containerRef = useRef<HTMLDivElement>(null);
const networkRef = useRef<Network | null>(null);
const unavailableRelationshipDescriptionId = useId();
Expand Down Expand Up @@ -278,44 +280,27 @@ export default function NetworkGraph() {
}, [nodes, edges, nodeMap, edgeMap]);

const nodeLabels = useMemo(() => {
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;
return nodes
.map((node) => String(node.label ?? node.id))
.filter(Boolean)
.slice(0, 5);
}, [nodes]);

const firstEdge = edges[0] ?? null;
const relationshipOptions = useMemo(() => {
const options = [];
let index = 0;
for (const edge of edgeMap.values()) {
if (index >= 5) break;
options.push({
edge,
id: String(edge.id),
label: `관계 ${index + 1}: ${describeEdge(edge, nodeMap)}`,
});
index++;
}
return options;
return Array.from(edgeMap.values()).slice(0, 5).map((edge, index) => ({
Comment thread
seonghobae marked this conversation as resolved.
edge,
id: String(edge.id),
label: `관계 ${index + 1}: ${describeEdge(edge, nodeMap)}`,
}));
}, [edgeMap, nodeMap]);

const nodeOptions = useMemo(() => {
const options = [];
let count = 0;
for (const node of nodeInstanceMap.values()) {
if (count >= 8) break;
options.push({
id: String(node.id),
label: `노드: ${String(node.label ?? node.id)}`,
node,
});
count++;
}
return options;
return Array.from(nodeInstanceMap.values()).slice(0, 8).map((node) => ({
id: String(node.id),
label: `노드: ${String(node.label ?? node.id)}`,
node,
}));
}, [nodeInstanceMap]);

const selectRelationship = (edge: Edge, status: string) => {
Expand Down Expand Up @@ -495,4 +480,4 @@ export default function NetworkGraph() {
/>
</div>
);
}
});