From 94869f342eadaf855638049656e065b7c4f5e718 Mon Sep 17 00:00:00 2001 From: mAi Date: Fri, 15 May 2026 20:33:17 +0200 Subject: [PATCH] fix(ui): +Dev inside a frame was silently dropped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit onCanvasPointerDown returned early whenever the click landed on a [data-device-id] or [data-frame-id] element so the per-element drag handlers wouldn't get hijacked. Problem: this early-return fired BEFORE the tool check, so clicking +Dev inside an existing frame never reached placeDeviceAt(). Reordered: tool-armed branches run first and short-circuit. Only when no tool is armed does the "click started on a child element — leave it alone" guard kick in. End behaviour: - +Dev anywhere (incl. inside a frame) drops a device. frame_id auto-resolves via the existing frameAt() point-in-rect. - +Frm anywhere (incl. inside an existing frame) starts a rubber-band; rare but not harmful. - No tool armed: clicking a device/frame still goes to its own handler (drag / select). Clicking empty canvas still clears selection. Hand-tested via the served /main.js + the equivalent backend POST/PATCH sequence: device-in-frame, device-outside, device-drag, frame-drag with cascaded device patches — all work. --- web/static/main.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/web/static/main.js b/web/static/main.js index 715dd4b..68dd8a0 100644 --- a/web/static/main.js +++ b/web/static/main.js @@ -458,12 +458,13 @@ let rubberStart = /** @type {{x:number,y:number}|null} */ (null); function onCanvasPointerDown(e) { if (!state.active) return; - // Ignore clicks that started on a device/frame — their own handlers - // captured the pointer already. - if (e.target instanceof Element && e.target.closest("[data-device-id], [data-frame-id]")) return; const p = svgPoint(e); + // Armed tool wins: a click anywhere on the canvas — including on top + // of an existing frame or device — fires the tool. The +Dev tool needs + // this so m can drop a device inside a frame; without it the frame's + // own pointerdown handler would steal the click and start a drag. if (state.tool === "frame") { startFrameRubberBand(e, p); return; @@ -472,6 +473,11 @@ function onCanvasPointerDown(e) { placeDeviceAt(p); return; } + + // No tool armed: clicks that started on a device/frame go to their + // own handlers (drag / select). Leave them alone. + if (e.target instanceof Element && e.target.closest("[data-device-id], [data-frame-id]")) return; + // Plain canvas click = clear selection. if (state.selection) { state.selection = null; render(); } }