fix: prevent dependency graph from going blank during agent activity

- Memoize onNodeClick callback in App.tsx to prevent unnecessary re-renders
- Add useRef pattern in DependencyGraph to store callback without triggering
  useMemo recalculation when callback identity changes
- Add hash-based change detection to only update ReactFlow state when
  actual graph data changes (node status, edges), not on every parent render
- Add GraphErrorBoundary class component to catch ReactFlow rendering errors
  and provide a "Reload Graph" recovery button instead of blank screen
- Wrap DependencyGraph with error boundary and resetKey for graceful recovery

The root cause was frequent WebSocket updates during active agent sessions
causing parent re-renders, which created new inline callback functions,
triggering useMemo/useEffect chains that corrupted ReactFlow's internal state
over time (approximately 1 minute of continuous updates).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-01-17 14:19:56 +02:00
parent bf3a6b0b73
commit 76e6521331
2 changed files with 127 additions and 23 deletions

View File

@@ -125,6 +125,17 @@ function App() {
}
}, [])
// Handle graph node click - memoized to prevent DependencyGraph re-renders
const handleGraphNodeClick = useCallback((nodeId: number) => {
const allFeatures = [
...(features?.pending ?? []),
...(features?.in_progress ?? []),
...(features?.done ?? [])
]
const feature = allFeatures.find(f => f.id === nodeId)
if (feature) setSelectedFeature(feature)
}, [features])
// Validate stored project exists (clear if project was deleted)
useEffect(() => {
if (selectedProject && projects && !projects.some(p => p.name === selectedProject)) {
@@ -386,16 +397,7 @@ function App() {
{graphData ? (
<DependencyGraph
graphData={graphData}
onNodeClick={(nodeId) => {
// Find the feature and open the modal
const allFeatures = [
...(features?.pending ?? []),
...(features?.in_progress ?? []),
...(features?.done ?? [])
]
const feature = allFeatures.find(f => f.id === nodeId)
if (feature) setSelectedFeature(feature)
}}
onNodeClick={handleGraphNodeClick}
/>
) : (
<div className="h-full flex items-center justify-center">