From 75842e366893f67f3370bdea0714ce1568a6a186 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Sun, 9 Aug 2026 08:13:48 +0200 Subject: [PATCH] fix(wires): recalculate endpoints when a late-defined element upgrades MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The canvas re-derives wire endpoints on load-settle timers (100/300/ 500ms), but an overlay-defined custom element can upgrade after the last one — its chunk is large — and calculatePinPosition then finds no pinInfo, so every wire to the part falls back to its corner and floats there until something moves it (field report: the Grove gesture module with all four Grove wires dangling on bare PCB). A customElements .whenDefined hook now recalculates when the tag actually defines, however late that is. --- frontend/src/components/DynamicComponent.tsx | 30 +++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/DynamicComponent.tsx b/frontend/src/components/DynamicComponent.tsx index 8290b51a..f9dc311c 100644 --- a/frontend/src/components/DynamicComponent.tsx +++ b/frontend/src/components/DynamicComponent.tsx @@ -446,7 +446,34 @@ export const DynamicComponent: React.FC = ({ } return false; }; - if (tryReseat()) return; + // Wires resolve through the same pinInfo. The canvas re-derives wire + // endpoints on load-settle timers (100/300/500ms), but an overlay-defined + // custom element can upgrade LATER than the last timer — its chunk is + // large — and then every wire to it stays snapped to the part's corner + // until something moves. Recalculate when the tag actually defines, + // however late that is (resolves immediately when already defined). + let cancelled = false; + customElements + .whenDefined(metadata.tagName) + .then(() => { + requestAnimationFrame(() => { + if (cancelled) return; + try { + useSimulatorStore.getState().recalculateAllWirePositions(); + } catch { + // headless tests + } + }); + }) + .catch(() => { + // invalid tag name: nothing will ever mount, nothing to recalc + }); + + if (tryReseat()) { + return () => { + cancelled = true; + }; + } // Same cadence as the pinInfo-ready poll above: the custom element may // upgrade a few frames after React commits. const interval = setInterval(() => { @@ -454,6 +481,7 @@ export const DynamicComponent: React.FC = ({ }, 100); const timeout = setTimeout(() => clearInterval(interval), 2000); return () => { + cancelled = true; clearInterval(interval); clearTimeout(timeout); };