[eric] canvas: the marquee inverts the PAINTED transform instead of a second camera copy, so a drawn-vs-painted mismatch can't exist

This commit is contained in:
ciregenz
2026-08-09 10:28:23 -07:00
parent 6bd3f93f07
commit 5f6cf216ad
3 changed files with 13 additions and 0 deletions
@@ -468,6 +468,7 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
{(
<div
ref={canvas.contentRef}
data-canvas-content
style={{
transform: `translate(${canvas.panX}px, ${canvas.panY}px) scale(${canvas.zoom})`,
transformOrigin: '0 0',
@@ -911,6 +911,8 @@ export function useCanvasControls(
}, [applyLive]);
const getLiveState = useCallback((): CanvasState => 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<string, unknown>).__OSW_LIVE_CAM__ = getLiveState;
const actions = useMemo(() => ({
zoomIn, zoomOut, resetZoom, fitToView, fitToCards, fitTidy, revealCards, animateTo, cancelAnimation,
@@ -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,