Commit Graph

1387 Commits

Author SHA1 Message Date
David Montero Crespo c9fe6f199d feat(editor): folders - create/delete folder trees in the file explorer
File names may now carry '/' paths; the explorer renders each board group
as a collapsible folder tree (folders first, files in group order, CSS-var
indentation because .fe-file-item pins padding with !important). New-folder
buttons on the board header and per-folder hover actions (new file here,
new subfolder, delete with confirm when files are inside; a folder holding
every file of the group cannot be deleted - groups keep >= 1 file).

Model: names are the source of truth; only EMPTY folders are tracked
(folderGroups per group) so they survive until a file lands in them, then
materialise into the name prefix. Round-trips through .vlx (optional
folderGroups field, laxly validated), the auth stash draft, and
loadProjectState. Examples gain boards[].files so a single board can ship
a full multi-file tree (first entry = main sketch).

Backend: the arduino-cli sketch writer now creates subdirectories for
'/'-pathed names and REJECTS names that resolve outside the sketch dir
('..' traversal) - previously it crashed on the former and wrote outside
the tmp dir on the latter.
2026-08-05 06:48:38 +02:00
David Montero Crespo 5298c7e58c fix(examples): translate leftover Spanish to English in gallery code
- examples-robot-desktop: Serial messages, comments and identifiers
  (right/left servos, light threshold naming, START state) across the
  embedded .ino and headers
- examples-100-days: PULL_DOWN explainer comment in the MicroPython menu day
2026-08-05 06:41:30 +02:00
David Montero Crespo 36c414df8f feat(sd): source files stay off the card - only project DATA files auto-copy
On real hardware your code lives in flash, not on the microSD; a sketch
listing the card should see its data (csv, json, txt, wav...), not its
own source. buildProjectSdImage now skips .ino/.h/.hpp/.c/.cpp/.py from
the workspace auto-copy. Explicit uploads are exempt - an upload always
lands whatever its extension. Panel hint updated; tests cover both the
exclusion and the upload exemption (15 green).
2026-08-04 19:58:31 +02:00
David Montero Crespo 2dcb3eb65f fix(ui): SD download label, board inspector on right-click while running, solid-blue Buy
- The live card file action now says Download (it was Save).
- Right-click opens the board inspector in EVERY mode: the contextmenu
  handler moves from the drag overlay (hidden during simulation) to the
  board wrapper, so the SD panel is reachable while the sketch runs and
  the board's buttons stay interactive.
- Buy button in the inspector action bar goes solid brand blue #0071e3,
  matching Velxio's solid-button language.
