From 83ec256cecc8aa327788ff0d84d4f323cf8c640f Mon Sep 17 00:00:00 2001 From: jreakin Date: Mon, 23 Mar 2026 09:41:31 -0500 Subject: [PATCH] =?UTF-8?q?fix(web):=20address=20React=20rendering=20revie?= =?UTF-8?q?w=20=E2=80=94=20stale=20closure,=20ref=20dep,=20O(1)=20lookups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MarkdownRenderer: wrap handleLinkClick in useCallback, add to markdownComponents useMemo deps (fixes stale closure) - GraphCanvas: remove sigmaRef from useEffect deps (ref identity never changes), extract handleToggleAIHighlights to useCallback - CodeReferencesPanel: add nodeById Map for O(1) focus-in-graph lookup (was O(N) graph.nodes.find on every click) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/CodeReferencesPanel.tsx | 8 +++++- gitnexus-web/src/components/GraphCanvas.tsx | 26 ++++++++++--------- .../src/components/MarkdownRenderer.tsx | 6 ++--- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/gitnexus-web/src/components/CodeReferencesPanel.tsx b/gitnexus-web/src/components/CodeReferencesPanel.tsx index b1bcd1897..89ff3026d 100644 --- a/gitnexus-web/src/components/CodeReferencesPanel.tsx +++ b/gitnexus-web/src/components/CodeReferencesPanel.tsx @@ -3,6 +3,7 @@ import { Code, PanelLeftClose, PanelLeft, Trash2, X, Target, FileCode, Sparkles, import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { useAppState } from '../hooks/useAppState'; +import type { GraphNode } from '../core/graph/types'; import { NODE_COLORS } from '../lib/constants'; /** Map file extension to Prism syntax highlighter language identifier */ @@ -75,6 +76,11 @@ export const CodeReferencesPanel = ({ onFocusNode }: CodeReferencesPanelProps) = codeReferenceFocus, } = useAppState(); + const nodeById = useMemo(() => { + if (!graph) return new Map(); + return new Map(graph.nodes.map(n => [n.id, n])); + }, [graph]); + const [isCollapsed, setIsCollapsed] = useState(false); const [glowRefId, setGlowRefId] = useState(null); const panelRef = useRef(null); @@ -421,7 +427,7 @@ export const CodeReferencesPanel = ({ onFocusNode }: CodeReferencesPanelProps) = const nodeId = ref.nodeId!; // Sync selection + focus graph if (graph) { - const node = graph.nodes.find((n) => n.id === nodeId); + const node = nodeById.get(nodeId); if (node) setSelectedNode(node); } onFocusNode(nodeId); diff --git a/gitnexus-web/src/components/GraphCanvas.tsx b/gitnexus-web/src/components/GraphCanvas.tsx index e5afc2ce9..9585584d9 100644 --- a/gitnexus-web/src/components/GraphCanvas.tsx +++ b/gitnexus-web/src/components/GraphCanvas.tsx @@ -82,6 +82,17 @@ export const GraphCanvas = forwardRef((_, ref) => { setSelectedNode(null); }, [setSelectedNode]); + const handleToggleAIHighlights = useCallback(() => { + if (isAIHighlightsEnabled) { + clearAIToolHighlights(); + clearAICitationHighlights(); + clearBlastRadius(); + setSelectedNode(null); + setSigmaSelectedNode(null); + } + toggleAIHighlights(); + }, [isAIHighlightsEnabled, clearAIToolHighlights, clearAICitationHighlights, clearBlastRadius, setSelectedNode, toggleAIHighlights]); + const { containerRef, sigmaRef, @@ -154,7 +165,8 @@ export const GraphCanvas = forwardRef((_, ref) => { filterGraphByDepth(sigmaGraph, appSelectedNode?.id || null, depthFilter, visibleLabels); sigma.refresh(); - }, [visibleLabels, depthFilter, appSelectedNode, sigmaRef]); + // eslint-disable-next-line react-hooks/exhaustive-deps -- sigmaRef identity never changes + }, [visibleLabels, depthFilter, appSelectedNode]); // Sync app selected node with sigma useEffect(() => { @@ -312,17 +324,7 @@ export const GraphCanvas = forwardRef((_, ref) => { {/* AI Highlights toggle - Top Right */}