From b384bc817867eba4a31fc5a447ba2f04b83d3240 Mon Sep 17 00:00:00 2001 From: Wassim SAMAD Date: Sat, 27 Jun 2026 17:34:42 -0400 Subject: [PATCH] perf(editor): skip floorplan viewport sync while the 2D panel is hidden MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Camera-zoom hitch: the FloorplanPanel stays mounted (display:none in 3D mode), so its navigation-pose subscriber fired every camera onUpdate. During zoom the view-width changes continuously, so the epsilon guard never short-circuited and `syncFloorplanViewportToNavigationPose` ran each frame → setViewport/ setFloorplanUserRotationDeg → a full re-render of the ~10k-line floorplan SVG, even though nothing is visible (React reconciles display:none subtrees). Gate the viewport sync on `isFloorplanOpen` via a ref the per-frame subscriber reads, and re-run the mount catch-up effect when the panel reopens so the viewport snaps to the current camera. The compass is unaffected — it's portaled to the always-visible viewer area and still receives the pose; only the panel's own viewport sync is skipped while hidden. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/components/editor/floorplan-panel.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/editor/floorplan-panel.tsx b/packages/editor/src/components/editor/floorplan-panel.tsx index fb856529..ac86095a 100644 --- a/packages/editor/src/components/editor/floorplan-panel.tsx +++ b/packages/editor/src/components/editor/floorplan-panel.tsx @@ -5046,6 +5046,11 @@ export function FloorplanPanel({ // the user closes and re-opens the 2D editor instead of restoring the // stale viewport from before they closed it. const isFloorplanOpen = useEditor((state) => state.isFloorplanOpen) + // Mirror for callbacks that fire outside React's render (the per-frame + // navigation-pose subscriber): when the 2D panel is hidden (`display:none` in + // 3D mode) it must NOT re-render on every camera-zoom frame. + const isFloorplanOpenRef = useRef(isFloorplanOpen) + isFloorplanOpenRef.current = isFloorplanOpen const selectedReferenceId = useEditor((state) => state.selectedReferenceId) const setSelectedReferenceId = useEditor((state) => state.setSelectedReferenceId) const setMode = useEditor((state) => state.setMode) @@ -6441,6 +6446,13 @@ export function FloorplanPanel({ const syncFloorplanViewportToNavigationPose = useCallback( (pose: NavigationSyncPose) => { + // Skip the viewport sync while the 2D panel is hidden (3D mode). It writes + // React state (`setViewport`) that re-renders the whole floorplan SVG, so + // doing it every camera-zoom frame for an invisible panel was a needless + // per-frame stall. The catch-up effect below re-syncs on reopen. + if (!isFloorplanOpenRef.current) { + return + } if (floorplanRotationStateRef.current) { return } @@ -6471,9 +6483,12 @@ export function FloorplanPanel({ latestNavigationSyncPoseRef.current = pose if (pose.source === '3d') { + // Re-runs when the panel reopens (`isFloorplanOpen`) so the viewport + // catches up to the camera after the per-frame sync was skipped while + // hidden; a no-op while closed (the sync early-returns). syncFloorplanViewportToNavigationPose(pose) } - }, [syncFloorplanViewportToNavigationPose]) + }, [isFloorplanOpen, syncFloorplanViewportToNavigationPose]) useEffect(() => { return useEditor.subscribe((state) => {