2026-08-04 17:43:55 +02:00
David Montero Crespo baa5925f95 feat(sd): live Card contents in the SD panel - list and download what the sketch wrote
The SD panel only knew about uploads; everything the GUEST wrote to the
FAT card (photos, logs) stayed invisible inside the bridge. New
readFat16Image() parses the card image back (BPB-driven, real cluster
chains, VFAT long names, root dir - the inverse of buildFat16Image but
general enough for FatFs-written cards), and the panel gains a live
'Card contents' section reading the bridge's readSdImage() with a
per-file Save (download) button and a Refresh. Round-trip covered in
fat-image.test.ts (16 green).
2026-08-04 16:29:54 +02:00
David Montero Crespo 57e04b60e8 fix(webcam): per-board frame byte budget - Sense frames fit the S3 fb
Real webcams hit the 23000-byte bound, but the S3 esp32-camera build
allocates width*height/5 = 15360 bytes for a QVGA JPEG frame buffer and
stops copying one 1 KiB DMA half-buffer short of it, so oversized frames
lost their tail - including the EOI marker cam_hal scans for (the NO-EOI
storm on staging; the small canvas-fake frames masked it). The bounded
encoder now takes a budget (14000 for the Sense, default 23000 for the
QEMU ESP32-CAM path unchanged), the downscale fallback re-runs its own
quality ladder, and a frame that cannot fit is skipped instead of sent
truncated.
2026-08-04 16:18:10 +02:00
David Montero Crespo 5a71023ac9 fix(camera-toggle): toolbar-wide ant line gone; ants only while running
The toggle's attention border reused the .velxio-ants class name, which
is ALSO the canvas selection outline whose ::after is position:absolute
- on the non-positioned button it anchored to the toolbar and drew a
marching-ant line across its full width. Renamed the button variant to
.velxio-btn-ants. The ants also showed while the simulation was stopped;
now they only run when the sketch is running and the camera is off (the
only moment the button is actually asking for a click).
2026-08-04 16:12:50 +02:00
David Montero Crespo e96dae794a feat(mic): stream the computer's microphone into boards with an on-board mic
New canvas-header Mic toggle, shown for boards whose ProBoardDef declares
builtInMicrophone (their bridge implements setMicrophoneSource). Capture
runs at the AudioContext's native rate, is decimated to 16 kHz and ring-
buffered (2 s, drop-oldest on overflow, silence on underflow - the engine
paces pulls in simulated cycles, up to 4x real time during boot boost).
No auto-start: with the toggle off the bridge's 440 Hz test tone keeps
mic sketches alive, so taking the user's microphone stays an explicit
click. The source is re-applied on every run start to survive bridge
rebuilds.
2026-08-04 15:57:17 +02:00
David Montero Crespo bd1ee89b5e feat(camera): the webcam toggle serves the XIAO ESP32S3 Sense too
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.
2026-08-04 06:29:53 +02:00
David Montero Crespo cfa03aeaf9 feat(sd): boards with a built-in microSD slot take user uploads
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.
2026-08-04 05:55:21 +02:00
David Montero Crespo c6256cc49a feat(ui): one action bar for both tabs, and no sideways scroll in the datasheet
- 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.
2026-08-04 05:05:32 +02:00
David Montero Crespo 269503b386 fix(ui): one height for both inspector tabs, and solid brand buttons
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.
2026-08-04 04:35:16 +02:00
David Montero Crespo a1307f117d fix(ui): the inspector remounted its preview on every render, so it flickered
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.
2026-08-04 04:03:45 +02:00
David Montero Crespo d74df3323f fix(ui): say when a part has no datasheet instead of showing an empty tab
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.
2026-08-04 03:36:54 +02:00
David Montero Crespo 7dd9b90cf9 fix(ui): a column is a RUN of pads, not two opposite ends of the board
The Arduino Uno came out wrong: 18 pins on the top header, 13 on the bottom,
and SIX x values shared between the two. The column detector accepted any two
pads sharing an x, so those pairs were read as columns and most of the board
was thrown into the side gutters with leader lines fanning across the art.

A column now needs three pads AND small gaps between them (no more than 40%
of the body), which is what a real header looks like; rows get the same guard
on x. The Uno lands as two headers, top and bottom, with no side gutters at
all - covered by a test using its real 31-pin table.
2026-08-04 03:01:21 +02:00
David Montero Crespo 4f68109421 feat(ui): pins always on in the inspector, above the chat, and traceable
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.
2026-08-04 02:43:53 +02:00
David Montero Crespo 34faca355e feat(ui): boards open the Part Inspector too, replacing their own menu
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.
2026-08-04 01:11:55 +02:00
David Montero Crespo 4be8ddd6c3 fix(ui): a pin stack taller than the part was clipped by the preview
The ReSpeaker's left side carries 13 labels: at the minimum spacing that is
208px against a 190px body, so the column ran past both ends and the first
and last labels were cut off by the preview's clip. The gutters now grow to
whatever the labels actually occupy - the box follows the content.
2026-08-03 23:20:49 +02:00
David Montero Crespo cbc74d8409 fix(ui): the inspector dropped labels for inset pin columns, and flickered
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.
2026-08-03 23:04:37 +02:00
David Montero Crespo b02aab9a47 fix(ui): crowded pin headers escaped the inspector - draw those labels vertically
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.
2026-08-03 22:04:43 +02:00
David Montero Crespo d4a42a4e75 feat(ui): the Part Inspector - right-click dialog merged with the picker card
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).
2026-08-03 21:44:46 +02:00
David Montero Crespo ca799136f6 feat(sd): parts with their own TF slot get the SD upload panel
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.
2026-08-03 18:34:46 +02:00
David Montero Crespo d4ec4fe04d fix(ntc): the temperature sensor read wrong on 3.3V boards, and three examples decoded backwards
Two independent defects, both visible as "the NTC reports the wrong
temperature" (issue #233).

