diff --git a/web/static/main.js b/web/static/main.js index 68dd8a0..af1e907 100644 --- a/web/static/main.js +++ b/web/static/main.js @@ -465,11 +465,21 @@ function onCanvasPointerDown(e) { // 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. + // + // e.preventDefault() suppresses the compatibility mousedown's default + // focus-shift. Without it, the freshly-focused inline-namer input gets + // blurred ~6ms later by the browser's "focus nearest focusable ancestor + // or blur active" behaviour (SVG rects are not focusable), and the + // blur handler tears the namer down before m can type. Root cause + + // verified fix from sherlock's Playwright shift; see docs/sherlock-+dev-bug.md + // for the full trace. if (state.tool === "frame") { + e.preventDefault(); startFrameRubberBand(e, p); return; } if (state.tool === "device") { + e.preventDefault(); placeDeviceAt(p); return; } @@ -582,7 +592,14 @@ function promptInline(placeholder, cx, cy) { const input = fo.querySelector("input"); input.focus(); const done = (val) => { - if (activeNamer === fo) { fo.remove(); activeNamer = null; } + // Clear the flag *before* removing the node. Enter-key triggers a + // synchronous blur on the input, which re-enters done() — and if + // fo.remove() ran first, the second call hits a + // "node no longer a child" pageerror. Reordering makes the second + // re-entry a no-op (activeNamer is already null). + if (activeNamer !== fo) return; + activeNamer = null; + fo.remove(); resolve(val); }; input.addEventListener("keydown", (e) => {