From 5f6cf216ad02483988aa14aaa81b77cdecd22add Mon Sep 17 00:00:00 2001 From: ciregenz Date: Sun, 9 Aug 2026 10:28:23 -0700 Subject: [PATCH] [eric] canvas: the marquee inverts the PAINTED transform instead of a second camera copy, so a drawn-vs-painted mismatch can't exist --- .../src/app/pages/Dashboard/canvas/DashboardCanvas.tsx | 1 + .../Dashboard/hooks/interaction/useCanvasControls.ts | 2 ++ .../Dashboard/hooks/state/useDashboardSelection.ts | 10 ++++++++++ 3 files changed, 13 insertions(+) diff --git a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx index 45658eb5..3251298a 100644 --- a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx +++ b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx @@ -468,6 +468,7 @@ const DashboardCanvas: React.FC = ({ {(
stateRef.current, []); + // Dev-only forensic handle: lets a CDP harness compare the live camera against the DOM transform. + if (process.env.NODE_ENV !== 'production') (window as unknown as Record).__OSW_LIVE_CAM__ = getLiveState; const actions = useMemo(() => ({ zoomIn, zoomOut, resetZoom, fitToView, fitToCards, fitTidy, revealCards, animateTo, cancelAnimation, diff --git a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts index 746a4d3d..3a28b9e4 100644 --- a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts +++ b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts @@ -66,6 +66,16 @@ export function useDashboardSelection( const vp = canvas.viewportRef.current; if (!vp) return { x: 0, y: 0 }; const rect = marqueeVpRectRef.current ?? vp.getBoundingClientRect(); + // Invert the PAINTED transform, not a second copy of the camera: the marquee renders inside + // that exact matrix, so deriving from it makes a drawn-vs-painted mismatch unrepresentable + // (a stale live-camera copy once put the rect ~240px left of the cursor). + const content = vp.querySelector('[data-canvas-content]') as HTMLElement | null; + if (content) { + const m = new DOMMatrixReadOnly(getComputedStyle(content).transform); + if (m.a > 0) { + return { x: (screenX - rect.left - m.e) / m.a, y: (screenY - rect.top - m.f) / m.a }; + } + } const cam = canvas.getLiveState(); return { x: (screenX - rect.left - cam.panX) / cam.zoom,