1. The part built its divider against a hard-coded 5 V rail, while the SPICE
   path used the board's real rail. They only agreed on AVR. On a 3.3 V board
   the injected node sat above the ADC's full scale: the slider at 25 C read
   ~1.4 C, and every slider value below ~11 C saturated to the same number, so
   -40, 0 and 10 C were indistinguishable. The rail now comes from the board
   (analogRailVolts), matching setAdcVoltage's own AVR/RP2040/ESP32 split.

2. The divider convention was corrected in 608c2538 (VCC -> R_pull -> OUT ->
   R_NTC -> GND) but data/examples.ts was not updated with it, so three of the
   four bundled NTC sketches still decoded the OTHER topology. Their readings
   came out mirrored about 25 C: the uno-ntc example printed -3.42 C with the
   slider at 60 C - the exact symptom dd22bcfe had recorded as the bug. Fixed
   in uno-ntc, pico-ntc and attiny85-ntc-sensor; examples-circuits.ts was
   already right.

The sibling analog sensors (gas, flame, heart-beat, sound) map their sliders
0..1023 onto the same hard-coded 5 V and will saturate the same way on 3.3 V
boards. Left alone here: that is not what #233 reports, and each one needs its
examples re-checked before changing.
2026-08-03 07:33:20 +02:00
David Montero Crespo 0d9a26d951 fix(espidf): a header-only library broke every ESP32 build
Installing ArduinoJson made the project stop compiling, with an error that
never mentioned ArduinoJson:

    CMake Error at user_libs/user_libs_all/CMakeLists.txt:15
      (target_compile_definitions):
      target_compile_definitions may only set INTERFACE properties on
      INTERFACE targets
    -- Configuring incomplete, errors occurred!

ArduinoJson ships headers only. With no sources, idf_component_register()
builds the merged user-libs component as an INTERFACE library, and PRIVATE
scope on an INTERFACE target is a hard CMake error - so the configure step
died before a single file was compiled. The user saw only a failing build
and reported it as "ArduinoJson can't be found even after it has been
installed" (#124).

