mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
fix(web): address React rendering review — stale closure, ref dep, O(1) lookups
- 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) <noreply@anthropic.com>
This commit is contained in:
parent
29795bb86e
commit
83ec256cec
3 changed files with 24 additions and 16 deletions
|
|
@ -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<string, GraphNode>();
|
||||
return new Map(graph.nodes.map(n => [n.id, n]));
|
||||
}, [graph]);
|
||||
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
const [glowRefId, setGlowRefId] = useState<string | null>(null);
|
||||
const panelRef = useRef<HTMLElement | null>(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);
|
||||
|
|
|
|||
|
|
@ -82,6 +82,17 @@ export const GraphCanvas = forwardRef<GraphCanvasHandle>((_, 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<GraphCanvasHandle>((_, 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<GraphCanvasHandle>((_, ref) => {
|
|||
{/* AI Highlights toggle - Top Right */}
|
||||
<div className="absolute top-4 right-4 z-20">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (isAIHighlightsEnabled) {
|
||||
// Turning off — clear AI highlights and selection (preserve user query highlights)
|
||||
clearAIToolHighlights();
|
||||
clearAICitationHighlights();
|
||||
clearBlastRadius();
|
||||
setSelectedNode(null);
|
||||
setSigmaSelectedNode(null);
|
||||
}
|
||||
toggleAIHighlights();
|
||||
}}
|
||||
onClick={handleToggleAIHighlights}
|
||||
className={
|
||||
isAIHighlightsEnabled
|
||||
? 'w-10 h-10 flex items-center justify-center bg-cyan-500/15 border border-cyan-400/40 rounded-lg text-cyan-200 hover:bg-cyan-500/20 hover:border-cyan-300/60 transition-colors'
|
||||
|
|
|
|||
|
|
@ -90,13 +90,13 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
|
|||
return parts.join('```');
|
||||
};
|
||||
|
||||
const handleLinkClick = (e: React.MouseEvent<HTMLAnchorElement>, href: string) => {
|
||||
const handleLinkClick = React.useCallback((e: React.MouseEvent<HTMLAnchorElement>, href: string) => {
|
||||
if (href.startsWith('code-ref:') || href.startsWith('node-ref:')) {
|
||||
e.preventDefault();
|
||||
onLinkClick?.(href);
|
||||
}
|
||||
// External links open in new tab (default behavior)
|
||||
};
|
||||
}, [onLinkClick]);
|
||||
|
||||
const formattedContent = React.useMemo(() => formatMarkdownForDisplay(content), [content]);
|
||||
|
||||
|
|
@ -176,7 +176,7 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
|
|||
);
|
||||
},
|
||||
pre: ({ children }: any) => <>{children}</>,
|
||||
}), [onLinkClick]); // Removed handleLinkClick dependency as it is defined inside component but depends on onLinkClick
|
||||
}), [handleLinkClick]);
|
||||
|
||||
return (
|
||||
<div className="text-text-primary text-sm">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue