Fix popover tracking during pan/drag and remove dead code

- Use viewportVersion (not zoomDisplay) as activePopoverPosition dependency
- Fire onViewportChange based on actual viewport state diff (panX/panY/zoom)
  instead of only during animated transitions (vpMoving)
- Also fire during simulation so popover tracks settling nodes
- Remove dead lastReportedZoom variable
- Remove no-op node ID tracking effect and prevIdsRef
This commit is contained in:
Vorflux AI 2026-03-27 05:37:22 +00:00
parent 4e0bf5bb40
commit 8087d8a40d
2 changed files with 17 additions and 19 deletions

View file

@ -56,7 +56,7 @@ export const GraphCanvas = memo<ExtendedGraphCanvasProps>(function GraphCanvas({
)
// Node ID tracking for smart re-init
const prevIdsRef = useRef<string>("")
// All mutable render state in a single ref — the rAF loop reads from here
const s = useRef({
@ -106,18 +106,6 @@ export const GraphCanvas = memo<ExtendedGraphCanvasProps>(function GraphCanvas({
renderNeeded.current = true
}, [nodes])
// Track node ID changes for smart simulation re-init
useEffect(() => {
const idKey = nodes
.map((n) => n.id)
.sort()
.join(",")
if (idKey !== prevIdsRef.current) {
prevIdsRef.current = idKey
// IDs changed - full re-init needed (handled by parent)
}
}, [nodes])
useEffect(() => {
s.current.highlightIds = new Set(highlightDocumentIds ?? [])
renderNeeded.current = true
@ -200,7 +188,9 @@ export const GraphCanvas = memo<ExtendedGraphCanvasProps>(function GraphCanvas({
// Single render loop — runs for component lifetime, reads everything from refs
// biome-ignore lint/correctness/useExhaustiveDependencies: intentionally empty deps — all state read via refs inside tick(), not reactive props
useEffect(() => {
let lastReportedZoom = 0
let prevVpX = 0
let prevVpY = 0
let prevVpZoom = 0
const tick = () => {
rafRef.current = requestAnimationFrame(tick)
@ -247,9 +237,17 @@ export const GraphCanvas = memo<ExtendedGraphCanvasProps>(function GraphCanvas({
return
renderNeeded.current = false
// Report viewport changes (zoom + pan) so popover positions update
if (vpMoving && cb.current.onViewportChange) {
// Report viewport changes (zoom, pan, or simulation-driven node movement)
// so popover positions update. Covers: momentum, spring zoom, mouse drag, and sim.
const vpChanged =
vp.panX !== prevVpX ||
vp.panY !== prevVpY ||
vp.zoom !== prevVpZoom
if ((vpChanged || simActive) && cb.current.onViewportChange) {
cb.current.onViewportChange(vp.zoom)
prevVpX = vp.panX
prevVpY = vp.panY
prevVpZoom = vp.zoom
}
renderFrame(

View file

@ -453,7 +453,7 @@ export function MemoryGraph({
return nodes.find((n) => n.id === activeNodeId) ?? null
}, [activeNodeId, nodes])
// biome-ignore lint/correctness/useExhaustiveDependencies: zoomDisplay intentionally used as proxy for viewport state changes
// biome-ignore lint/correctness/useExhaustiveDependencies: viewportVersion intentionally used as proxy for viewport state changes
const activePopoverPosition = useMemo(() => {
if (!activeNodeData || !viewportRef.current) return null
const vp = viewportRef.current
@ -463,9 +463,9 @@ export function MemoryGraph({
screenY: screen.y,
nodeRadius: (activeNodeData.size * vp.zoom) / 2,
}
// zoomDisplay triggers re-computation on viewport changes (pan/zoom)
// viewportVersion triggers re-computation on any viewport change (pan + zoom)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeNodeData, zoomDisplay])
}, [activeNodeData, viewportVersion])
const activeVersionChain = useMemo(() => {
if (!activeNodeData || activeNodeData.type !== "memory") return null