From 55b3dd29e5b7e48cdce4a63b304e41d320406615 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Mon, 18 May 2026 22:07:42 -0300 Subject: [PATCH] fix(DynamicComponent): pass componentId through to PinTracer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `PinTracer` signature is `(componentId, componentPinName) => number | null` but the local `getArduinoPin` lambda only accepted one arg and used the closure-captured `id`. When `createDefaultPinResolver` passed both args (per the typed signature), JS bound the FIRST arg (the componentId) into the lambda's single `componentPinName` parameter. `traceDetailed` then looked up a pin literally named "rgb-led-1" on component "rgb-led-1", returned null, and the resolver locked itself into 'FLOATING' state — its onChange path never subscribed and the wokwi-rgb-led element's ledRed/ledGreen/ledBlue stayed at 0 forever even as the SPICE side correctly cycled through R, G, B, Y, C, M, W via analogWrite(). Same bug latent for any multi-pin component that goes through the PinResolver path (multi-pin LEDs, RGB strips, 7-seg drivers, anything that calls `getPinResolver()` for several pin names). Fix: lambda now accepts both shapes — `getArduinoPin(pinName)` (legacy single-arg used by every PartSimulationRegistry handler) AND `getArduinoPin(componentId, pinName)` (PinTracer 2-arg form used by createDefaultPinResolver / createSpiceResolvedPinResolver). Picks the right componentId in either case. Verified via the rgb-led example: ledRed/ledGreen/ledBlue now cycle 0→255→0 in sync with the SPICE node voltages on pins 9/10/11. Co-Authored-By: Claude Opus 4.7 --- frontend/src/components/DynamicComponent.tsx | 26 +++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/DynamicComponent.tsx b/frontend/src/components/DynamicComponent.tsx index 19d0952f..7c6e8e3c 100644 --- a/frontend/src/components/DynamicComponent.tsx +++ b/frontend/src/components/DynamicComponent.tsx @@ -369,11 +369,29 @@ export const DynamicComponent: React.FC = ({ // Helper to find Arduino pin connected to a component pin. // Traces through electrically-transparent passive components so that a // circuit like LED-cathode → resistor → GND returns -1 (GND) instead - // of null. Delegates to the module-level `traceDetailed` (shared with - // getPinResolver). - const getArduinoPin = (componentPinName: string): number | null => { + // of null. Delegates to the module-level `traceDetailed`. + // + // Two call shapes are supported because this same function is passed + // BOTH to PartSimulationRegistry handlers (which call it as + // `getArduinoPin(componentPinName)`) AND to `createDefaultPinResolver` + // as a `PinTracer` (which calls it as `tracePin(componentId, + // componentPinName)`). When the second arg is present we treat the + // first as a componentId override; otherwise we use the closure- + // captured component id. The previous single-arg signature silently + // matched the PinTracer 2-arg call as `(componentId, undefined)` — + // traceDetailed then looked up a pin literally named "rgb-led-1" on + // component "rgb-led-1", got null, and the PinResolver reported + // FLOATING forever (the canonical "wokwi-rgb-led never lights up + // even though SPICE is driving R/G/B" symptom). + const getArduinoPin = ( + componentIdOrPin: string, + maybePinName?: string, + ): number | null => { const state = useSimulatorStore.getState(); - return traceDetailed(state, id, componentPinName, 0).arduinoPin; + const componentId = maybePinName !== undefined ? componentIdOrPin : id; + const componentPinName = + maybePinName !== undefined ? maybePinName : componentIdOrPin; + return traceDetailed(state, componentId, componentPinName, 0).arduinoPin; }; // PinResolver factory — Phase 0 of the mixed-mode simulator project