The Camera button was hard-gated to the esp32-cam board kind. The Sense now
runs its camera through the S3 engine's LCD_CAM model, fed by the same
useWebcamFrames -> sendCameraFrame path, so the toggle shows for it as well.
A ProBoardDef can declare builtInSdCsPin, and the store already built a FAT
card from the project files for it - but there was no way to put YOUR files
on that card: the upload panel only existed for the microsd-card component
and for parts with metadata.sdSlot, and boards have no properties bag.
Boards now carry sdFiles (same shape the SD panel persists everywhere else):
- BoardInstance.sdFiles, consumed by the store's Run path (card component
still wins when one is on the canvas);
- the board inspector shows the SD Card panel whenever the board kind
declares builtInSdCsPin, and routes the panel's onChange to updateBoard;
- .vlx round-trips it (serialisableBoard + payload type), and gallery
examples can pre-load the slot via ExampleBoard.sdFiles, applied by
loadExample the way the panel would.
First user: the XIAO ESP32S3 Sense (CS=GPIO21), whose board def and example
land in the pro overlay.
- Rotate/Delete (and the board actions) moved OUT of the left column into a
bar across the bottom of the dialog, so they are in the same place on both
tabs. Buy joins them, right-aligned: it used to be the last thing in the
datasheet body, which meant scrolling that pane to the end to reach it, and
it did not exist at all on the Properties tab.
- The datasheet scrolled sideways because the doc tables have a nowrap first
column (the pin name) and wide pinouts pushed past the pane. The dialog is
wider (720px, still clamped to the viewport so it cannot run off the right
edge) and the doc now wraps: fixed table layout, wrapping first column,
pre-wrap on code blocks, overflow-x hidden.
Switching Properties <-> Datasheet resized the whole dialog: each tab sized
to its own content, so a short property list shrank it and a long datasheet
grew it. The tab pane is now a fixed 360px (capped at 46vh) and each tab
scrolls inside it, so the dialog holds still. The mobile full-sheet keeps
flowing as one column.
Rotate and Delete were outline buttons; the brand kit says interactive
surfaces are solid accent blue (--blue-600 #0071e3, hover --blue-500) and
destructive ones solid danger red (--red-500 #ff453a), both with white text.
They use the app's own tokens rather than hardcoded hex.
The preview effect still depended on componentMetadata BY IDENTITY. For
components that is stable (the registry hands back the same object), but for
BOARDS the canvas builds the metadata inline per render, so every parent
re-render - which a pointer moving over the canvas causes - tore the preview
element down and rebuilt it. The dialog visibly resized and snapped back.
It now keys on a digest of the metadata (id, tagName, defaults) and reads the
object through a ref, joining propsKey and pinsKey which were fixed the same
way earlier. Nothing rebuilds unless something real changed.
91 of the 156 catalogue parts have neither an authored doc nor a metadata
description, so the Datasheet tab rendered nothing but tag chips and read as
broken. It now states plainly that no datasheet is written yet, and points at
the pin map, which IS the useful content for those parts. The picker hover
card gets the same line, since both render one body.
Three things from using it:
- The pin map is why the dialog gets opened, so hiding it behind a checkbox
meant every visit started with a click. Pins are always drawn now and the
toggle is gone (with its translation key).
- The dialog sat at z-index 1300 and the pro agent chat panel docks over the
canvas at 8000, so on a wide layout the dialog opened UNDERNEATH the chat -
invisible, on the very area the chat covers. It now sits at 8500: above the
chat, still below the component picker.
- On a 32-pin board a name in the gutter could not be matched to a pad by
eye. Hovering (or focusing) either the label or the dot now lights the
whole run - label, leader line and pad together.
Right-clicking a board opened a small bespoke menu (Board Options... / Flash
to real board / Remove board) with its styling inline, while components got
the inspector. Boards now open the SAME dialog: the live board art with every
pin labelled at its real position, and an actions column that carries Board
Options and Flash instead of Rotate (boards do not rotate), with the delete
button reading "Remove board" and the wire count moved into the confirmation.
The dialog grew four optional props to make that possible without a second
implementation: extraActions, deleteLabel/deleteHint, and
previewTagName/previewAttributes - boards select their art through a
board-kind ATTRIBUTE rather than a property, so the preview needs to set it.
Also adds the boardOptions and flashToBoard strings to the nine locales: the
old menu hardcoded them in English, so those two items never translated.
205 lines of inline-styled menu are gone.
Three things the dialog got wrong in use:
1. Missing pins. Classification was nearest-edge with an "interior" bucket,
and interior pins printed no label - only a dot with a tooltip. The
ReSpeaker Lite showed it: its XIAO socket is two INSET columns (x=50.5 and
126.5 on a 178-wide body) plus two breakout columns, so most of its pins
were called interior and the part looked like it was missing them. The
classifier now finds the LINES first - pins sharing an x are a column,
pins sharing a y a row - assigns each line to the side of the body it sits
on however far inset it is, and joins label to dot with a leader. Only a
lone pad falls back to nearest-edge. Every pin gets a printed label; that
is now the contract, covered by a ReSpeaker test with its real table.
2. The panel was cut off on the Datasheet tab until some unrelated event
re-rendered it. The datasheet loads asynchronously, so the one-shot
measure placed the dialog while it was still short and it then grew off
screen. A ResizeObserver repositions on any size change, and the tab body
caps its own height so long markdown scrolls inside the tab.
3. Flicker when the pointer left and re-entered. The preview effect depended
on componentProperties and pinInfo by identity - both are rebuilt on every
parent render (pinInfo is a getter) - so the element was torn down and
remounted constantly. It now depends on stable digests of them.
An SSD1306 has 8 pins along a 240px header. Horizontal labels need ~36px
each = 288px, so they overflowed the art box and rendered outside the dialog,
over the header and the canvas. Top/bottom labels are now vertical text - what
printed pinout diagrams do - which needs only the side spacing, and the gutter
grows to fit. The preview also clips, so nothing can escape again.
The right-click dialog was a 250px column: every pin as a text row (an ESP32
means 30 rows of scrolling), a few editors, Rotate/Delete. The picker's info
card (datasheet, brand, Buy) was a separate surface you could only reach by
hovering the catalogue. One dialog now serves both:
- ~560px, two panes: live preview + actions on the left, Properties/Datasheet
tabs on the right. The Datasheet tab IS the picker card's body -
ComponentInfoPanel was split into an embeddable ComponentInfoBody (+
useComponentDoc, and its .cip-* styles moved to their own stylesheet) so
both surfaces render one implementation.
- "Show pins" (off by default): the preview is the REAL web component,
mounted and scaled, with pin markers at their true pinInfo positions and
labels flanking the side each pin lives on. pinInspectorLayout is pure
geometry - nearest-edge classification, per-side label stacking with a
minimum spacing and leader lines when nudged, interior pins as
dot+tooltip - driven only by pinInfo plus the measured element size, so
every part and board, present or future, works with no per-part data.
Dots are tinted by the pin's declared signal (i2c/spi/power/...).
- Clicking a pin keeps the old contract (onPinSelect -> handlePinClick), so
wires start with the same undo, colors and running-state gating.
- The two open gestures finally agree on coordinates: right-click always
passed viewport coords while touch passed canvas-local ones; the dialog is
now portaled to body in viewport space and the touch site converts.
- Keybind capture, the SD Card panel, the two-step delete, Escape and
click-outside all migrate unchanged. Mobile keeps the full-sheet layout.
Geometry covered by pin-inspector-layout.test.ts with coordinate tables
copied verbatim from the shipped parts (ESP32 two-column board, LED, an
interior-pin case).
The "SD Card" upload panel in the property dialog was keyed to one component
id, microsd-card, so a part that CARRIES a slot of its own (the Seeed Round
Display shield) had a working card electrically but no way to put files on
it - the user saw the slot mount and could never feed it.
New metadata flag sdSlot: the dialog shows the same panel for any part that
declares it, persisting uploads as properties.sdFiles exactly like the
standalone card. The part's simulation is responsible for reading them when
it builds its card image.
Three generic seams, none naming any board:
The pin tracer gains a socket hop: a component that declares boardSocket
with a board actually seated on it resolves a pad to the SAME-NAMED pin
of that board — seating IS the connection, like a XIAO pushed into a
shield header or a HAT on the 40-pin. Until now a seated shield's
buttons and LEDs were dead unless the user drew wires the real stack
does not have. Wires keep working exactly as before for every other
host (Uno, Mega, Pi, STM32, ...).
setMicrophoneSource now RETURNS whether a bridge actually took the
source, so a part can state that this host has no I2S instead of
pretending to stream into a void — capability reported, never invented.
createNeopixelDecoder is exported: boards are not the only things
carrying an addressable LED, and a part with one on board needs the
same decode rather than a drifting copy.
DS3231 (I2C 0x68):
- New velxio-ds3231 Web Component (4-pin module: GND/VCC/SDA/SCL) with
pinInfo per CLAUDE.md 6a; registered in elements-register + main.tsx.
- Catalog entry in scripts/component-overrides.json (_customComponents)
mirrored into components-metadata.json exactly as the generator emits it
(verified by cloning wokwi-elements and running generate:metadata: zero drift).
- The existing VirtualDS3231 device sim (DS1307-compatible time regs
0x00-0x06 + control 0x0E / status 0x0F / temperature 0x11-0x12 at 0.25 C
steps) is now user-reachable from the picker; the AVR/RP2040 attach path
gains live temperature updates via the SensorControlPanel (new ds3231
slider in sensorControlConfig).
GPS NEO-6M (UART talker, 9600 baud):
- New velxio-gps-neo6m Web Component (VCC/RX/TX/GND, pulsing PPS LED).
- New part sim GpsParts.ts: emits a valid GPGGA+GPRMC cycle per second
(correct XOR checksums, ddmm.mmmmm coords, UTC clock advancing 1 s/cycle)
with lat/lng/altitude/speed configurable via property dialog and live
via a new SensorControlPanel entry. Defaults: Madrid 40.4168 N 3.7038 W.
- Injection routes, chosen by classifying the wired board pin:
1) hardware UART RX -> new uniform sim.feedUart(uart, data) seam
(AVR USART0, RP2040 uart0/1 via rp2040js feedByte, ESP32 uart0/2 and
STM32 USART1/2 via their bridge shims); byte feed is paced in 8-byte
wall-clock chunks so 32-byte RX FIFOs never overflow;
2) any other digital pin on a cycle-accurate sim -> real 8N1 bit-banged
waveform via schedulePinChange, so SoftwareSerial decodes it;
3) feedUart rejection (e.g. Mega USART1-3, not modelled by avr8js)
degrades permanently to the bit-banged path.
- Interconnect already probed sim.feedUart; implementing it also upgrades
cross-board UART byte shortcuts for RP2040 uart1.
Tests: new gps-neo6m.test.ts (16 tests: NMEA builders, byte injection on
Uno RX0 / ESP32 RX2, Mega RX1 fallback, waveform decode back to $GPGGA,
live position updates, PPS pulse) + 3 new ds3231 AVR-path tests in
protocol-parts.test.ts. Full frontend suite: 2294 passed, 2 pre-existing
Galaksija Z80 timeouts that pass in isolation. npx tsc --noEmit clean.
Drag-to-front lifted boards but never parts: a component's z lives
inside .component-interactive-group, which is itself a stacking
context, so the 10+rank set on the inner wrapper was clamped to the
group's own level (1/2) while boards compete one level up as direct
children of .canvas-world. Once any board had been dragged it sat at
10+rank and no component could ever climb back over it — drag an LED
onto the Arduino and the board swallowed it, exactly as reported.
The rank now lands on the group itself (both branches: instruments and
ordinary parts), so parts and boards share ONE rank space and the last
thing dragged really is the thing on top. The inner wrapper keeps its
local 1/5 for selection order within the group.
An unseated board dropped over a component was painted underneath it
(boards z 0, components z 1) and became impossible to grab back; the
earlier blanket z bump for boards broke the opposite case, hiding LEDs
and transistors behind every board. Neither static order can win both.
The rule is now the user's own gesture: the FIRST movement of a drag
raises that item (board or part, symmetrically) to the top of a
monotonic dragged-stack, so a board dropped over an LED sits visibly on
top — and dragging the LED afterwards wins the stack right back.
Click-select alone never raises: selection must not reshuffle a scene
you arranged. Untouched items keep the static layering (components
above unseated boards, seated boards above their socket), and the rank
map is ephemeral — never saved with the project.
setMicrophoneSource on the simulator shim mirrors addI2CDevice: a part
that produces audio hands one 16-bit sample per call to whatever bridge
implements the method (the in-browser JS-engine bridges); everywhere
else the optional chain makes it a silent mic. No board-kind checks in
components. Plus the flat reSpeaker Lite glyph for gallery cards.
Every control in the unified strip is now 28px tall: tb-btn (was 30),
Libraries (was 26), the C++/MicroPython select (was ~22), the board
selector, Serial/Scope, canvas icon buttons, zoom and Add (all were 32).
The explorer toggle moves inside the Code/Both/Circuit segmented group
as its first segment, and every active-segment blue is the Libraries
blue (var(--color-action-primary)) instead of the hardcoded #0e639c.
The editor zone's overlap floor becomes min-width: max-content — the
hardcoded 280px sat below the real content width, letting the console
button paint over the board selector when the docked chat narrowed the
bar. Now the zone can never shrink under its buttons; flex-wrap moves
the canvas controls to a second line instead. Also closes the gap
between the logo and the File menu (header gap 16->6, menubar
margin-left 14->2).
The bottom-left block read wrong: a globe crowding the account button for
no gain — language already lives in the menubar's Language menu for
everyone, and (for signed-in users) inside the account menu. The footer
now holds only the account button.
Width-aware, as requested: when the explorer is dragged narrow (footer
under 150px, via container query) the username hides and the avatar
alone identifies the account; with the explorer collapsed, the floating
fallback box shows just the circle.
Adds the OSS half of the crash fix: LocaleSync listens for
'velxio-locale-switch' window events and performs the locale change as a
normal SPA navigation. The pro account menu is injected into its own
React root OUTSIDE the Router, so it cannot navigate itself — its
language rows dispatch this event instead.
The explorer toggle tooltips (Hide/Show file explorer) and the header's
Share-project title were raw English — now keyed and translated in all
nine locales, same common2.json home as the menubar strings.
Every menu we added lived on t() English fallbacks — the keys did not
exist in ANY locale file, so a Spanish or Chinese user got an English
menubar. 29 keys (editor.menu.* + the toolbar labels the File menu
reuses) now exist in en/es/pt-br/it/fr/zh-cn/de/ja/ru.
Two entries needed their own leaf keys: editor.toolbar.compile and .run
are nested OBJECTS (title/options/... sub-keys), so t() on them cannot
resolve to a string — the menu now uses editor.menu.compile / .run,
which also avoids double shortcuts ("Compile (Ctrl+B)" plus the menu's
own Ctrl+B column).
Verified mechanically: all 38 keys the menubar references resolve to
strings in all nine locales.
The menubar now matches the desktop app's native menu set: File, Edit,
View, Language, Help.
View collects what the user asked to reach from a menu: Compile (Ctrl+B),
Run, Stop, Reset up top; the panel toggles — File Explorer, Output
Console, Serial Monitor, Oscilloscope/Logic Analyzer — in the middle
(store-backed ones render a live check, like a real desktop menu); and
the canvas view actions (center, zoom) move here from Edit, which goes
back to being undo/redo only, as menus have always worked.
Language lists the nine locales with the current one checked, switching
through the same switchLocale path the header globe uses — so language is
reachable from the menubar regardless of the account state, on top of
living in the signed-in account menu.
Run/Stop/Compile/Reset and the console toggle register through the
editorCommands seam from EditorToolbar (their handlers close over its
state); the explorer toggle from EditorPage; serial and scope call their
stores directly, same as undo/redo.
The floating corner box sat ON TOP of the file tree — create enough files
and the last rows scrolled underneath it. The account + language block is
now the explorer panel's footer (VS Code style): the tree scrolls above
it, always. When the explorer is collapsed the block falls back to the
small fixed corner box, so account and language stay reachable.
The standalone language globe becomes a logged-out affordance: once the
pro auth slot renders its signed-in marker (data-auth-user), a :has()
rule hides the globe — signed-in users change language inside the
account menu, where the pro overlay now hosts it.
The language menu and the account menu were written for the top bar and
open downward; from the bottom-left corner box that means straight off
the bottom of the viewport. The language menu flips via a scoped CSS
rule; the account menu (pro) now anchors bottom-up whenever its trigger
sits in the lower half of the screen.
The header's backdrop-filter makes it a containing block for fixed
descendants, so the corner box's bottom:8px resolved against the 45px
header — measured at y=-4, sitting on the logo. Portaled to <body>, where
fixed means the viewport. The auth slot keeps working: pro's injector
finds it by attribute wherever it lives.
IDE-style: a small fixed box in the explorer's quiet bottom corner holds
the language switcher and the account button (pro keeps injecting it into
the same data-velxio-slot, just relocated). The autosave dot rides next
to the menus, and the header's Share button is gone — File > Share/Embed
already covers it.
The point is width: emptying the header's right side gives the toolbar
the last ~150px it needed, so the single-row layout now holds down to
~1400px WITH the chat docked — which is exactly the reported 1440x900
case. Tight-fallback thresholds drop accordingly (1400/1060/1040).
Desktop-editor variant only; marketing pages and mobile keep their
header untouched.
The toolbar's overflow button held four leftovers (Share/Embed, Sync to
GitHub, Upload firmware, Record simulation). They move into the File
menu, which is where a session-frequency action belongs, and the strip
loses one more button. The PRO pill travels with them — same style the
overflow used (now .emb-pro) — and the two File items that were always
premium (BOM, schematic image) finally show it too: users should know an
item is premium BEFORE clicking, not via a surprise upgrade prompt.
The pro actions keep firing the same window events the overflow items
fired (share/github-sync/record prompts), so the overlay's listeners are
untouched and OSS builds keep their silent no-op. Dead overflow CSS
removed with the button.
With the marketing nav gone from the editor header, its middle was ~700px
of dead space at 1280 while the toolbar occupied a whole second 38px bar.
The unified toolbar strip now fills that middle through a new AppHeader
editorToolbar slot: one 44px row where there used to be 44+38 — a full
row returned to the code and the canvas.
The strip keeps its own class names, so everything that keyed on them
keeps working untouched: the container queries, the docked-chat
padding-right, and the internal flex-wrap. When it truly cannot fit, the
strip wraps and the header grows (height: auto on the modifier class)
instead of clipping or overlapping; brand and the right-side controls
stay pinned to the first line. Inside the header the strip drops its own
background and border so it reads as one bar, not a box within a bar.
Mobile keeps the previous layout (no strip; the mobile tab bar remains).
The open-source build now ships exactly its product surface: /editor, the
examples gallery (/examples, /examples/:id) and the per-example editor
(/example/:id). Landing, about, pricing, docs, the 14 keyword-targeted
simulator landings and the v2/v2.5/v3 showcases move to the private
overlay, registered through the same registerProRoutes seam that already
carries login/admin/classroom.
Root behaves per build: an overlay that registers an index route claims
'/' (velxio.dev keeps its landing); otherwise '/' redirects to /editor.
The redirect waits for the overlay import to settle — same contract as
markProExamplesSettled — so a velxio.dev visitor is never bounced into
the editor because the landing was 300ms away from registering.
Prerender and sitemap follow the split: entry-server pulls the marketing
page map from '@pro/pages/marketing' behind a VITE_PRO_BUILD-gated dynamic
import (the proven main.tsx pattern, with an OSS stub for tsc), and
generate-sitemap lists only served routes in OSS builds (2 URLs) while
pro builds stay byte-identical — verified against a pre-migration
baseline: sitemap (37 URLs) and prerendered /, /about, /docs,
/arduino-simulator, /esp32-simulator, /v3 all identical modulo hashed
asset names. OSS prerender drops exactly the 32 marketing pages (348→316).
The OSS header slims down to match: Editor, Examples, GitHub, Discord —
the marketing links only render in pro builds, where their routes exist.
The editor Help menu links those pages absolutely (velxio.dev) in OSS,
exactly like the desktop app's Help menu.
Mismo contenido y orden que el Help del menu nativo de escritorio
(pro/desktop menu.rs): Documentation, Examples, Pricing | Velxio Home,
Blog, About Velxio | Discord Community, GitHub Repository. Los enlaces que
el header del editor dejo de mostrar como nav de marketing recuperan aqui
un sitio ordenado, y todo abre en pestana nueva para que el editor (y el
trabajo sin guardar) se quede donde esta — el mismo criterio que la app
nativa, que abre el navegador del sistema.
La queja: demasiados botones en una sola fila, y en pantallas pequenas se
solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a
Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y
el usuario cortados fuera del viewport.
Tres piezas:
1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya
estas dentro, y cuesta exactamente el ancho que le falta a la toolbar.
AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la
build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu
File/Edit junto al logo. El logo sigue llevando a inicio; idioma,
autoguardado, Share y usuario se quedan.
2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo
boton; lo que se usa unas veces por sesion va al menu. File: nuevo
workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM,
imagen del esquema. Edit: undo/redo (con su estado real del historial),
centrar vista, zoom. Las acciones viven en cierres de cuatro
componentes distintos, asi que hay un registro id->handler
(lib/editorCommands, mismo patron de seam que registerProExamples o
registerBoardBuiltins): cada dueno registra al montar y el menu invoca
por id; un item sin dueno montado se pinta deshabilitado, que ademas es
la verdad. El registro sobrevive al remontaje StrictMode (el cleanup
viejo no borra al sucesor) y tiene test de eso.
3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los
botones inline de Import/Export y sus gemelos responsivos del More
(~70px recuperados); el More queda solo con los extras pro (firmware,
GitHub, share, grabacion). El CSS responsivo de los botones retirados
se va con ellos.
Suite en verde (2300) y build del overlay verificado desde este arbol.
Tres cambios que van juntos porque atacan la misma queja: "anado un
elemento y a veces ni veo donde se anadio".
1. Donde cae. Ya se anclaba a la esquina visible, pero la cascada que evita
que se apilen iba indexada por components.length: seguia avanzando
aunque movieras o borraras piezas, asi que las caidas se alejaban cada
vez mas de donde estabas mirando. Ahora toma el primer hueco LIBRE desde
la esquina, bajando en diagonal; si apartas la ultima, la siguiente
recupera su sitio. Extraido a utils/dropSlot con 8 tests, incluido el
caso de "la apartaron" y el tope para no salirse de la vista.
2. Que se vea. El recien anadido queda seleccionado, y la seleccion pasa de
un borde discontinuo quieto a un caminito de hormigas. El movimiento es
lo que capta el ojo en un canvas lleno; un borde fijo se pierde. Va en
un pseudo-elemento por fuera del cuerpo, sin robar clicks ni tapar el
dibujo, y se queda quieto si el sistema pide menos animacion.
3. Clicks. El izquierdo SELECCIONA y ya esta; antes abria el panel de
propiedades, o sea que no podias ni senalar una pieza sin comerte un
popup que luego habia que cerrar. Propiedades y pines pasan al click
derecho, que es donde va lo deliberado. En tactil se mantiene tocar ->
panel, que ahi no hay boton derecho.
Seam nuevo registerBoardBuiltins/getBoardBuiltins: una placa que el arbol
OSS ya dibuja (la familia Pi) puede recibir perifericos del overlay sin
registrar un ProBoardDef, que es lo que secuestraba el arte de la placa y
dejaba los cables en la esquina. El overlay lo usa para llevar los frames
del guest (cv2.imshow) al panel cableado en el canvas.
Arregla ademas el guard de connect() del bridge: comparaba readyState con
WebSocket.OPEN leido del global, asi que cuando esas constantes no estaban
`undefined === undefined` era cierto con socket a null y connect() volvia
sin abrir nada — el bridge se quedaba muerto. Ahora comprueba que el socket
exista y usa el valor numerico. Recupera los 12 tests de
multi-board-integration que esto habia roto.
El test del UART Pi->Uno pasa a comprobar el contrato vigente: por el cable
va onUartTx (el UART del header), no la consola del guest; y se anade el
caso que fija que la charla de arranque NO se filtra al vecino.
Dos fallos que salieron probando pi-to-arduino-led-control y
pi5-pir-motion-alarm.
1) BoardOnCanvas mira getProBoard() ANTES del switch OSS para decidir si
dibuja un elemento del overlay. El overlay registraba un def minimo
para las seis Pi solo para llevar una linea de setup del guest, y eso
basto para cambiarles el render: en vez de la ilustracion
Raspberry_Pi_3_illustration.svg salia la caja esquematica, con otras
coordenadas de pines, y los cables quedaban colgando en la esquina.
Ahora hay un registro aparte, registerGuestSetup(kind, linea), que
lleva la cadena y nada mas; getGuestSetup() la resuelve dando
prioridad al def del overlay si existe.
2) Las partes de entrada (PIR, botones, sensores) avisan con
simulator.setPinState(pin, nivel). En una placa QEMU-Linux no hay
simulador de MCU -- el CPU es el guest -- asi que la llamada acababa
en la instancia AVR heredada y se perdia: pulsar el sensor no hacia
nada. traceDetailed devuelve ahora tambien la placa a la que llega el
pin, y si es de la familia Pi la parte recibe un simulador que empuja
el nivel al bridge (gpio_in para el guest, el valor pin<N> que leen
los shims del motor de navegador) y al PinManager de esa placa.
Without this we cannot tell whether the in-browser path is actually
displacing guest boots — the whole point of the dual engine is a number
(% of runs that never touched the backend), so the event has to say.
Generic seam only — no board, OS or runtime specifics in the OSS tree:
an overlay may register an engine that decides, per board, whether a run
needs the Linux guest or can happen in the browser. The decision is data
(engine + reason + where) so the UI can explain a 90 s boot instead of
just taking it, and BoardInstance carries engineMode / enginePinned so
the user's choice is predictable and the terminal panel knows whether an
interactive shell exists. Nothing registers in OSS: the QEMU path is
untouched.
Keeping Play enabled while the guest ran broke the convention every
other board follows (owner report). Run now disables as usual; RESET on
a booted guest re-uploads the edited files and re-runs the script
without the ~45 s reboot, matching Reset's restart-the-program meaning.
QEMU-Linux boards used three competing file surfaces (workspace group
with a meaningless sketch.ino/libraries.json, the VFS panel with its
Upload button, and the Pi workspace's own editor). Now they behave like
every other board:
- the editor file group defaults to script.py for ANY Pi-family kind
(kind-based check via isPiBoardKind, not the old raspberry-pi- string)
- the libraries.json manifest row is hidden for Pi boards
- EditorPage always renders Monaco; the RaspberryPiWorkspace swap is
gone
- the bottom serial panel renders the interactive xterm (PiTerminal,
now seeded with session history) for running Pi boards
- example vfsFiles load into the editor group (single source of truth);
the run path uploads the group into the guest home
- Run on a booted guest re-runs without the 45 s reboot (Ctrl-C +
re-upload + run); starting a Pi board pops the terminal open
- piSyncAndRunScript/piRerunScript exported from the store; auto-run
now applies to the whole family (guestHome/autoRun overridable)