Those definitions map alloca/memcpy_P/memcmp_P for library C sources that
use them undeclared; with no sources there is nothing to define, so they are
emitted only when the component actually compiles something. A library that
carries its own .cpp is unchanged - which is why LiquidCrystal_I2C (#257)
worked and hid this.
2026-08-03 07:26:01 +02:00
David Montero Crespo 6877099e78 fix(micropython): the \r poke must not fire once the prompt is already up
Stage 1 arms a \r at banner+800ms to flush QEMU's held ">>> " prompt, and
stage 2 arms Ctrl-A at prompt+200ms. Nothing kept the first from firing
after the second: if the prompt ever arrives on its own within 600ms, the
\r lands inside the pasted source, and MicroPython reads a lone \r as a
newline - it cuts a source line in half with no diagnostic of its own.

Here that is still latent; the in-browser JS engines, which deliver serial
per byte, hit it on every single run.
2026-08-03 05:48:40 +02:00
David Montero Crespo fd18e8db30 fix(libraries): Escape closes the manager, the way out everyone tries 2026-08-03 03:01:54 +02:00
David Montero Crespo 349478bb0f ui(picker): the gesture card shows the camera it is named for 2026-08-02 04:49:32 +02:00
David Montero Crespo 32e5e9c50b ui(picker): the gesture sensor's card shows what it really is 2026-08-01 08:26:16 +02:00
David Montero Crespo 13a8403d6d fix(sockets): a board a hair off the seat is still plugged in 2026-08-01 06:14:43 +02:00
David Montero Crespo f2e6464c36 fix(canvas): the dragged board's own run state decides if the stack holds 2026-08-01 05:55:35 +02:00
David Montero Crespo faed282135 fix(canvas): latch the socket at drag start so the stack cannot come apart 2026-08-01 05:32:46 +02:00
David Montero Crespo 540c3081db fix(canvas): a seated board stays above the socket it is plugged into 2026-08-01 04:49:02 +02:00
David Montero Crespo cd2dc2f54b Merge remote-tracking branch 'origin/master' into v3.2-worktree 2026-08-01 04:23:55 +02:00
David Montero Crespo 84080a0ff8 fix(sim): a seated stack moves as one piece; unplugging is edit-only 2026-08-01 04:16:21 +02:00
David Montero Crespo 5f8a3e171c feat(sim): a speaker-sink gate on the shim — audio out follows the part 2026-08-01 00:22:57 +02:00
David Montero Crespo bad2a03c4f fix(sim): import the seat check the socket hop relies on 2026-07-31 23:11:50 +02:00
David Montero Crespo 8bb9d4e85b ui(picker): reSpeaker ONLINE card matches the functional component (sensors, vertical art) 2026-07-31 22:37:07 +02:00
David Montero Crespo ab7949502d feat(sim): seated sockets are electrical, and capability is probed
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.
2026-07-31 22:32:47 +02:00
David Montero aa273a56e9 test(espidf): assert the two-tier architecture guard (issue #257)
Direct sketch includes of arch-excluded libs merge; transitive pulls
stay skipped; 'all' counts as wildcard. Replaces the old blanket-skip
assertion that correctly blocked the deploy gate.
2026-07-31 22:14:39 +02:00
David Montero d4b0b3d822 espidf: fix architecture guard skipping usable libraries (issue #257)
Two-tier rule replacing the blanket skip:
- 'all' now counts as wildcard ('*'): LiquidCrystal_I2C 2.0.0 declares
  architectures=all and was skipped on every ESP32 build, breaking the
  most popular I2C LCD library with a bare 'No such file or directory'.
- Headers the SKETCH includes directly merge even when the library
  declares a foreign-only architecture (many avr-declared libs are pure
  Wire/SPI code that compiles fine; a truthful compile error beats a
  missing-header one). Transitive pulls keep the HARD guard, which is
  the path that dragged SAMD-only Adafruit_ZeroDMA into ESP32 builds.
2026-07-31 22:04:49 +02:00
David Montero 127b3f54f7 docker: fetch the ESP32-S3 ROM (esp32s3_rev0_rom.bin) alongside the other ROMs
Self-hosted builds got 'qemu: -bios argument not set, and ROM code
binary not found' on any ESP32-S3 run because the ROM fetch loop only
knew the classic/C3 ROMs (issue #259, second half — the first half was
the drifted arm64 libqemu build, now republished for every platform).
2026-07-31 21:51:22 +02:00
David Montero eb3b04efe3 spice: seed INPUT_PULLUP pins to their resting level on pull enable
Deterministic repro (emulation-gaps F4): after pinMode(2, INPUT_PULLUP)
with a correctly-wired button-to-GND, all 8 setup()-time digitalReads
and the first two loop() passes returned 0 — the input register only
got its level from the first SPICE solve (~400 ms). That window is what
fired phantom emergency-stop latches and auto-started state machines in
audited sessions (and an unwired INPUT_PULLUP pin on the qemu bridges
never got driven at all: no net in pinNetMap, connector skips it, guest
read 0 forever).

Fix: PinManager.onPullChange fires on pull transitions; AVRSimulator
seeds its port input to the resting level instantly (real silicon does
this in ns), and makePinPullHandler does the same for the qemu-bridge
boards via the shim's setPinState. Sourced nets are still overridden by
the very next solve, so the 'mis-wired button still works' regression
guarded by the June work cannot return.
2026-07-31 20:51:42 +02:00
David Montero fc048ff941 Merge branch 'feat/circuit-safety-p1' 2026-07-31 20:43:10 +02:00
David Montero 2af5a502a6 Merge branch 'feat/catalog-ds3231-gps' 2026-07-31 20:43:10 +02:00
David Montero c97a43fd7c verify: catch unpowered nets, open source loops, and relay coil voltage mismatch
Audited circuits (2026-07) passed pre-flight with zero findings while being
electrically dead or mis-supplied: a battery with one pole wired, a MOSFET
switching a rail nothing powers, a 12V-coil relay fed from 5V, and VCC pins
tied together with no source. Three new graph-based rules (no solve needed,
so they fire even when ngspice cannot converge; all non-blocking warnings):

- unpowered-net: power-input pins (VCC/VIN/V+/COIL+/rated supply pins) and
  whole conduction regions that no battery, power supply, or board pin
  reaches. Ground and source-negative nets do not bridge power; MOSFET/BJT
  conduct only through their channel; a relay coil is isolated from its
  contacts; custom chips and boards are self-powered.
- no-return-path: a source with a single pole wired, or whose + side never
  reconnects to its - side (return analysis joins all pins, so voltage-driven
  gates/bases are not false positives).
- voltage-mismatch: relay coil_voltage vs the nominal supply on the coil
  nets (region fallback for coils fed through a switch/transistor): below
  the 60% pull-in threshold it never actuates, above 1.5x it burns out.

verifyFromStore now also verifies circuits whose only source is a
power-supply component. Gallery sweep extended to fail on the new codes:
zero findings across all 228 wired examples.
2026-07-31 20:38:23 +02:00
David Montero 1ad1fd4137 feat(components): DS3231 RTC module + GPS NEO-6M NMEA talker
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.
2026-07-31 20:38:06 +02:00
David Montero 730ab02bca compile: thread initiated_by through CompileRequest and metrics extras
Lets metrics overlays distinguish agent-triggered compiles from manual
ones (the AI assistant's compiles were previously invisible in project
counters). Loose optional field — omitted means manual.
2026-07-31 20:22:29 +02:00
David Montero 986b532922 spice: stamp STM32 output pins as V sources + STM32 board pin groups (3.3V)
The STM32 branch of collectPinStates deliberately skipped outputs from
the netlist — a workaround from when pinNameToArduinoPin couldn't map
'PC13' at all. With the mapping unified, emit digital outputs like every
other board so wired LEDs light from the solve (emulation-gaps F3).
Adds the missing BOARD_PIN_GROUPS entries for the STM32 family (3.3V
logic, suffixed GND/3V3 silks) so sources stamp at the right voltage.
2026-07-31 19:36:21 +02:00
David Montero a18964b401 spice: unify silkscreen->GPIO mapping on boardPinToNumber (fixes STM32 outputs, nano-esp32 buttons, esp32-family SPICE analog)
Three parallel half-implementations of the pin-name mapping each broke a
different board family (2026-07 emulation-gaps audit):

- collectPinStates.pinNameToArduinoPin returned -1 for STM32 'PC13' (no
  V source stamped: LED dark with correct firmware) and for nano-esp32
  'D2'/'A0' (no pull resistor stamped for INPUT_PULLUP buttons).
- connectDigitalInputsToMcu.gpioFromPinName only knew digits/GPIO/GP, so
  labeled pins were never driven from the solve (buttons stuck LOW).
- connectAnalogInputsToMcu's ADC_PIN_MAP used 'GPIO32'-style lookup keys
  that never matched the real bare-number wire pin names, so the
  SPICE->ADC path skipped every esp32-family pin; its per-kind GPIO
  converters also mapped nano-esp32 A-pins with the AVR convention.

All three now delegate to utils/boardPinMapping.boardPinToNumber (with
the previous behavior as fallback for unknown names). Also adds the
ESP32-C3 branch to the store's adcChannelForPin (GPIO0-5 -> CH0-5,
verified against the qemu SARADC channel layout).
2026-07-31 19:20:59 +02:00
David Montero cd5cbb14f3 esp32: map S3-family GPIO pins to ADC channels in setAdcVoltage/setAdcWaveform
The GPIO->channel map only covered the classic ESP32 (GPIO 36-39/32-35),
so on esp32-s3 / xiao-esp32-s3 / arduino-nano-esp32 every SPICE-driven
analog value was dropped and analogRead saw 0 (when it didn't hang —
fixed machine-side in libqemu 1.2.5's SENS stub). S3 family: ADC1 =
GPIO 1-10 -> CH0-9, ADC2 = GPIO 11-20 -> channel index 10-19, matching
the machine's channel layout.
2026-07-31 18:38:04 +02:00