PULSE 실시간 맥락 기반 자동화 기획전 PRD

UX_GUIDELINES

PULSE_DESIGN_SYSTEM

OPTIMIZATION_REPORT

PULSE 코드 최적화 보고서

생성일: 2026-05-17

분석 도구: 정적 분석 (Big-O / 메모리 / React deps / 에러 바운더리)

대상 파일: DiggingMap.tsx · page.tsx · match/route.ts · generate/route.ts · ContentTipsPanel.tsx


요약

심각도 건수 주요 영역
🔴 HIGH 3 D3 tick 내 setState, 드래그 이벤트 누수, window 리스너 미해제
🟡 MEDIUM 4 O(n²) 노드 탐색, sort() 셔플 편향, 내부 setTimeout 미추적, Anthropic 타임아웃 없음
🟢 LOW 3 brandFeatures find() 중첩, 마커 배열 선언 오류(이미 수정됨), 에러 바운더리 부재

발견된 이슈 목록


🔴 ISSUE-01 | D3 tick 핸들러 내부 setState 무한 호출

파일: src/components/pulse/DiggingMap.tsx

라인: sim.on('tick', () => { ... setHoveredLink(...) })

심각도: 🔴 HIGH — 성능

현재 코드 (문제)

sim.on('tick', () => {
  linkSel.attr('x1', ...).attr('y1', ...).attr('x2', ...).attr('y2', ...);
  nodeSel.attr('transform', ...);

  // 🚨 60fps마다 React setState 호출 → 매 프레임 리렌더 유발
  setHoveredLink((prev) => {
    if (!prev) return null;
    const l = links.find((l) => l.id === prev.id); // 🚨 O(n) in every frame
    if (!l) return null;
    const s = l.source as GNode; const t = l.target as GNode;
    return { id: prev.id, x: ((s.x ?? 0) + (t.x ?? 0)) / 2, y: ((s.y ?? 0) + (t.y ?? 0)) / 2 };
  });
});

수정된 코드

// hoveredLinkRef로 DOM 좌표를 추적 — React 리렌더 없이 SVG 오버레이 갱신
const hoveredLinkRef = useRef<LinkMidpoint | null>(null);

sim.on('tick', () => {
  linkSel.attr('x1', (d) => (d.source as GNode).x ?? 0)
         .attr('y1', (d) => (d.source as GNode).y ?? 0)
         .attr('x2', (d) => (d.target as GNode).x ?? 0)
         .attr('y2', (d) => (d.target as GNode).y ?? 0);
  nodeSel.attr('transform', (d) => `translate(${d.x ?? 0},${d.y ?? 0})`);

  // ✅ setState 제거 — hoveredLink 위치는 mouseenter/mouseleave에서만 갱신
  // tick에서는 ref에만 좌표 기록
  if (hoveredLinkRef.current) {
    const l = linksMap.get(hoveredLinkRef.current.id); // O(1) Map 조회
    if (l) {
      const s = l.source as GNode; const t = l.target as GNode;
      hoveredLinkRef.current = {
        id: hoveredLinkRef.current.id,
        x: ((s.x ?? 0) + (t.x ?? 0)) / 2,
        y: ((s.y ?? 0) + (t.y ?? 0)) / 2,
      };
    }
  }
});

변경 사유