Commit Graph

580 Commits

Author SHA1 Message Date
David Montero Crespo be0cd514aa fix(esp32): worker subprocess fallback for esp32_flash_image import
CI's e2e test_hcsr04_simulation.mjs caught the regression introduced by
a3f21a2 (the issue #101 fix). The worker crashes on boot with

    Firmware decode error: No module named 'app'

esp32_worker.py runs as a subprocess via subprocess.Popen([sys.executable,
WORKER_PATH, ...]). When Python launches a script directly, sys.path[0]
is the SCRIPT's directory (backend/app/services/), not the backend root.
So `from app.services.esp32_flash_image import pad_to_flash_size` fails
because there is no `app/` under `backend/app/services/`.

esp32_lib_bridge.py wasn't affected because it runs in-process inside
uvicorn, where backend/ is implicitly on sys.path.

Fix: same try/except + importlib fallback the worker already uses for
esp32_i2c_slaves at the top of the file. First try the package import
(works when imported by the bridge's tests or anything else with the
backend root on sys.path), fall back to direct file loading otherwise.

Verified the fallback works in isolation by simulating the subprocess
context (sys.path containing only backend/app/services/) — the package
import fails as expected and the file-load fallback returns a properly
padded 4 MB buffer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:22:53 -03:00
David Montero Crespo c939005bf7 test(esp32): integration tests for QEMU examples
Closes the loop on the four ESP32 fixes that landed earlier in this
series. Each previously-broken or noisy example now has a regression
test that compiles the sketch through the production ESP-IDF compiler
and runs it in QEMU via esp32_worker.py — the same code path the
WebSocket /ws/{client_id} endpoint drives in production. Testing the
worker directly skips the WS transport but exercises the same compile
→ flash → boot → serial cascade.

Coverage:
- TestEsp32SerialCleanliness — DHT22, Servo+Pot, Joystick, Dual ADC
  Asserts the user's Serial.print substring shows up AND no
  `I (xxx) gpio:|wifi:|phy:` info-level ESP-IDF logs leak through.
  This validates the sdkconfig CONFIG_LOG_DEFAULT_LEVEL_WARN change
  from commit b373c97.

- TestEsp32CompileSuccess — BLE Advertise, LEDC RGB
  BLE Advertise validates the sdkconfig switch to Bluedroid (was
  NimBLE-only, which broke arduino-esp32's BLEDevice.h).
  LEDC RGB validates velxio_compat.h's ledcAttach() shim from
  commit f6f6f43; the sketch uses the arduino-esp32 3.x one-shot API
  on a 2.0.17 toolchain.

- TestEsp32WiFiSketches — WiFi Connect, WiFi WebServer
  Regression coverage to make sure the sdkconfig changes didn't break
  WiFi association. Connect must reach an "IP Address:" line; Server
  must report "Server started".

- frontend/src/__tests__/component-metadata-bmp280.test.ts
  Sanity check that the BMP280 entry from commit 1f3f2e0 survives
  metadata regeneration.

All ESP-IDF/QEMU tests use unittest.skipUnless on
_toolchain_available() so they no-op cleanly on dev boxes without
libqemu-xtensa, and only do real work in the Docker CI image.

Sketches are inlined verbatim from the public Velxio examples.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 18:39:22 -03:00
David Montero Crespo 1f3f2e07bb feat(components): register BMP280 in component metadata
A beta tester reported "BMP280 - module graphic missing" — picking the
example loaded a working sketch but the canvas component fell back to
the MPU6050 placeholder.

Bmp280Element.ts already exists and registers velxio-bmp280 with the
right pinInfo, but it was never injected into components-metadata.json,
so the component picker and CircuitPreview didn't know about it. Add
an entry in scripts/component-overrides.json under _customComponents
(per CLAUDE.md §6b — direct edits to the generated JSON would be
clobbered by the next metadata regen) and regen.

The thumbnail mirrors the GY-BMP280 breakout look from the Web
Component itself: green PCB, black die label, four gold pin pads.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:26:12 -03:00
David Montero Crespo bba935f93d fix(serial-monitor): strip ANSI escape sequences from board output
Beta testers saw raw `[0;32m` and `[0m` text mixed into the serial
monitor for several ESP32 examples. Those are ANSI SGR escapes the
ESP-IDF logger emits to color INFO/WARN lines on a real terminal. Our
<pre> renders them literally because there was no ANSI handling in
the path.

Strip the SGR sequences (`\x1b\[[0-9;]*m`) before the IP-linkifier so
the user only sees plain text. Combined with the sdkconfig change that
drops the default log level to WARN, the ESP32 output is now as clean
as the AVR output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 16:32:59 -03:00
David Montero Crespo f6f6f43cc2 feat(esp32): velxio_compat.h shim for arduino-esp32 3.x APIs on 2.0.17
A user reported the LEDC PWM RGB example failing to compile. The sketch
calls ledcAttach(pin, freq, resolution) — the one-shot API added in
arduino-esp32 3.x. Our toolchain image pins arduino-esp32 to 2.0.17
(matched to ESP-IDF 4.4.7 + the lcgamboa QEMU ROM), where the API is
the older two-step ledcSetup + ledcAttachPin pair. Sketches written
against 3.x docs hit "ledcAttach was not declared in this scope".

Bumping arduino-esp32 to 3.x means moving to ESP-IDF 5.x, which may
break our QEMU fork. Cheaper fix: ship a compat shim header in the
ESP-IDF project template that defines ledcAttach + ledcAttachChannel
in terms of the 2.x API, gated on `!defined(ledcAttach)` so it
disappears the day we bump.

espidf_compiler.py now injects #include "velxio_compat.h" right after
Arduino.h whether the user explicitly included Arduino.h or we
prepended it ourselves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 16:32:12 -03:00
David Montero Crespo b373c97377 fix(esp32): suppress ESP-IDF info logs and switch BLE stack to Bluedroid
Two reports rolled into one sdkconfig change:

1. Beta testers reported "weird serial output" across DHT22, Servo+Pot,
   Joystick, WiFi×2, and DualADC examples. The user's Serial.print lines
   came interleaved with internal ESP-IDF chatter:

       I (53306) gpio: GPIO[4]| InputEn: 1| OutputEn: 0| ...
       I (10626) phy_init: phy_version 4791,2c4672b,...

   sdkconfig.defaults didn't pin a default log level so it inherited
   CONFIG_LOG_DEFAULT_LEVEL_INFO. Set WARN (level 2) so only warnings
   and errors leak into the user's serial output. Sketches can still
   esp_log_level_set() per tag at runtime if they want verbose.

2. BLE Advertise example failed to compile. sdkconfig had NimBLE
   enabled, but arduino-esp32 2.0.17's BLEDevice.h targets Bluedroid.
   Switch the stack: enable BT_BLUEDROID + BTDM_CTRL_MODE_BR_EDR_BLE +
   BT_BLE so the standard arduino-esp32 BLE library compiles. Sketches
   that explicitly include NimBLEDevice.h will not compile under this
   config — that's a smaller minority than the BLEDevice.h users.

The runtime side of BLE still depends on stubs in the qemu-lcgamboa
fork; this only fixes the compile path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 16:29:12 -03:00
David Montero Crespo a3f21a218e fix(esp32): trim flash image before serializing, pad on QEMU attach
Issue #101 reproducer: an ESP32 sketch that pulls in Adafruit_SSD1306 +
Adafruit_GFX produced a "No response from server. Is the backend
running on port 8001?" error in the browser. The compile actually
succeeded backend-side, but the JSON response carrying the firmware
was ~5.5 MB of base64 — the ESP-IDF compiler builds a full 4 MB merged
flash image (mostly 0xFF padding), encodes it whole, and ships it. In
prod that response goes through nginx + Cloudflare, which buffer-fail
or RST the connection on payloads that big — axios then lands in the
"no response" branch with no HTTP status to surface.

Fix: trim the trailing 0xFF padding before serializing, re-pad to a
valid QEMU flash size (2/4/8/16 MB) just before mtd attach. Lossless:
bytes after `last_used` in the merge are 0xFF by construction, so
trim → pad reproduces the original image byte-for-byte.

Numbers from the reproducer (Adafruit_SSD1306 + Adafruit_GFX,
esp32:esp32:esp32 board):
  before: ~5.5 MB JSON response
  after:  539 KB JSON response (10× smaller)

backend/app/services/espidf_compiler.py
  _merge_flash_image now tracks `last_used` across the three placed
  sections (bootloader / partitions / app) and writes only
  flash[:last_used] to merged_flash.bin.

backend/app/services/esp32_flash_image.py (new)
  Shared `pad_to_flash_size(bytes) -> bytes` helper. Rounds up to the
  next valid QEMU flash size with a 4 MB minimum, matches the
  frontend's existing padToFlashSize logic in Esp32MicroPythonLoader.
  Raises ValueError on >16 MB inputs (would indicate a broken upstream
  merge, not anything user-recoverable).

backend/app/services/esp32_lib_bridge.py
backend/app/services/esp32_worker.py
  Both QEMU consumer paths (in-process and subprocess) call
  pad_to_flash_size right after base64.b64decode, before writing the
  tmp .bin that QEMU attaches with `-drive if=mtd,format=raw`.

Verified: smoke test confirms trim → pad → original is byte-exact.
Edge cases covered: small payloads pad up to the 4 MB minimum;
firmwares >16 MB are rejected loudly.

Closes #101

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:36:34 -03:00
David Montero Crespo 2bcc8a62ee feat(canvas): inline two-step delete confirm in ComponentPropertyDialog
Replaces the window.confirm("Delete X?") modal with a footer that flips
into a "Delete X?" prompt + Cancel / Delete pair when the user arms the
delete. Less jarring on mobile (no native dialog), keeps the user's
flow inside the property panel.
2026-05-08 12:25:13 -03:00
David Montero Crespo 0fcd6221b5 fix(canvas): use lucide-react Undo2/Redo2 for the toolbar icons
The two hand-rolled curved-arrow SVGs I drew in bd5fd18 looked off — the
arrowheads were misaligned and the curve clipped at the bottom of the
viewBox. Swapped both for the canonical lucide-react icons (Undo2 /
Redo2), which match the visual weight + alignment of the rest of the
toolbar.

lucide-react was already in the OSS deps (added when other parts of the
app started using it). No new dependency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:19:50 -03:00
David Montero Crespo bd5fd18756 feat(canvas): keyboard shortcuts + toolbar undo/redo buttons
UI-facing half of the undo/redo feature. Combined with the previous two
commits, Ctrl+Z (or the toolbar button) now reverses every canvas
mutation: add/remove component, move, rotate, set property, add/remove
wire.

EditorPage.tsx:
- New window-keydown effect for Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z (and the
  Cmd equivalents). Uses the same input/textarea/contenteditable guard
  as the existing Ctrl+S handler — Monaco's per-file undo and the AI
  chat composer keep their own behaviour.

SimulatorCanvas.tsx:
- Two icon buttons (undo + redo) added to the canvas header, between
  the board selector and the Serial Monitor toggle. Tooltip surfaces
  the next command's description ("Undo: Add LED (Ctrl+Z)") so the
  user knows exactly what's about to revert. Buttons disable when the
  stack is empty in that direction.
- New `canvas-icon-btn` CSS class for square 32×32 icon-only buttons
  (matches the visual weight of the existing Serial button without
  the label).
- Subscribes to history / historyIndex via store selectors so the
  buttons re-render reactively as commands are pushed/undone.

No new tests — the store-level coverage from 99ed22b already exercises
undo/redo round trips. UI affordances are wired pass-through to those
store APIs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:11:53 -03:00
David Montero Crespo df8e666aa3 feat(canvas): SimulatorCanvas routes mutations through record* actions
Wires every user-initiated canvas mutation in SimulatorCanvas to the
recorded variants added in 99ed22b, so Ctrl+Z (next commit) can roll
each one back as a single step.

Routed through record*:
- handleSelectComponent (picker → add) → recordAddComponent
- Delete keyboard handler (selectedComponentId branch) → recordRemoveComponent
- handleRotateComponent → recordRotate (still mutates live via
  updateComponent so the rotation visually applies; record stores the
  prev/next angles for undo)
- Drag → recordMove on mouseup. Captures component.x/y at mousedown in
  a new dragStartPosRef and only records on actual drag-end (skips the
  click branch that just opens the property dialog).
- Pin-click "finish wire" path → calls finishWireCreation (which
  atomically appends the wire) then pushes a CanvasCommand for that
  wire with applyNow:false (state is already at post-add).
- Selected-wire delete (keyboard + SelectionActionBar + PinPickerDialog)
  → recordRemoveWire
- Selection action bar component delete → recordRemoveComponent
- Pin picker dialog component delete → recordRemoveComponent
- ComponentPropertyDialog onPropertyChange → updateComponent applies
  live, then recordSetProperty captures prev/next so Ctrl+Z reverts the
  value without re-running the raw mutation.

Cleaned up unused destructures of addComponent / removeComponent /
removeWire from the original useSimulatorStore() call — every call site
now uses the record* equivalents.

No new keyboard shortcuts or toolbar buttons in this commit; that's
landing next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:09:20 -03:00
David Montero Crespo 99ed22ba5d feat(canvas): undo/redo command pattern + history slice
Adds the foundation for canvas undo/redo. UI wiring, keyboard shortcuts,
toolbar buttons and agent-tools refactor land in follow-up commits.

useSimulatorStore.ts:
- New CanvasCommand type — { description, execute, undo }.
- HISTORY_MAX = 50 (oldest entries dropped on overflow).
- New state: history[] + historyIndex (-1 = empty).
- New APIs: pushCommand(cmd, {applyNow?}), undo, redo, canUndo, canRedo,
  clearHistory.
- New "recorded" actions that wrap raw mutators with a CanvasCommand:
  recordAddComponent, recordRemoveComponent, recordMove, recordRotate,
  recordSetProperty, recordAddWire, recordRemoveWire, recordUpdateWire.
- recordRemoveComponent captures both the component AND any wires that
  cascade with it, so undo restores both atomically.
- recordMove also re-runs updateWirePositions on undo/redo so wire
  endpoints follow the component back/forward.
- setComponents and setWires (project-load / clear paths) now call
  clearHistory inline — leaving stale commands pointing at IDs that no
  longer exist would crash on undo.

Why custom Command pattern over zundo / travels:
- The store has 30+ ephemeral fields (simulator instances, serialOutput
  growing byte-by-byte, hexEpoch counter, wireInProgress that ticks 60×/s
  on drag). Snapshot/diff middleware would either burn memory tracking
  them or need a fragile partialize allow-list.
- Per-op descriptions ("Add LED", "Move resistor") for tooltips come for
  free with this approach; zundo/travels would need to infer them.

Tests: 15/15 in src/__tests__/undo-redo.test.ts — covers cap-at-50,
redo-truncation, cascade undo of remove-component, move/rotate/property
round trips, bulk-setter clearing.

Raw mutators (addComponent / removeComponent / updateComponent / addWire /
removeWire / updateWire) are unchanged. UI handlers can keep using them
during live drags for preview frames without spamming history; the
record* actions are what drag-end, click-finish and agent tools should
call going forward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:03:17 -03:00
David Montero Crespo 5114c40af5 chore(about): refresh community stats
- GitHub Stars: 600+ → 2,000+ (project crossed 2K)
- Supported Boards: 19 → 17 (matches the actual BOARD_KIND_LABELS
  count: 3 AVR Arduino + ATtiny85 + 2 RP2040 + Pi 3 + 4 ESP32 Xtensa-LX6
  + 3 ESP32-S3 + 3 ESP32-C3 = 17). The previous 19 was inflated.
- CPU Architectures: 5 → 6 (AVR, RP2040 ARM Cortex-M0+, Cortex-A53 64-bit,
  Xtensa LX6, Xtensa LX7, RISC-V RV32IMC).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 11:28:47 -03:00
David Montero Crespo ebde9cc8da feat(about): real avatar + Recent releases section linking to /v2 and /v2-5
- Replace the "DMC" initials placeholder with the GitHub avatar
  (https://avatars.githubusercontent.com/u/47928504?v=4). The CSS for
  .about-creator-avatar already had the rounded frame; just swapped to
  object-fit: cover so the <img> fills the circle correctly, plus a
  subtle ring + drop shadow.
- Add a "Recent releases" section between the Creator block and the
  personal-story quote, with two cards:
    - Velxio 2.5 (Latest) → /v2-5  (ngspice-WASM analog co-simulation)
    - Velxio 2.0          → /v2
  Each card has a tagline + 2-3 line blurb. The 2.5 card gets a blue
  border + "Latest" tag so it reads as the current launch. About now
  surfaces both release pages, which previously were only linked from
  the Circuit/Electronics/SPICE simulator pages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 11:20:38 -03:00
David Montero Crespo c20a7498fc feat: add touch-friendly components for improved mobile usability
- Implemented PinPickerDialog for selecting pins on touch devices.
- Added SelectionActionBar for managing selected items with touch actions.
- Created WireModeBanner to provide feedback during wire creation.
- Introduced useTouchDevice utility for detecting coarse pointer input.
- Refactored WireLayer to utilize useIsCoarsePointer for touch detection.
2026-05-08 11:12:26 -03:00
David Montero Crespo b83b6f28d6 fix(vite): only preserveSymlinks during dev, not build
Previous commit unconditionally enabled preserveSymlinks when
VITE_PRO_BUILD was set. That works for `vite dev` (where the overlay
is wired in via a Windows junction and the resolver needs to keep the
junction path so relative imports back into the OSS sibling dirs
resolve), but it BREAKS `vite build` in Docker — there are no symlinks
to preserve, and Rollup with preserveSymlinks=true fails to resolve
relative imports from the copied overlay tree:

    Could not resolve "../../../services/componentRegistry"
    from "src/pro/agent/tools/canvas.ts"

Gate the flag on `command === 'serve'` so it only kicks in during dev.
Production builds always run with preserveSymlinks=false (the default).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 01:18:40 -03:00
David Montero Crespo 78f990b04f
Merge pull request #146 from davidmonterocrespo24/examples-real-thumbnails
feat(examples): add 53 missing thumbnails — 100-days IoT + Pico W WiFi
2026-05-08 01:11:38 -03:00
davidmonterocrespo24 fc985bb385 feat(examples): add 53 missing thumbnails — 100-days IoT + Pico W WiFi
Discovery regex was matching only single-quoted ID literals, so the
auto-generated examples-100-days.ts file (which uses double-quoted
strings, by convention of its Python emitter) was completely missed.
Same for picow-wifi: the script wasn't reading examples-picow-wifi.ts
at all.

Two-line fix in scripts/capture-example-thumbs.mjs: the regex now
accepts both `'…'` and `"…"`, and the data-file list includes
examples-picow-wifi.ts.

Captured slugs:
- 49 × `100d-*` (100 Days of IoT — MicroPython on ESP32 / Pico)
- 4  × `picow-*` (Pico W WiFi — async LED, relay web server,
  servo web, websocket LED)

Coverage: 219/226 examples (97%). The 7 still falling back to
CircuitPreview are component-ID literals (`epaper-1in54-bw`,
`epaper-2in13-bw`, etc.) that the regex over-matches — they 404
on /examples/<slug> because they aren't real example IDs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 05:57:35 +02:00
David Montero Crespo 70cc8c2e11 feat(editor): view-mode toggle, agent-chat slot, toolbar polish
Changes that ship to OSS — all benign for self-hosters, but most are
extension points the velxio-prod overlay (and any private fork) needs to
plug an in-editor AI chat into the page.

Editor:
- 3-way view-mode toggle (code / both / circuit) in the unified toolbar.
  Lets users hide a pane to give a right-docked sidebar (e.g. the AI
  chat overlay) more breathing room. Persisted in useEditorStore.
- Default file explorer narrower (210 → 165 px); min 110.
- Removed the redundant `tb-board-pill` (icon + "Editing: X" tooltip);
  the BoardSelector dropdown elsewhere already shows the active board.
- Inlined Import/Export/Upload-firmware buttons; the 3-dot overflow
  menu gave up too much discoverability. Removed dead overflow state.

Simulator:
- Fix: global Delete/Backspace handler in SimulatorCanvas no longer
  fires when the event target is an INPUT/TEXTAREA/SELECT/contentEditable
  — affected any in-page text field, not just the chat overlay.

Overlay extensibility:
- New `data-velxio-slot="agent-chat"` at the bottom of EditorPage so
  pro overlays can portal a chat panel into the editor without
  forking the page.
- vite.config.ts: preserveSymlinks=true when VITE_PRO_BUILD is set.
  Lets local-dev junctions (overlay tree → frontend/src/pro) resolve
  bare imports back to the OSS node_modules without resolving symlinks.

Deps:
- Added react-markdown + remark-gfm (rendered chat output) and
  @google/genai + zod (overlay agent loop). Tree-shaken from the OSS
  bundle when no pro code imports them.

gitignore:
- Ignore backend/app/pro/ and frontend/src/pro/ junctions used by
  developers running a private overlay against the OSS dev server.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 00:56:51 -03:00
David Montero Crespo bb14ab663a
Merge pull request #145 from davidmonterocrespo24/examples-real-thumbnails
feat(examples): add 7 ePaper example thumbnails
2026-05-08 00:42:09 -03:00
davidmonterocrespo24 3df43ca52e feat(examples): add 7 ePaper example thumbnails
The ePaper examples in examples-displays-epaper.ts use slugs prefixed
`epaper-*` (e.g. `epaper-1in54-uno-hello`), but the previous discovery
regex was matching `epd-*` — those are ePaper-component IDs that
appear in wire definitions, NOT example IDs. So /examples/epd-154
returns 404 ("Example Not Found") and the 7 actual ePaper examples
were never captured.

Fix: discovery regex now reads `epaper-` (the real prefix). Captured
all 7:
  - epaper-1in54-uno-hello   (Uno + 1.54" SSD1681)
  - epaper-2in13-pico-clock  (Pico + 2.13")
  - epaper-2in9-esp32-weather (ESP32 + 2.9" weather panel)
  - epaper-4in2-pico-image   (Pico + 4.2" image)
  - epaper-7in5-esp32-dashboard (ESP32 + 7.5" dashboard)
  - epaper-2in9-bwr-esp32-alert (ESP32 + 2.9" black-white-red)
  - epaper-5in65-7c-esp32-rainbow (ESP32 + 5.65" 7-color)

Coverage now: 166/166 examples (was 159/166).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 05:36:28 +02:00
David Montero Crespo 49d6dceaed
Merge pull request #144 from davidmonterocrespo24/examples-real-thumbnails
feat(examples): add 30 analog-only example thumbnails
2026-05-08 00:23:59 -03:00
davidmonterocrespo24 41244f6f6c feat(examples): add 30 analog-only example thumbnails
The analog-only circuits in examples-analog.ts (slugs prefixed `an-*`)
have no SEO route — they aren't in sitemap.xml — and the capture
script previously discovered slugs from sitemap only, so all 30
fell through to the CircuitPreview SVG mock which doesn't draw the
wires for these layouts.

Updated discovery: also grep the local velxio submodule's
examples-analog.ts for `an-*` ID literals (and `100d-*` / `epd-*`
while we're at it for the 100-days and epaper data files), in
addition to the sitemap pull.

Updated wait condition: capture now waits for any board OR component
OR wire path inside .canvas-world, not specifically [data-board-id]
(analog-only examples have no Arduino, just a signal-generator + parts).

Coverage: 159/166 examples have real screenshots now (was 129/166).
The 7 `epd-*` epaper examples are still falling back to CircuitPreview
because they don't render an "Open in Simulator" CTA on /examples/<slug>;
they'll need a separate loader path.

Re-captured the 109 existing thumbs at the same time — content is
visually identical for those, no regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 04:57:10 +02:00
David Montero Crespo 9d36eac816
Merge pull request #143 from davidmonterocrespo24/examples-real-thumbnails
Examples real thumbnails
2026-05-07 17:56:44 -03:00
David Montero Crespo 3dc52485a8
Merge pull request #142 from davidmonterocrespo24/fix-riscv-marketing-copy
fix(docs): RISC-V emulation goes through QEMU, not a TypeScript brows…
2026-05-07 17:56:20 -03:00
davidmonterocrespo24 37fbc502d1 chore(examples): drop PNG thumbnails, ship WebP only (-80% asset size)
The PNG fallbacks were carrying 80% of the gallery's bundled weight
(16 MB of 20 MB) and serving virtually no traffic — WebP is supported
on ~97% of in-use browsers, and the few hold-outs (very old Safari)
fall through to the CircuitPreview SVG mock via the existing onError
handler. No visual regression for modern browsers.

Numbers:
- before: 258 files, ~20 MB total
- after:  129 files, ~3.6 MB total (avg 28.6 KB / WebP)

ExampleThumbnail simplified: drops the <picture>/<source> wrap around
the WebP <source> + PNG fallback, just renders the WebP <img> directly
with onError → CircuitPreview.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:54:33 +02:00
davidmonterocrespo24 436bafbd84 feat(examples): backfill remaining 27 example thumbnails
Adds the slugs that hadn't been captured in the first batch (some
extra coverage from a later examples-* file, plus 10 retries that
hit a transient waitForLoadState timeout on the first sweep).

Coverage is now 129/129 — every example exposed via sitemap.xml has
a real canvas screenshot. The few that still 404 (slugs only present
in non-sitemap data files) keep the CircuitPreview fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:48:29 +02:00
davidmonterocrespo24 d53f4f1380 feat(examples): real canvas screenshots as gallery thumbnails
Replaces the manually-positioned wokwi-element mock (CircuitPreview) in
the /examples gallery with real screenshots of the actual simulator
canvas, so each card shows the example's boards + components + wires
exactly as they appear when you open the example.

How it works
- A new <ExampleThumbnail> component tries
  /examples-thumbs/<id>.{webp,png} first. If the image 404s — or no
  thumbnail has been captured yet — it falls back to the existing
  CircuitPreview component. No-op for examples without a screenshot.
- ExamplesGallery and ExampleDetailPage now render <ExampleThumbnail>
  instead of CircuitPreview directly.
- Explicit example.thumbnail field still wins (kept the existing
  override path in case someone wants a custom asset).

Capture pipeline
- Generated by velxio-prod's scripts/capture-example-thumbs.mjs
  (Playwright + sharp). For each example: opens /examples/<slug>,
  clicks "Open in Simulator", waits for [data-board-id] elements,
  computes the bbox of all boards + components, sets a transform on
  .canvas-world to center and fit them with 12% padding inside the
  canvas viewport, screenshots .canvas-content, and re-encodes to
  600x360 @2x DPI as .png + .webp.

This commit ships the first batch (102 of ~129 examples — the rest
will follow once the capture completes; missing slugs gracefully
fall back to CircuitPreview in the meantime).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:44:10 +02:00
davidmonterocrespo24 9d5d2e8a4a fix(docs): RISC-V emulation goes through QEMU, not a TypeScript browser core
The marketing copy and docs claimed ESP32-C3 / XIAO-C3 / SuperMini /
CH32V003 ran on a "browser-native RV32IMC core written in TypeScript",
but production runs through QEMU lcgamboa (libqemu-riscv32) with the
esp32c3-picsimlab machine — same backend pattern as Xtensa ESP32, just
a different libqemu binary. The TypeScript ISA layer
(RiscVCore.ts / Esp32C3Simulator.ts / RiscVSimulator.ts) is kept only
as Vitest unit-test infrastructure for RV32IMC instruction decoding;
it cannot handle the 150+ ROM functions ESP-IDF needs at boot and is
not wired into the production emulation path.

Files updated:

Marketing pages
- LandingPage: board-group label, FAQ answer, architecture description
  no longer claim "browser-native" or "no backend needed" for RISC-V.
- AboutPage: arch card retitled "RISC-V via QEMU", body explains the
  libqemu-riscv32 / lcgamboa backend.
- Velxio2Page: arch group engine label, multi-board feature item,
  competitive-comparison card all corrected.
- ArduinoEmulatorPage: two RISC-V cards corrected.
- ESP32SimulatorPage: ESP32-C3 cross-link card corrected.
- ESP32C3SimulatorPage: hero subtitle, trust strip, supported-boards
  intro, JSON-LD description corrected.
- ElectronicsSimulatorPage: install-needed FAQ corrected.
- examples.ts: c3-blink description and code-comment corrected.

SEO surfaces
- index.html: JSON-LD SoftwareApplication description, OS-fallback FAQ
  body, supported-boards <ul> bullets, feature list bullets corrected.
- seoRoutes.ts: /esp32-c3-simulator title + description corrected;
  homepage description corrected.

Docs page
- DocsPage RiscVEmulationSection: intro paragraph rewritten — RISC-V
  goes through QEMU lcgamboa with libqemu-riscv32 / esp32c3-picsimlab,
  TypeScript layer is Vitest-only.
- DocsPage Esp32EmulationSection callout: section now applies to all
  ESP32 family (Xtensa + RISC-V), pointer to RISC-V doc clarified.

README
- "Boards" table: production-engine column for ESP32-C3 family and
  CH32V003 changed from "RiscVCore.ts (browser)" to "QEMU lcgamboa
  (backend)".
- "ESP32-C3 / XIAO-C3 / SuperMini / CH32V003" subsection retitled
  "(RISC-V via QEMU)" — body explains libqemu-riscv32 backend and
  flags the TypeScript layer as Vitest-only.

The two remaining "browser-native" hits in the codebase
(Velxio25Page:176, index.html:348) are about ngspice-WASM SPICE
analog simulation, which genuinely is browser-native — left alone.

Build verified: npm run build:docker succeeds, 246 SEO pages prerender.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:56:29 +02:00
David Montero Crespo 458d2b0bc1
Merge pull request #141 from davidmonterocrespo24/hero-editor-screenshot
feat(marketing): replace mock hero with live editor screenshot
2026-05-07 16:30:05 -03:00
davidmonterocrespo24 0c8d96a05c feat(marketing): replace mock hero with live editor screenshot
Captures /examples/traffic-light → /editor in headless Chromium and saves
the rendered editor as a 3840x2160 (2x DPI) PNG + WebP for the landing
page hero.

The shot includes the code editor on the left (Traffic Light Simulator
.ino), the Arduino Uno on the canvas with three LEDs wired up, the SPICE
nets indicator, and the full chrome — a much stronger first impression
than the previous CSS-mocked schematic.

Generation script lives in the private velxio-prod repo
(scripts/capture-hero.mjs) and can be re-run any time to refresh the
asset against the live deployment.

- /marketing/hero-editor.png (320 KB)
- /marketing/hero-editor.webp (160 KB)
- LandingPage hero <picture> now points at these (loading=eager,
  fetchPriority=high since it's above the fold).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:21:52 +02:00
David Montero Crespo 8781719e40
Merge pull request #140 from davidmonterocrespo24/design-system-overhaul
feat(design): tokens, webfonts, Lucide icons, board PNG/WebP picture …
2026-05-07 16:08:40 -03:00
davidmonterocrespo24 e0dbde0a76 feat(design): tokens, webfonts, Lucide icons, board PNG/WebP picture strip
Foundation
- Add 7 token CSS files in src/tokens/ — semantic colors, 4-pt spacing,
  Apple-HIG type ramp, radius/elevation/motion/z-index scales.
- Refactor src/index.css to import tokens and remap legacy aliases
  (--accent, --bg, etc.) onto the new --color-* semantics so existing
  components keep rendering during migration.
- Drop the duplicate font-family from src/App.css; body inherits from :root.
- Global *:focus-visible ring backed by --color-focus-ring (WCAG 2.4.7).

Webfonts (self-hosted)
- Add Inter.var.woff2 (variable, OFL) and JetBrainsMono.var.woff2 to
  public/fonts/. Preloaded in index.html with crossorigin.
- Old stack -apple-system kept as fallback so Mac users still get SF Pro.
- Fixes cross-OS rendering inconsistency (Win/Linux/Android were falling
  back to Segoe UI / Roboto, breaking the type grid).

Component primitives
- New src/components/ui/{Button,Card,Input}.tsx + .css. Built on the
  semantic tokens, ready for incremental migration of .ap-* CSS classes.

Lucide icons
- Replace 6 inline SVG icon components in LandingPage (IcoChip / IcoCpu /
  IcoCode / IcoZap / IcoLayers / IcoMonitor) with lucide-react imports.
  Aliased so call sites are unchanged. ~80 lines of inline SVG removed.
- IcoGitHub kept bespoke (filled glyph, brand-correct).

Marketing assets
- Convert top 8 boards to transparent PNG + WebP at 1x / 2x:
  Arduino Uno, Nano, Mega 2560, Pi Pico, Pi Pico W, ESP32-C3,
  ESP32-DevKit-V1, XIAO ESP32-S3.
- Migrate matching cards in LandingPage and Velxio2Page to <picture>
  with WebP > PNG > SVG fallback. Other 8 boards keep <img src=*.svg>
  for now (Raspberry Pi 3B, ESP32-CAM, etc.).
- Refresh og-image.png — same canonical URL, new content (4 hero boards
  + branding instead of generic logo card).
- Fix latent bug in LandingPage: ESP32 DevKit V1 card was loading
  esp32-devkit-c-v4.svg; now uses esp32-devkit-v1.{webp,png,svg}.

Build verified: npm run build:docker succeeds, 246 SEO pages prerender.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:45:44 +02:00
David Montero Crespo 0fdbeffda5 docs: add ESP32-P4 research and feasibility report
Investigation into adding ESP32-P4 to Velxio via either of the two existing
emulation paths (frontend JS/WASM or backend QEMU/WebSocket). Verified the
arduino-cli toolchain works (RISC-V 32-bit ELF, RVC, single-float ABI), but
both emulation paths are blocked upstream:

- espressif/qemu has no esp32p4 machine yet (issue #127, status: To Do).
- No open-source JS/WASM ESP32 emulator exists; Wokwi's engine is closed.

Includes a smoke-test script ready for the day the Espressif QEMU machine
lands, plus a Phase A/B/C plan in autosearch/06_recommendations.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 15:48:05 -03:00
David Montero Crespo 694a2f4073 fix(frontend): polyfill crypto.randomUUID for non-secure contexts
crypto.randomUUID() is only exposed on secure contexts (HTTPS, localhost,
127.0.0.1, ::1). When Velxio is self-hosted and accessed via a LAN IP over
plain HTTP (e.g. http://192.168.31.139:3080/), crypto.randomUUID is
undefined and any code path that calls it throws TypeError.

This silently broke ESP32 simulation start for self-hosters: the frontend
generates a UUID for the WS client_id when Run is clicked; the throw
rejected the promise before reaching the WS connect, so the backend
never got the start request — no worker spawned, logs empty, simulation
"didn't start" with no visible error.

Same root cause would also break the multi-file editor (createFile,
createFileGroup) on the same LAN-HTTP self-host setup, just less
observably.

Add a single generateUUID() helper that:
  1. Uses crypto.randomUUID() when available (secure context fast path).
  2. Falls back to crypto.getRandomValues() — which IS available in
     non-secure contexts — to build a v4 UUID by hand.
  3. Final fallback to Math.random() if even that is missing
     (defensive — Web Crypto getRandomValues has been universal for
     years).

Replace all 6 crypto.randomUUID() call sites:
  - frontend/src/simulation/Esp32Bridge.ts (2 sites — getTabSessionId)
  - frontend/src/store/useEditorStore.ts   (4 sites — file IDs)

Reported by a self-hoster on OrangePi 5B accessing Velxio via LAN IP.
DevTools console showed:
  TypeError: crypto.randomUUID is not a function
    at Ph (...) at wh.connect (...) at startBoard (...)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 13:41:30 -03:00
David Montero Crespo cd3ee6172b copy: rewrite landing hero — drop SPICE jargon, lead with the boards
The previous hero ("Circuits + Code. / One Browser Tab. / SPICE-accurate.")
was optimised for EE engineers searching for circuit simulators. Most
visitors land here looking for an Arduino emulator they can use without
installing anything — the names of the supported boards are a stronger
hook than analog-simulation accuracy.

Restored the older "Arduino, ESP32 & Raspberry Pi. / Right in your
browser." framing and tightened the subtitle to action verbs (Write,
wire, run) plus concrete numbers (19 boards, 48+ parts). Drops:
- "SPICE-accurate" — kept on the dedicated /arduino-emulator,
  /circuit-simulator etc. SEO landing pages where the audience is
  actively looking for it
- "ngspice", "co-simulated", "custom chips in C or Rust" — niche, fit
  better in the features section below
2026-05-05 11:29:45 -03:00
David Montero Crespo 26c7d50310 fix(ci): two stale-path bugs from the recent refactors
1. Backend test (test_arduino_cli_attinycore.py): the entrypoint script
   was renamed deploy/ → docker/ in commit b736aea but this test still
   pointed at the old path. Update the read_text() call + docstring.

2. Frontend CI (frontend-tests.yml): the cache key
   `frontend-${{ hashFiles('frontend/package-lock.json') }}` was tied to
   a file that has since been gitignored (commit eb9a3ec). hashFiles()
   on a missing file returns the same empty hash forever, so every CI
   run was restoring the same stale node_modules — including the
   symlinks to `file:../third-party/wokwi-elements` that existed before
   the npm migration in commit 531c337. On revalidation, npm tried to
   run wokwi-elements' `prepare` script (`husky install && npm run
   build`), which failed with "husky: not found".

   Drop the cache step entirely; lock files aren't committed so cache
   keys can't be made meaningful without overcomplication. Adds ~30s
   per CI run, but actually correct. Also pass --no-audit --no-fund
   to npm install for cleaner logs.
2026-05-05 11:22:02 -03:00
David Montero Crespo 26e0c2be60 feat(components): add mergeComponents API to ComponentRegistry
Forgotten in the prior commit (case-mismatch on Windows tracked the wrong
filename). Adds the public method overlays use to splice extra components
into the picker after default-metadata load. Components with an existing
id are replaced; new ones are appended.
2026-05-05 10:34:43 -03:00
David Montero Crespo 8b14815094 feat(components): pro_only flag + registry merge API + picker gate hook
Three small additions so private overlays can add components gated behind
a paid subscription without forking the picker:

- types/component-metadata.ts: optional pro_only?: boolean field on
  ComponentMetadata. Self-hosters never set it; picker behaves identically.
- services/componentRegistry.ts: new mergeComponents() public method.
  Pro overlay calls this after the default registry has loaded to splice
  in extra components (replacing any with the same id).
- components/ComponentPickerModal.tsx: when a pro_only component is
  clicked, the picker first calls window.__velxio_pro_gate__(component)
  if defined. If the gate returns true, the click is consumed (overlay
  shows an upgrade modal). If absent or returns false, the click passes
  through to onSelectComponent as normal.

Net upstream change: ~25 lines, all backwards-compatible. OSS image
behaves exactly as before since no overlay sets pro_only or installs
the gate.
2026-05-05 10:33:53 -03:00
David Montero Crespo 77b3e86b50 feat(frontend): /pricing route placeholder + UserResponse subscription fields
Two upstream additions to support private overlays implementing paid tiers
without forking client code:

- store/useAuthStore.ts: UserResponse extended with optional
  is_paid_subscriber, subscription_status, subscription_period_end. The
  backend now returns these in /api/auth/me; the persist middleware
  serialises them automatically.
- pages/PricingPlaceholder.tsx (NEW): the /pricing route. Renders a polite
  "this image is fully free" message for self-hosters plus a
  data-velxio-slot="pricing-page" target where private overlays can
  portal-inject a real pricing page.
- App.tsx: register the /pricing route after /about.

Self-hosted OSS image: /pricing shows the placeholder, no behavioural
change anywhere else. Production with a private overlay: /pricing shows
the overlay's full pricing UI.

Frontend build verified.
2026-05-05 10:24:01 -03:00
David Montero Crespo 5f0e7523ed feat(backend): subscription state columns on users table
Adds 4 nullable columns to the users table so deployments that wire an
external billing system (e.g. velxio.dev with Odoo) have a place to cache
subscription status. Self-hosters never write these — defaults are safe
(no paid features unlock).

- models/user.py: is_paid_subscriber (bool, default false), subscription_status
  (str|None), subscription_period_end (datetime|None), odoo_partner_id
  (int|None, indexed).
- main.py: 4 ALTER TABLE statements appended to the legacy_migrations list
  so existing deployments auto-migrate on next boot.
- schemas/auth.py: extend UserResponse with the 3 user-visible fields
  (is_paid_subscriber, subscription_status, subscription_period_end). The
  frontend useAuthStore already persists the whole UserResponse, so these
  surface automatically without any frontend changes upstream.
  odoo_partner_id stays internal — clients don't need it.

Zero behavioural change for existing OSS deployments.
2026-05-05 10:21:37 -03:00
David Montero Crespo a5b5e57aae feat: add data-velxio-slot markers for overlay portal targets
Three small markers (each one HTML attribute) so private overlays can
portal-inject UI into well-defined places without forking the upstream
component:

- AppHeader user dropdown: data-velxio-slot="user-menu"
  Lets overlays add menu items between "My projects" and "Sign out"
  (e.g. a Privacy / opt-out item).
- AdminPage tab bar: data-velxio-slot="admin-tabs"
  Lets overlays add extra tabs alongside Dashboard / Users / Projects /
  Boards (e.g. a Pro Analytics tab).
- AdminPage tab content area: data-velxio-slot="admin-tab-content"
  Sibling div where overlay tab content can portal-render.

Generic markers, no overlay-specific code in upstream. Anyone with
private extensions can use them. The OSS build is otherwise unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:28:02 -03:00
David Montero Crespo 20c9aa3beb chore: bump qemu-lcgamboa to 1bca754 (linux/macos build fixes)
Two upstream fixes for compiling libqemu-xtensa.so / .dylib from source:
- grep regex no longer requires space before softmmu_main
- parse link cmd from verbose stdout (no .rsp file on macOS)

Runtime behaviour unchanged — these only affect anyone who rebuilds
QEMU from source. Prebuilt .so/.dylib in the qemu-prebuilt release
are unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 22:04:04 -03:00
David Montero Crespo edd2ac32d5 feat: add optional extension hooks for private overlays
Three small, backwards-compatible hooks let anyone with private features
(velxio.dev's analytics, custom integrations, paid tiers, …) layer them
on top of the open-source build without forking files.

Backend (app/main.py):
- After standard router registration, try-import an optional `app.pro`
  module exposing `register_pro(app)`. ImportError is silently swallowed
  (the OSS image doesn't ship `app.pro`, so this is a no-op there).

Frontend:
- EditorToolbar: new optional `rightSlot` prop renders extra elements
  after the built-in right-group buttons (mirrors the existing
  `centerSlot` pattern).
- main.tsx: dynamic `import('@pro/index')` gated by VITE_PRO_BUILD env.
  When unset (OSS build), the branch is dead-code-eliminated and no pro
  chunk is emitted.
- vite.config.ts: `@pro` alias resolves to `src/__pro_stub__/` by default.
  Private builds set `VITE_PRO_BUILD=true` and `PRO_OVERLAY_PATH=<path>`
  to point at their real overlay tree.
- src/__pro_stub__/index.ts: 1-line no-op `mountPro` so TypeScript and
  Vite resolvers stay happy in OSS builds.

Verified: `npm run build:docker` succeeds; `npm test` passes 1161/1162;
the OSS bundle (43 MB) contains zero references to `__pro_stub__`,
`@pro`, or `pro/index` (verified via `grep dist/`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:03:20 -03:00
David Montero 156d89c61d fix(espidf): include non-utility subdirs for libs without src/ layout
The _should_include filter inside _merge_arduino_libs_to_component
rejected every subdirectory of a library except 'utility/' when the
library lacked a src/ layout. That blocked legitimate header dirs like
Adafruit_GFX_Library/Fonts/, breaking compiles that use any GxEPD2
example with a custom font (#include <Fonts/FreeMonoBold12pt7b.h>).

The earlier filter at the top of the function already excludes
docs/examples/tests/etc. via excluded_dirs, so anything that survives
that check is presumed to be buildable source. Letting all remaining
subdirs through restores Fonts/, gfxfont/, and similar conventional
auxiliary header directories that Adafruit-style libs rely on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 15:41:53 +02:00
David Montero 71e90f9b90 fix(compile): bump frontend axios timeout 180s → 600s for ESP-IDF builds
Cold ESP-IDF builds (esp32, esp32-c3, esp32-cam) routinely take 5-10
minutes the first time a project is compiled. The 180s axios timeout
on POST /api/compile/ was cutting the connection long before the
backend finished, surfacing as the misleading 'No response from
server. Is the backend running on port 8001?' error.

Bumping the client timeout to 600s aligns with the nginx
proxy_read_timeout (also 600s) so the chain end-to-end is consistent.

Arduino sketches still compile in seconds — the timeout is an upper
bound, not a delay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 15:32:24 +02:00
David Montero 4fbaac9b4d fix(nginx): bump /api/ proxy_read_timeout 300s → 600s for ESP-IDF builds
Cold ESP-IDF compiles (esp32, esp32-cam, etc.) can legitimately take
5–10 minutes — first time the project is built, ninja has to compile
the entire IDF + Arduino-ESP32 component graph from scratch. Nginx was
cutting the connection at 5 min, which surfaced as 'No response from
server' in the frontend even though the build was still progressing.

Worse, since the backend doesn't cancel the compile on client
disconnect, repeated user clicks pile up parallel ninja jobs that
saturate CPU and slow every concurrent build further.

10 min covers cold builds with comfortable margin. Long-term we should
make compile a job (POST → job_id, GET status) and cancel duplicates
server-side, but the timeout bump unblocks users today.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 15:15:11 +02:00
David Montero 78a834700b fix(examples): use esp32-devkit-c-v4 for ePaper examples (GPIO 16/17)
The 4 ESP32 ePaper examples (BW weather, BWR alert, UC8179 dashboard,
ACeP rainbow) wired GxEPD2 to GPIO 16 (RST) and GPIO 17 (DC), which is
the canonical pinout shown in every GxEPD2 example. But those pins are
not broken out on the DevKit V1 variant (PINS_ESP32) — they only exist
on DevKit-C-V4 (PINS_ESP32_DEVKIT_C_V4).

Result: the RST and DC wires fell back to (0,0) and rendered as a red
+ purple line shooting from the corner of the board. CLAUDE.md §6a
documents this exact symptom.

Switching boardType to 'esp32-devkit-c-v4' renders the variant whose
pinInfo includes 16 and 17. Also rename pinName 'GND' → 'GND.1' since
DevKit-C-V4 exposes three GND pins as GND.1/2/3 (not a plain 'GND').

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 06:37:20 +02:00
David Montero Crespo eb9a3ec92f chore: stop committing package-lock.json (cross-platform breakage)
A lock file pins platform-specific native binaries — Rollup, esbuild, swc.
A lock generated on Windows brings @rollup/rollup-win32-x64-msvc but no
Linux variant; a lock generated on Linux does the inverse. The Docker
build kept blowing up with MODULE_NOT_FOUND on rollup/dist/native.js
whenever the lock came from a contributor's non-Linux machine.

Trade-off: we lose npm's transitive-version pinning. Mitigated by:
- package.json caret ranges keep majors stable
- Docker image is rebuilt + retagged per release, so a deployed image
  has a frozen dep set regardless of the lock
- Production uses a pinned upstream commit via velxio-prod's submodule,
  not lock-driven repro
- Dependabot still flags vulnerable transitives via package.json scans

Changes:
- .gitignore: ignore package-lock.json everywhere
- .dockerignore: same (defense-in-depth — never enter build context)
- Dockerfile.standalone: keep `rm -f package-lock.json` as a safety net
  for `docker build` runs from trees with a local lock
- frontend-tests.yml: `npm ci` → `npm install` (npm ci requires a lock)
- Delete the two committed locks (frontend/ + root). The test/* and
  vscode-extension/* locks are left as-is — internal tooling, separate
  install paths, not in the Docker build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 01:08:23 -03:00
David Montero Crespo 2a99540e5a fix(docker): drop host package-lock.json before npm install in build
Lock files generated on Windows/macOS pin platform-specific Rollup native
binaries (e.g. @rollup/rollup-win32-x64-msvc) and won't bring in the Linux
ones the Docker image needs. Symptom: `MODULE_NOT_FOUND` on
`rollup/dist/native.js` during `npm run build:docker`. See npm/cli#4828.

Removing the lock inside the build forces a fresh, Linux-native dep
resolution. Side effect: a tiny loss of cross-build version pinning, which
is acceptable here — `npm install` honours the version ranges in
package.json so semver-compatible patches at most slip in.

The proper long-term fix is to regenerate package-lock.json on Linux and
commit that. Until then this rm guards every Docker build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 01:04:47 -03:00