diff --git a/ui/src/hooks/useFlowNavigation.test.ts b/ui/src/hooks/useFlowNavigation.test.ts index 1410c890..395979a1 100644 --- a/ui/src/hooks/useFlowNavigation.test.ts +++ b/ui/src/hooks/useFlowNavigation.test.ts @@ -129,13 +129,22 @@ describe("useFlowNavigation", () => { }); it("onNodeClick calls setFlow, setViewMode('flow'), and fitView", async () => { + const frames: FrameRequestCallback[] = []; + const raf = vi + .spyOn(globalThis, "requestAnimationFrame") + .mockImplementation((cb: FrameRequestCallback) => frames.push(cb)); const { result } = setup(); await act(async () => { await result.current.onNodeClick({} as any, { id: "a", type: "FUNCTION" } as any); }); expect(mockSetFlow).toHaveBeenCalled(); expect(mockSetViewMode).toHaveBeenCalledWith("flow"); + // Not in the same tick: React Flow has not rendered the new nodes yet, and a + // synchronous fitView measured the old view, zooming to the 2x cap. + expect(mockFitView).not.toHaveBeenCalled(); + frames.forEach((cb) => cb(0)); expect(mockFitView).toHaveBeenCalledWith({ padding: 0.15, duration: 250 }); + raf.mockRestore(); }); it("cache is keyed by graphVersion — same node, same version uses cache", async () => { diff --git a/ui/src/hooks/useFlowNavigation.ts b/ui/src/hooks/useFlowNavigation.ts index 83b3c497..15e70868 100644 --- a/ui/src/hooks/useFlowNavigation.ts +++ b/ui/src/hooks/useFlowNavigation.ts @@ -44,7 +44,9 @@ export function useFlowNavigation(allowedEdgeTypes: string[] = DEFAULT_ALLOWED) setFlow(layoutedNodes, flowEdges); setViewMode("flow"); - fitView({ padding: 0.15, duration: 250 }); + // After the frame that renders the new nodes: called synchronously, fitView + // measures the previous view and zooms to the 2x cap on the clicked node. + requestAnimationFrame(() => void fitView({ padding: 0.15, duration: 250 })); }, [rawNodes, rawEdges, graphVersion, allowedEdgeTypes, setFlow, setViewMode, fitView] ); diff --git a/ui/src/hooks/useLayoutComputation.ts b/ui/src/hooks/useLayoutComputation.ts index 5124be49..137d7936 100644 --- a/ui/src/hooks/useLayoutComputation.ts +++ b/ui/src/hooks/useLayoutComputation.ts @@ -88,7 +88,8 @@ export function useLayoutComputation(): void { if (generationRef.current !== gen) return; // stale async, discard setLayout(layoutedNodes, layoutedEdges); - fitView({ padding: 0.15, duration: 250 }); + // After the frame that renders the new layout, not against the old one. + requestAnimationFrame(() => void fitView({ padding: 0.15, duration: 250 })); })(); }, [ rawNodes,