diff --git a/frontend/src/components/simulator/SimulatorCanvas.tsx b/frontend/src/components/simulator/SimulatorCanvas.tsx index 026da3f3..768bc299 100644 --- a/frontend/src/components/simulator/SimulatorCanvas.tsx +++ b/frontend/src/components/simulator/SimulatorCanvas.tsx @@ -1,4 +1,5 @@ -import { useSimulatorStore, getEsp32Bridge } from '../../store/useSimulatorStore'; +import { useSimulatorStore, getEsp32Bridge, getBoardSimulator } from '../../store/useSimulatorStore'; +import { getProBoard } from '../../lib/proBoardRegistry'; import { useElectricalStore } from '../../store/useElectricalStore'; import { openDeviceGateway } from '../../lib/openDeviceGateway'; import React, { useEffect, useState, useRef, useCallback } from 'react'; @@ -206,6 +207,36 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => { } }, [registry, registryLoaded]); + // Overlay-registered boards: built-in peripheral attachment (LCD decoder on + // the board's own canvas, speaker, on-board button/keyboard forwarding). The + // overlay owns the wiring via ProBoardDef.attachBuiltins; we hand it the DOM + // element + the board's simulator/bridge handles shortly after run start + // (the element and bridge need a beat to exist) and dispose on stop/unmount. + useEffect(() => { + const cleanups: (() => void)[] = []; + boards.forEach((board) => { + const proDef = getProBoard(board.boardKind); + if (!proDef?.attachBuiltins || !board.running) return; + const timeout = setTimeout(() => { + const el = document.getElementById(board.id); + if (!el) return; + try { + cleanups.push( + proDef.attachBuiltins!({ + el, + sim: getBoardSimulator(board.id), + bridge: getEsp32Bridge(board.id), + }), + ); + } catch (e) { + console.warn(`[${board.boardKind}] built-in peripheral attach failed:`, e); + } + }, 500); + cleanups.push(() => clearTimeout(timeout)); + }); + return () => cleanups.forEach((fn) => fn()); + }, [boards]); + // Component selection const [selectedComponentId, setSelectedComponentId] = useState(null); diff --git a/frontend/src/lib/proBoardRegistry.ts b/frontend/src/lib/proBoardRegistry.ts index 3a46e306..248d5fd4 100644 --- a/frontend/src/lib/proBoardRegistry.ts +++ b/frontend/src/lib/proBoardRegistry.ts @@ -78,6 +78,17 @@ export interface ProBoardDef { /** Built-in microSD on a shared SPI bus: the CS pin the bridge must gate. * (A standalone SD card component still overrides this to un-gated.) */ builtInSdCsPin?: number; + /** Built-in peripheral attachment for a RUNNING board (LCD decoder onto the + * element's own canvas, speaker, on-board button/keyboard event forwarding). + * Called shortly after run start with the board's DOM element plus its + * simulator shim and ESP32 bridge (either may be null depending on the run + * path); must return a cleanup that detaches everything. */ + attachBuiltins?: (ctx: { + el: HTMLElement; + sim: unknown; + bridge: unknown; + }) => () => void; + /** Sidebar / toolbar accents (fall back to a neutral chip icon). */ icon?: string; color?: string;