From 1a1bfd095734b4ccbcfc287dc790156c39896dee Mon Sep 17 00:00:00 2001 From: ciregenz Date: Wed, 5 Aug 2026 20:07:48 -0700 Subject: [PATCH] [eric] canvas: marquee converts through the LIVE camera, stale mid-glide selection rects sat far from the mouse --- .../hooks/state/useDashboardController.ts | 2 +- .../Dashboard/hooks/state/useDashboardSelection.ts | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts index 1ee53637..af08154f 100644 --- a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts +++ b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts @@ -70,7 +70,7 @@ export function useDashboardController(dashboardId: string, isActive: boolean) { const canvas = useCanvasControls(zoomSensitivity, contentBounds, isActive, mouseWheelAction); const selection = useDashboardSelection( - { panX: canvas.panX, panY: canvas.panY, zoom: canvas.zoom, viewportRef: canvas.viewportRef }, + { getLiveState: canvas.actions.getLiveState, viewportRef: canvas.viewportRef }, cards, viewCards, browserCards, diff --git a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts index 70d179cb..f5ece92d 100644 --- a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts +++ b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts @@ -18,9 +18,9 @@ export interface MarqueeRect { } interface ScreenToCanvas { - panX: number; - panY: number; - zoom: number; + // The LIVE camera getter, never committed React state: a marquee drawn during a pan glide or + // inertia was converted with the stale pre-gesture camera and landed far from the mouse. + getLiveState: () => { panX: number; panY: number; zoom: number }; viewportRef: RefObject; } @@ -59,12 +59,14 @@ export function useDashboardSelection( const vp = canvas.viewportRef.current; if (!vp) return { x: 0, y: 0 }; const rect = vp.getBoundingClientRect(); + const cam = canvas.getLiveState(); return { - x: (screenX - rect.left - canvas.panX) / canvas.zoom, - y: (screenY - rect.top - canvas.panY) / canvas.zoom, + x: (screenX - rect.left - cam.panX) / cam.zoom, + y: (screenY - rect.top - cam.panY) / cam.zoom, }; }, - [canvas.panX, canvas.panY, canvas.zoom, canvas.viewportRef], + // The stable members, not the wrapper: the call site builds the wrapper object fresh per render. + [canvas.getLiveState, canvas.viewportRef], ); const isSelected = useCallback((id: string) => selectedIds.has(id), [selectedIds]);