From 52c0a615c1b69af81ee9fe1471029edff26fcb78 Mon Sep 17 00:00:00 2001 From: Vorflux AI Date: Fri, 27 Mar 2026 18:34:24 +0000 Subject: [PATCH] Perf: gate onViewportChange behind popover-visible check Only fire onViewportChange (which triggers React state update + reconciliation) when a popover is actually visible (selectedNodeId or hoveredNodeId is set). This avoids forcing 60fps React re-renders during plain panning/zooming when no popover needs repositioning. Also separate prevVp* tracking from the callback gate so viewport state is always current when a node is first hovered/selected. --- .../memory-graph/src/components/graph-canvas.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/memory-graph/src/components/graph-canvas.tsx b/packages/memory-graph/src/components/graph-canvas.tsx index 6b3e91e0..e1a66129 100644 --- a/packages/memory-graph/src/components/graph-canvas.tsx +++ b/packages/memory-graph/src/components/graph-canvas.tsx @@ -234,12 +234,19 @@ export const GraphCanvas = memo(function GraphCanvas({ return renderNeeded.current = false - // Report viewport changes (zoom, pan, or simulation-driven node movement) - // so popover positions update. Covers: momentum, spring zoom, mouse drag, and sim. + // Report viewport changes so popover positions update. + // Only fire when a popover is actually visible (selected or hovered node) + // to avoid triggering React re-renders at 60fps during plain panning. const vpChanged = vp.panX !== prevVpX || vp.panY !== prevVpY || vp.zoom !== prevVpZoom - if ((vpChanged || simActive) && cb.current.onViewportChange) { + if ( + (vpChanged || simActive) && + (cur.selectedNodeId || cur.hoveredNodeId) && + cb.current.onViewportChange + ) { cb.current.onViewportChange(vp.zoom) + } + if (vpChanged) { prevVpX = vp.panX prevVpY = vp.panY prevVpZoom = vp.zoom