Overlay QEMU-Linux boards are not Raspberry Pis: ProBoardDef.guestSetup
lets a board send one shell line at the boot prompt (hostname/PS1/clear)
to de-brand the generic image, sent before piBooted flips so uploads
cannot interleave; the workspace start button and power-on title carry
the board's own label for non raspberry-pi kinds; the compile console
line uses the board label instead of 'Raspberry Pi 3B'.
The QEMU branch of handleRun required compiledProgram, but Pi-family
boards never have one (handleCompile early-returns 'no compilation
needed' without producing firmware), so the toolbar Run always surfaced
'Compilation produced no firmware' instead of powering the board on.
Start them directly, same as the workspace Start button.
add_reader on the proto FIFO armed epoll but never delivered callbacks
under uvloop when the fd number recycled a just-closed socket fd
(nondeterministic per instance): the guest's GPIO lines sat unread in
the pipe and canvas LEDs stayed dark while serial kept flowing. Pump
the FIFO from a worker thread (select + os.read, like _watch_stderr)
and schedule _handle_gpio_line back onto the loop.
registerPiFamilyKind + ProBoardDef.piFamily route an overlay board through
the existing Raspberry Pi bridge path (WebSocket qemu, VFS panel, boot
terminal); register_pi_board_profile lets the overlay add the matching
PI_CONFIGS entry (cpu/image_set/...) at registration time.
Un crash entre ambos (el NameError de board_fqbn) dejaba defaults==rendered
con un sdkconfig stale, y el re-seed de kconfig no disparaba nunca mas en ese
dir persistente — medido: CONSOLE_SECONDARY quedo en usb_serial_jtag pese a
los defaults nuevos. El bloque de velxio_board.cmake pasa a despues.
El bloque de velxio_board.cmake usaba board_fqbn, que vivia en compile() y no
en la firma de _compile_in_dir: parametro nuevo con default None, propagado en
los dos call sites, y guard para el camino sin fqbn (tests directos).
Tres piezas:
- _arduino_board_flags lee <id>.build.board y <id>.build.cdc_on_boot de
boards.txt (cache, mismo patron que _arduino_variant).
- Cada compile escribe velxio_board.cmake (incluido OPTIONAL por el
CMakeLists ANTES de project(), asi los defines llegan a TODOS los
componentes — que Serial sea el CDC se decide dentro del core Arduino):
ARDUINO_<BOARD> siempre que boards.txt lo nombre (los sketches de vendor
hacen #ifdef ARDUINO_XIAO_ESP32S3), y ARDUINO_USB_CDC_ON_BOOT=1 +
ARDUINO_USB_MODE=1 cuando cdc_on_boot=1. USB_MODE va FORZADO a 1 (HWCDC
por USB-Serial-JTAG, la opcion de menu USBMode=hwcdc): el motor modela esa
consola, no la pila OTG/TinyUSB. Ir por archivo y no por env hace el flag
dependencia de configure: CMake reconfigura al cambiar.
- CONFIG_ESP_CONSOLE_SECONDARY_NONE en el sdkconfig: sin ella el ROM/IDF
espejan cada byte de log al USB y el monitor unico del emulador (que
mezcla ambos streams) mostraria el log entero dos veces en builds CDC.
setAdcVoltage/setAdcWaveform solo conocian el mapa del ESP32 clasico
(GPIO32..39): en un S3 devolvian false para TODOS los pines y el part no
podia alimentar el ADC. Ahora el shim consulta bridge.adcChannelForGpio si
existe (los puentes js exponen el mapa de su chip) y cae al mapa clasico
solo si no.
_normalize_options rellena arduinoVariant con _arduino_variant(board_fqbn),
que ya leia boards.txt; la plantilla ya consumia la clave. Sin esto todas las
placas S3 compilaban como esp32s3 generico: sin nombres Dx del XIAO y con
Serial en UART0, cuyo RX por defecto es GPIO44 = D7 del XIAO — un
pinMode(D7) tardio desinstala el driver y silencia los prints del loop
(medido en el banco con el ejemplo del Round Display).
El adaptador SPI del Esp32BridgeShim descartaba el MISO de los parts ('el worker
lo lleva por _spi_response'), cierto solo en la era QEMU: cualquier part SPI que
RESPONDE (una SD contestando CMD0) hablaba con nadie en modo js — medido como
SD.begin()=0 con sd_diskio reintentando CMD0 para siempre en el Round Display.
Ahora reenvia a bridge.setSpiResponse, que todos los puentes tienen: el de QEMU
lo manda al worker, y los motores js fijan el byte que su SpiForwarder devuelve
para ESTA transferencia (toda la cadena onByte corre sincrona dentro del
transfer del motor). El reposo lo restaura el decodificador compartido, que ya
completa cada byte ajeno con 0xff.
El boton conservaba animation:'none' inline fuera del estado requesting, y el
estilo inline gana a la clase: .velxio-ants quedaba con animationName none —
medido con getComputedStyle en staging. Inline solo mientras solicita permiso;
en el resto, undefined para que la clase anime.
El modulo es retrato con la botonera de pines en el borde INFERIOR y el sketch
dibuja en setRotation(1): segun el mapeo MADCTL del propio modelo (writePixel:
el borde superior del contenido rot-1 cae sobre el borde IZQUIERDO del canvas),
verlo derecho exige girar el modulo un cuarto horario — igual que en el banco
fisico. El mismo giro lleva los pines al borde izquierdo, de cara a la placa,
asi que los cables van rectos entre ambos en vez de por encima del cristal.
Cuatro tiras de gradiente repetido, una por borde, deslizando un periodo de
guion por ciclo — fondo y no border porque un borde CSS no puede animar su
dash-offset. currentColor hereda el color de estado (gris en reposo, rojo en
error). Se apaga en streaming/solicitando, y con prefers-reduced-motion.
Ojo del gato encerrado: el estilo inline usaba el shorthand background, que
pisa el background-image de la clase — pasa a backgroundColor.
Un sketch de ESP32-CAM existe para capturar, asi que esperar el click en el
toggle se lee como 'la camara esta rota': el guest arranca, esp_camera_init
completa contra el OV2640 modelado, y cam_hal agota el tiempo eternamente
esperando fotogramas que nadie envia — sin pedir siquiera el permiso.
Ahora la webcam se solicita al iniciar la ejecucion; el prompt del navegador ES
el consentimiento, y el toggle queda como apagador manual. Solo una vez por
ejecucion: parar el stream a mano no lo re-dispara.
Estaban a zIndex 0 con los componentes a 1: cualquier solape las escondia. El
caso que lo decide es el apilado — una XIAO posada en el zocalo del Round
Display debe ser lo que se ve, como la placa recien soltada queda arriba del
monton. Los montajes lado a lado no solapan, asi que nada mas cambia.
Como el iman de la breadboard pero para PLACAS: un componente puede declarar en
su elemento (estilo regla 6a, igual que pinInfo) que lleva un zocalo:
get boardSocket(): { anchorPin, accepts }
y una placa cuyo boardKind case con accepts, arrastrada cerca, se posa de golpe
con su pad anchorPin sobre el homonimo del zocalo. Un solo ancla basta porque
ambas rejillas comparten paso — esa es la gracia de un zocalo. Arrastrarla mas
alla de la tolerancia la suelta, sin estado que recordar.
Enganchado en los dos caminos de arrastre de placas (raton y tactil). El
overlay privado declara el zocalo sin que este repo sepa que existe.
Sin esta linea la variante se queda en el nombre del chip, asi que TODA placa
compila como dev-kit generico y variants/<VARIANTE>/pins_arduino.h nunca entra
en el include path: los nombres propios de la placa (D0..D10 en una XIAO, la
serigrafia de las M5) sencillamente no existen. Justo los sketches que la gente
copia de la wiki del fabricante.
El helper que deriva la variante del FQBN leyendo boards.txt ya estaba; faltaba
la plantilla que la consume. Sigue pendiente rellenar 'arduinoVariant' en
normalized para que surta efecto (ver F7.6 en velxio-prod).
Los dos ejemplos de camara (esp32cam-lcd-preview y esp32cam-webcam-demo) morian
con "esp_camera.h: No such file or directory".
No es que el ejemplo estuviera mal: es una regresion de la migracion. El arbol
viejo /opt/arduino-esp32 (2.x) traia un SDK precompilado con esp32-camera dentro,
en tools/sdk/<target>/include/esp32-camera/, para esp32, c3, s2 y s3. El
componente 3.x que usamos ahora NO lo trae, y su idf_component.yml tampoco
depende de el, asi que el header simplemente dejo de existir. Por eso funcionaba
antes y dejo de funcionar sin que nadie tocara el ejemplo.
Mezclar los headers de la 2.x en este build no es opcion — vienen con su SDK
precompilado y aqui compilamos contra IDF 5. Lo correcto en el mundo 3.x es
pedirle el componente al gestor: la plantilla no traia idf_component.yml, asi que
todo lo gestionado llegaba de rebote por el manifiesto de arduino-esp32, y lo que
el no arrastra hay que pedirlo explicitamente.
_detect_camera_usage() ve el include y _add_managed_components() escribe
main/idf_component.yml con espressif/esp32-camera. Verificado en el contenedor
sobre el mismo directorio que fallaba: el gestor descarga 2.1.7, el configure
termina con esp32-camera en la lista de componentes, y sketch.ino.cpp compila.
Tres cosas que salieron al tirar del hilo de los pull-ups.
1) ESP32_ADC_PIN_MAP era la tabla del ESP32 CLASICO aplicada a toda la familia, y
fallaba de dos maneras:
- S3: sus pines ADC son GPIO1..20, ninguno esta en esa tabla, asi que la
busqueda devolvia undefined y el listener del potenciometro no llegaba a
engancharse. El mando no hacia nada.
- C3/C6: GPIO0 SI esta en la tabla clasica, como ADC2_CH1 -> canal 9. Pero en
esos chips GPIO0 es ADC1_CH0 y sus motores toman un indice de canal de ADC1
(0..4). El valor se empujaba al canal 9, fuera de rango, descartado en
silencio mientras el firmware leia el 0. Una respuesta equivocada en vez de
ninguna, que es peor.
adcPinMapFor(boardKind) devuelve la tabla del chip.
2) El ejemplo de las gafas OLED en MicroPython declaraba sus botones con Pin.IN a
secas. Van entre 3V3 y el pin, asi que sin pull-down el pin queda FLOTANDO con
el boton abierto. El propio ejemplo ya lo avisaba en un comentario ("Add
Pin.PULL_DOWN if the pin floats") — ahora lo hace. Es un fallo de hardware de
verdad, no un artefacto del emulador.
3) gallery-run-gate.audit.test.ts: EditorToolbar bloquea el Run cuando el
verificador de circuito saca errores, asi que un circuito invalido no es
cosmetico, es la diferencia entre un ejemplo que arranca y uno que parece
muerto. Y esos defectos se esconden hasta que el circuito resuelve DE PUNTA A
PUNTA: c3-button llevaba un LED de 506 mA que nadie veia porque sus cables
apuntaban a pines inexistentes.
El test reproduce la puerta del Run tal cual (mismo snapshot de peor caso,
mismo buildInputFromStore, mismo verifyCircuit, ngspice de verdad) sobre los
227 ejemplos. Encontro tres mas sin resistencia en serie —nano-button-led,
mega-led-chase y mega-serial-control, 17 LEDs en total— y ahora quedan 0.
A partir de aqui, un ejemplo nuevo con un LED colgado del GPIO salta en CI.
Los dos colgaban el LED directo del GPIO. Con los cables del pulsador arreglados
el circuito por fin resuelve entero, y el verificador saca lo que llevaba ahi
desde siempre: 506,9 mA por un LED de 20 mA. Como un error de circuito bloquea la
ejecucion (EditorToolbar checkOrBlock devuelve false y saca el modal de
"ejecutar de todos modos"), el ejemplo se quedaba sin arrancar.
O sea que no era un fallo nuevo, era uno viejo que hasta ahora nadie podia ver
porque el circuito no llegaba a resolverse. 220 ohm en serie, igual que el resto
de ejemplos con LED (esp32-blink-led y esp32-doom ya lo hacian bien).
El canvas reenviaba la pulsacion de un pulsador cableado como
sendPinEvent(gpio, true), o sea "pulsado = ALTO". Para el idiom canonico de
Arduino — pin -> pulsador -> GND con INPUT_PULLUP, activo a nivel BAJO, que usan
13 de los 18 ejemplos con entrada de usuario — eso esta al reves: pulsar conducia
el pin a su nivel de REPOSO y soltarlo al ACTIVO. Ademas escribia directamente en
el latch de entrada del emulador por detras de connectDigitalInputsToMcu,
desincronizando la cache lastLevel de la que ese fichero se declara unico
escritor. La victima visible era esp32-doom: cuatro botones que hacian lo
contrario de lo que pulsabas.
No lo sustituyo por la polaridad opuesta, que seria la misma adivinanza al reves:
cuando el simulador resuelve entradas por el circuito (spiceDrivenInputs), el
nivel sale del propio cableado. El INPUT_PULLUP del guest se reporta ahora como
gpio_pull, estampa una resistencia de 45k al riel de 3V3, y cerrar el pulsador
cortocircuita el nodo contra la pata que tenga al otro lado. Sale bien tanto para
un boton a GND como para uno a 3V3, sin que nadie asuma nada. El atajo se queda
solo para simuladores que se salgan del modelo electrico.
Aparte, dos arreglos de cableado:
- boardPinToNumber devuelve -1 (no null) para pines de alimentacion y masa, y
la guarda comprobaba `=== null`. Asi que la pata de GND de cada boton colaba
y registraba un SEGUNDO par de listeners apuntando al pin -1.
- c3-button y pico-button-led cableaban sus pulsadores a los pines '1a' y '1b',
que NO EXISTEN: el wokwi-pushbutton expone 1.l, 2.l, 1.r y 2.r. Esos dos
cables llevaban colgando desde siempre, asi que esos botones no han
funcionado nunca. Pasan a 1.l / 2.l, como el resto de ejemplos.
boardPinToNumber no tenia rama para 'cardputer-adv', asi que caia al return
null del final: cada cable a su cabecera EXT o al Grove Port A se conectaba a
nada. La pieza quedaba en el lienzo, el sketch leia un pin muerto y nadie
avisaba de nada.
La rama compartida de esp32 tampoco habria servido: recorta en 39 y este board
saca G40 en la cabecera. El S3 tiene 48 GPIOs.
Arduino trata igual #include "Lib.h" que #include <Lib.h> para librerias, y
los ejemplos de los propios fabricantes usan la forma con comillas: los de
M5Stack para el Cardputer abren con #include "M5Cardputer.h". Al escanear solo
la forma con angulos, ese sketch no llegaba siquiera al resolutor de librerias y
moria con 'fatal error: M5Cardputer.h: No such file', mientras el mismo sketch
con angulos compilaba sin problema. Cualquiera que pegue un ejemplo oficial se
daba de bruces con esto.
Se pasan ademas los nombres de los ficheros del propio sketch, para que un
#include "Common.h" local siga siendo una cabecera del proyecto y no se
confunda con una libreria.
test/backend/unit/test_espidf_compiler.py cubre ambas formas, el espaciado, las
cabeceras internas de IDF y la cabecera propia del proyecto.
Se anclaba a la esquina inferior derecha del lienzo y tapaba una parte del area
util del circuito, que es justo donde se trabaja. Quien pana y hace zoom no lo
necesitaba para orientarse, asi que se retira entero en vez de esconderlo tras
un interruptor mas.
Se van el componente y su CSS (no los usaba nadie mas) y la linea del listado de
novedades que lo anunciaba, que ya no seria cierta.
Las dos ramas habian divergido: master llevaba el modo lenguaje ESP-IDF puro
(#139) y v3.2 la ruta de compilacion IDF v5.5 para toda la familia ESP32 mas
los arreglos de venv/toolchain. Ambas tocaban espidf_compiler.py.
Los dos lados son ejes ORTOGONALES y se conservan enteros:
- use_idf5 / arduino_mode (v3.2): que arbol IDF usa el build (5.5 vs 4.4) y
si cabe Arduino-como-componente.
- pure_idf (master): el modo LENGUAJE que elige el usuario; sus ficheros son
las fuentes del componente main con su propio app_main().
Resolucion:
- _build_env acepta los tres. Un build IDF puro fuerza arduino_mode a falso:
la plantilla CMake mete el componente arduino-esp32 en cuanto existe
ARDUINO_ESP32_PATH, asi que dejarlo puesto compilaba el core de Arduino en
un build que no tiene sketch. Lo cazaron los tests de master.
- VELXIO_PURE_SKETCH solo con pure_idf, nunca con arduino_mode a falso a
secas: un target sin core arduino-esp32 sigue entregando un SKETCH al
traductor legacy y no debe tomar la rama del glob puro.
- La identidad del build-dir suma los dos tokens (|idf:N|ard:N y |lang:pure):
ningun par de esas combinaciones puede compartir un build/ configurado.
- La cadena de escritura de fuentes queda pure_idf -> arduino_mode -> legacy.
- sdkconfig: render de v3.2 (con target/use_idf5) mas el filtrado de simbolos
CONFIG_ARDUINO* de master cuando el build es puro.
test/backend/unit/test_espidf_compiler.py: los 7 tests que ya estaban rotos en
v3.2 (AttributeError: idf5_path, fixture sin actualizar desde que se anadio la
seleccion de IDF) vuelven a pasar.
Verificado: backend 293 pasan / 0 fallan (v3.2 traia 7 rotos); frontend 2268
pasan / 0 fallan en los dos shards.
connectMcuEdgesToService resubscribes its per-pin listeners whenever
pinNetMap changes. The subscription swept pin NUMBERS 0..63 and
reverse-mapped them to names ('GPIO2' on ESP32, 'GPIO17' on Pi) to match
against pinNetMap's keys — but those keys are the WIRE pin names ('2',
'4', 'A0'), so after the first solve the match failed for every pin and
the resubscription attached nothing. Any mid-run pinNetMap update then
silently killed the MCU-edge → SPICE path and the canvas froze at the
last solved state while the firmware kept toggling.
Masked until now because nothing perturbed pinNetMap mid-run on the
blink examples; pure ESP-IDF mode (#139) unmasked it — gpio_reset_pin()
leaves the internal pull-up enabled, the worker reports gpio_pull, the
handler requests an electrical resolve, pinNetMap gets a new identity,
and the ESP-IDF blink example's LED froze ON.
Fix: subscribe FROM the pinNetMap names, mapped to PinManager pins with
the same pinNameToArduinoPin the netlist collector uses (STM32 via
stm32PinNameToLinear), and hand schedulePin the netlist name so
handleMcuEdge's v_<board>_<pin> lookup hits the fast alterSource path
instead of a full rebuild per edge. The 0..63 sweep remains as the
pre-first-solve fallback. Also fixed pinNameToArduinoPin's dead 'GPIO'
branch ('GP' tested first turned 'GPIO32' into parseInt('IO32') = NaN).
Adds a third entry to the board language selector next to Arduino C++
and MicroPython: ESP-IDF. In this mode the user writes a plain ESP-IDF
project — app_main() entry point, FreeRTOS + driver APIs — and the
backend compiles it through the same ESP-IDF toolchain it already uses
for ESP32 Arduino sketches, just without the arduino-esp32 component.
Backend:
- CompileRequest.language ('espidf') threaded through the sync + async
compile paths and folded into the dedup job key (language='arduino'
and omitted hash identically so old clients keep dedupping).
- espidf_compiler: pure_idf flag. User files are written into main/
as-is (no Arduino.h wrap, no velxio_compat.h, Arduino library
resolution skipped), ARDUINO_ESP32_PATH is dropped from the build env
and VELXIO_PURE_SKETCH raised so the template CMake compiles the
user's own sources via a glob branch. Pure builds get their own
persistent build-dir variant through the eff_hash fold.
- QEMU WiFi compat for IDF-style code: esp_wifi.h/esp_wifi_init
detection sets has_wifi, and literal #define SSID/PASS plus
wifi_config_t designated initializers are normalized to the QEMU AP.
- CONFIG_ARDUINO_* lines are stripped from sdkconfig.defaults in pure
mode (the symbols don't exist without the arduino component).
Frontend:
- LanguageMode gains 'espidf'; BOARD_SUPPORTS_ESPIDF covers the ESP32
family (Xtensa, S3, C3). Toolbar shows the option only for those.
- Switching modes seeds a main.c blink skeleton (app_main + gpio
driver), mirroring the MicroPython main.py flow.
- compileCode sends language='espidf'; run/stop paths are unchanged
(the QEMU worker consumes the same merged flash image).
- New gallery example: esp32-idf-blink (LED + resistor on GPIO 2).
Tests: unit coverage for the build-env switch, IDF wifi normalization,
job-key variance, file-group seeding and the new example; verified
end-to-end in a container from the prod image (pure build produces a
bootable flash image; Arduino-mode build unchanged, same variant hash).
componentToSpice/isSpiceMapped now consult a pro-registered mapper table after
the static MAPPERS, so a private build can give its closed components a SPICE
model (e.g. the DFRobot Gravity analog sensors emit a voltage source at AOUT).
Empty in a pure OSS build. Mapper type exported.
Round Display, Grove Gesture and ReSpeaker Lite now advertise themselves in
the OSS picker like the M5Stack matrices; they auto-hide in any build whose
overlay merges the real components (registry.getById).
A wire to a pro board's Dx pad (XIAO 'D2', etc.) did nothing because
isBoardComponent only knew the OSS board-id list, so the electrical/sensor
chain skipped the board endpoint. It now also returns true for any
proBoardRegistry kind, so boardPinToNumber (which already consults the pro
def's pinToNumber) resolves the Dx name. Fixes the numeric-alias-only
workaround in the XIAO examples.
registerSensorControls() lets a private build add SensorControlDef entries for
sensors it ships outside the OSS tree (e.g. the DFRobot Gravity analog family)
so they get the live slider panel; every SENSOR_CONTROLS lookup now goes
through getSensorControl(id) which falls back to the registered map. Dead code
in a pure OSS build, same contract as proBoardRegistry / registerComponentDoc.
xiao-esp32s3-sense / xiao-esp32c6 / xiao-rp2040 advertise themselves in the
OSS picker like the other hosted-only boards; the ads auto-hide in any build
whose overlay registers the real kinds (same BOARD_KIND_LABELS contract).
filteredComponents' new registryVersion dep evaluated in its useMemo deps
array while the const was still in the temporal dead zone (declared further
down the component) — 'Cannot access N before initialization', white screen
on every page. Hooks moved up next to the registry declaration.
The @pro overlay import is dynamic, so board/component registration can land
AFTER the picker mounted and memoized its lists. allBoards had frozen deps —
if the picker rendered first, overlay boards (M5Stack Core, Cardputer,
Pimoroni, C6) vanished for the whole session while their ONLINE ads were
already hidden; the component grid + ONLINE component ads flipped between ad
cards and real entries depending on who won the reload race (different SVG,
ONLINE badge appearing and disappearing, wrong hover thumbnail).
proBoardRegistry and ComponentRegistry.mergeComponents now bump a version and
notify subscribers; the picker's allBoards / filteredComponents /
visibleComponentAds memos key off those via useSyncExternalStore — same
contract as proRoutes and registerProExamples.
- The card preview creates the live element but only forwarded
defaultValues.value, so variants sharing a tag rendered identically (both
M5Stack Chain matrices showed the dark RGB housing — the mono flag never
reached the element). Forward every defaultValues entry, matching what
DynamicComponent assigns at placement.
- The hover datasheet panel sat at z-index 2000 while the picker overlay was
raised to 9000 (above the AI chat), hiding the popover behind the very
modal that summons it. Raise it to 9100.
Replaces the static hero-editor screenshot with the animated
estacion-meteorologica-esp32.gif (whole circuit executing). contain +
matching background so the full circuit stays visible instead of
cover-cropping it.
Boards with built-in hardware (LCD on the element's own canvas, speaker,
on-board buttons/keyboard) need run-time wiring between the DOM element and
the board's simulator shim / ESP32 bridge. One generic SimulatorCanvas effect
now hands those handles to the overlay's attachBuiltins shortly after run
start and runs its cleanup on stop — no board names in the OSS tree.
registerProExamples() now notifies useSyncExternalStore subscribers (same
contract as proRoutes): a direct URL to an overlay-registered example resolved
before the dynamic @pro import landed and stuck on the 404 branch. The three
example pages subscribe to the version counter.
registerProBoards() lets a hosted overlay ship boards outside the OSS tree as
data-only definitions: registration patches the exported BoardKind maps
(labels / FQBN / MicroPython) so every existing read site keeps working, and
the sites a map can't cover consult the registry — canvas render (custom
element or overlay render fn), BOARD_SIZE, pin-name mapping, picker list +
descriptions + tag, ESP32 family routing, in-browser simulator construction
and firmware load (structural ProBoardSimulator contract, duck-typed PIO
attach/detach), built-in bridge sensors, and a CS-gated built-in microSD
(sdCsPin -> sd_card.cs_pin worker config). Esp32Bridge additionally gains the
esp32-c6 machine type + TX pin (public chip knowledge — the C6 compile path
already ships) and a generic sendKey() for built-in matrix keyboards.
registerProExamples() appends gallery examples at runtime; the board ONLINE
ads recompute at render so registration hides them. OSS behavior without an
overlay is unchanged — the registry is dead code, same as the other seams.
Components that only exist in the hosted editor now advertise themselves in
the picker exactly like the online-only boards do: ONLINE_ONLY_COMPONENT_ADS
(same module as the board ads) renders an ONLINE-badged card that links to
velxio.com, auto-hidden in any build whose ComponentRegistry has the real
component (the hosted overlay merges it in — no per-build switches). First
entries: the M5Stack Chain RGB/Mono 8x8 matrices. componentDocs gains
registerComponentDoc(id, raw) so an overlay can supply the hover datasheet
for components it injects at runtime.
Ports the v5 compiler layer: /opt/esp-idf-v5 + arduino-esp32 3.x
discovery (IDF5_PATH / ARDUINO_ESP32_5_PATH overridable), per-compile IDF
selection routing the family to the v5 tree, esp32c6 target support, and
the docker-runtime hardening (version-matched python venv pinned on PATH,
toolchain ordering by the tree's tools.json, user-libs component REQUIRES
for the IDF components Arduino libraries include directly). The LED_BUILTIN
fallback in the project template becomes a whitelist so non-esp32 variants
(C6 first) never hit the redefinition trap. Includes the option tests.
The newest-env-wins heuristic pinned a 5.x env for a 4.4 tree — each
generation's dependency check only accepts its own requirement set. The
version is read from tools/cmake/version.cmake of the active IDF tree,
falling back to the newest env when unreadable.
Two failure modes seen on a fresh docker deploy where the final image's
system python differs from the one the IDF venv was created with:
- IDF's cmake derives the venv dir name from the SYSTEM python version
and fails with 'python doesn't exist' before configure; and when cmake
is invoked directly (not via idf.py) its PYTHON property falls back to
the bare 'python' from PATH. The Linux branch of _build_env now pins
IDF_PYTHON_ENV_PATH to the newest env under the tools root and puts its
bin first on PATH (mirroring what the Windows branch already did).
- When several toolchain generations share one tools root, glob order
decided which won the PATH race. Toolchain dirs are now ordered by the
versions the IDF tree declares in tools/tools.json.
Gallery templates and agent-loaded projects can store element tag names
("wokwi-lcd2004") where the registry keys by the bare id ("lcd2004").
getById now falls back to the stripped id, so such a component renders
instead of sitting invisible in the store — a real agent session shipped
an LCD counter whose LCD existed in the store, was wired and validated,
and simply never appeared on the canvas.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root cause of "el agente construye el reloj, dice que funciona, pero el
display queda en blanco hasta recargar la página" — diagnosed by driving
the live agent end-to-end and instrumenting the element:
The 7-segment part simulator caches its state (segments, digitValues,
digitEnabled) in a WeakMap keyed by the DOM element, sizing it from
element.digits at FIRST access. The agent builds incrementally: it adds
the display with the default digits=1 and only then sets digits=4 — so
the cached state was born in single-digit mode. Every later attachEvents
(compile bumps hexEpoch → re-attach with the finished wiring) kept
consulting the stale state: it subscribed COM.1/COM.2 (which don't exist
on a 4-digit part) instead of DIG1..DIG4, and because those resolvers DID
attach, the all-digits-on fallback never kicked in either. Result: no
digit ever enabled, no flush ever ran, values stayed a frozen 8-zero
array. A page reload "fixed" it because the fresh element mounted with
digits already 4.
get7SegState now compares the cached digit count against the element's
current value and rebuilds the state when they differ, so any re-attach
after a digits change subscribes the right pins.
Test: attach with digits=1 (COM subscribed), set digits=4, re-attach →
DIG1..4 subscribed, and a segment+digit pulse actually lights values[0]
in the 32-slot array.
Verified live: the exact agent prompt that produced a permanently blank
display now shows the multiplexed digits + blinking colon in-session,
no reload.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>