Commit Graph

44 Commits

Author SHA1 Message Date
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 a2df942d74 fix(docker): vendor drazzy.com board index + seed missing indexes at boot
drazzy.com (ATTinyCore index host) has had an expired TLS certificate
since 2026-06-22 and now 301-redirects http to https, defeating the
plain-http URL pinned to sidestep its TLS issues. Two failure modes:

1. A /root/.arduino15 volume from an older image can reference the index
   in config while lacking the file; arduino-cli then fails instance
   init outright, breaking EVERY compile, not just ATtiny (issue #254).
2. backend/Dockerfile chained update-index with &&, so any uncached
   image build fails hard while the host is broken (exit 1 verified).

A stale index is harmless (ATTinyCore 1.4.1's platform archive and its
micronucleus 2.0a4 both download from github.com); a missing one is
fatal. So: vendor the index under backend/board-indexes/, copy it to
/opt/arduino15-seed in Dockerfile.standalone, and teach entrypoint.sh
to seed any missing package_*.json into /root/.arduino15 at boot,
healing stale volumes. backend/Dockerfile seeds the index directly and
makes update-index best-effort; core install lines stay strict.

Verified: removing the index reproduces the reporter's exact
'Error initializing instance' brick; after seeding, instance init
exits 0 with the host still broken.
2026-07-17 06:41:44 +02:00
David Montero 7bdf28ed76 fix(esp32): bundle mkspiffs so uploaded SPIFFS files are actually flashed
The SPIFFS upload feature (GH #162) is wired end-to-end — Board Options
uploads -> spiffs_files -> espidf_compiler._build_spiffs_image() ->
_merge_flash_image() at the partition offset — but the mkspiffs binary it
shells out to was never bundled in the image. _locate_mkspiffs() returns
None, _build_spiffs_image() logs "mkspiffs not found ... files will be
ignored" and returns None, and the merged flash ships with a blank SPIFFS
region. The firmware's SPIFFS.begin() then fails to mount, auto-formats,
and the uploaded files never appear.

Install the arduino-esp32 0.2.3 flavor of mkspiffs (the exact build
referenced by the arduino-esp32 2.0.17 package index, matching the SPIFFS
on-disk format SPIFFS.begin() expects) into the espidf-builder stage at the
path the runtime already searches (IDF_TOOLS_PATH/tools/mkspiffs/*/mkspiffs/).
No code change needed — _build_spiffs_image() works once the binary exists.

LittleFS is a separate follow-up (arduino-esp32 2.0.17's index does not ship
mklittlefs; needs sourcing the matching tool + FS-type selection in the
compiler).
2026-06-10 19:53:21 +02:00
David Montero Crespo 0e2f0790db feat(chips): C-to-Z80 compile via SDCC + LED chaser example
Adds a third format to /api/compile-rom: `c` (C source compiled by SDCC
to Z80 bytes). Same chip-program flow as 8080/Z80 asm — write C in a
project file, click Compile, click Run.

Backend:
- backend/app/services/c_compile.py — async SDCC wrapper. Locates the
  sdcc binary on PATH (or via SDCC env var, or common Windows install
  paths) and shells out with target=mz80 + --code-loc 0x100 --data-loc
  0x8000. Parses the resulting Intel HEX into raw ROM bytes. Pure 8080
  is rejected with a clear error (SDCC has no 8080 backend; Z80 ROMs
  also run on the i8080-cpu chip if you avoid Z80-only ops).
- rom_compile.py: compile_rom is now async; the new c branch delegates
  to c_compile. compile_rom_endpoint awaits it.

Frontend:
- romCompileService: RomFormat gains 'c'; formatForFile maps .c/.cpp to
  'c'. isChipProgramFile intentionally still excludes .c — disambiguation
  happens at the EditorToolbar level.
- EditorToolbar: the chip-program path also fires when a custom-chip
  has programFile === activeFile.name (regardless of extension). That
  lets .c files route to /api/compile-rom (SDCC) when bound to a CPU
  chip, while .c files NOT bound to any chip continue to route to
  arduino-cli as before.

Docker:
- Dockerfile.standalone adds `sdcc` to the apt-get install list, so the
  prod image ships with SDCC out of the box.

Example:
- /examples/z80-led-chaser-c — z80-cpu chip + chaser.c (a Larson
  scanner written in C with __at() MMIO definitions). Compiles cleanly
  with SDCC's --code-loc 0x100 default crt0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:31:10 -03:00
davidmonterocrespo24 93fd4617af feat(sim): boot_images module + Pi 3 emulation restored
Pi 3 simulation had been broken since at least April 2026 (51
fail-events / 24h per docs/PI3_EMULATION_BROKEN.md). Two distinct
defects compounded:

1. qemu_manager.py hard-coded paths for kernel8.img, a device-tree
   blob (under a DOS 8.3 short name!), and a 5.4 GiB Raspberry Pi OS
   SD image — none of which shipped in the repo or were pulled at
   image build.

2. qemu-system-arm + qemu-utils were missing from the Docker image
   entirely, so even with the boot files in place QEMU couldn't
   launch. Add both to Dockerfile.standalone (~200 MB).

The architecture fix is a new `app.services.boot_images` module:

  * Manifest-driven (boot_images/manifest.json, versioned in repo,
    declares SHA256 + size for each file, supports an optional
    `compressed.{encoding,sha256,size_bytes}` block for assets shipped
    as .zst).

  * `BootImageProvider` materialises files lazily, atomically (temp +
    rename), verifies SHA256 pre- AND post-decompression, caches under
    /var/cache/velxio/boot-images, serialises concurrent get() calls
    per image set via asyncio.Lock.

  * `AssetDownloader` Protocol with two impls:
    - `LicenseGatedDownloader` — same flow ESP32 / RISC-V QEMU libs
      use (VELXIO_BINARY_BASE_URL + VELXIO_LICENSE_KEY).
    - `LocalDirectoryDownloader` — for tests + in-prod use where the
      licence-module storage is already on the same filesystem (saves
      the loopback HTTP roundtrip on a 1.4 GiB blob).

  * `build_downloader_from_env()` picks one — local-dir wins if both
    sets of env vars are present, so the prod box short-circuits to
    direct disk reads automatically.

  * Lifespan hook in qemu_manager.py pre-warms the cache on container
    boot so first-time user requests don't pay the 30-60 s download
    + decompress latency.

Adding a future board kind (Pi 4 / Pi 5) is now: upload assets via
upload-binary.sh, append an entry to manifest.json, register a
lifespan pre-warm in the new board's service module. Zero edits to
provider.py / downloader.py.

Manifest entries for raspberry-pi-3:
  kernel8.img             9 695 883 bytes  (uncompressed)
  bcm2710-rpi-3-b.dtb        34 687 bytes  (uncompressed)
  raspios-trixie-armhf.img  5 729 419 264 bytes raw
                          / 1 488 002 803 bytes .zst on wire (zstd -19)
  source: 2026-04-21 build from raspberrypi.com

Tests: 21 new unit tests covering manifest parsing, integrity
helpers, both downloaders, and the provider's idempotent /
concurrent / integrity / decompression / warmup paths. In-process
FakeDownloader keeps the suite under 1 s and httpx-free.

Docs: new docs/BOOT_IMAGES.md describes the architecture, on-disk
layout, named-volume operation, and the procedure for adding a new
image set.
2026-05-16 05:41:46 +02:00
davidmonterocrespo24 36bd49f507 feat(build): fetch QEMU binaries from velxio.dev license endpoint
The qemu-prebuilt GitHub Release was the convenience-binary hosting
path before we shipped the license module. With the license module
live at /api/pro/license/downloads/, the prebuilts now live there
behind a free personal-tier key.

Dockerfile.standalone:
  - New build-args VELXIO_LICENSE_KEY + VELXIO_BINARY_BASE_URL
  - prebuilt/qemu/ local files still win first (lets users compile
    QEMU from source per docs/BUILD-QEMU.md and use that instead)
  - Legacy QEMU_RELEASE_URL kept as escape hatch for private mirrors
  - Fail-fast with a friendly message if no path is configured

backend-e2e-tests workflow:
  - Reads secrets.VELXIO_BUILD_LICENSE_KEY (set in repo settings)
  - Uses the gated URL pattern; same fallback message on missing key

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 05:55:38 +02:00
davidmonterocrespo24 04eb36d0a2 fix(docker): VOLUME defaults so standalone runs get the perf caches too
A user reported on Discord that an ESP32 Blink compile on a fresh
`docker run -d ghcr.io/.../velxio:master` (no -v flags) takes 8-9
minutes — every single time. Same hardware that the local project
checkout flies through in 5-30 seconds with the new persistent build
dir + ccache pipeline.

Root cause: `docker run` without `-v` mounts gets nothing persistent.
ccache + persistent build dir live in /var/cache/ccache and
/var/lib/velxio-build, both wiped on every `docker rm` (which the
user explicitly did when troubleshooting). docker-compose users get
the volumes via the compose file; standalone users got nothing
because the Dockerfile didn't declare them.

This PR closes that gap.

Dockerfile.standalone
- VOLUME ["/app/data", "/root/.arduino15", "/root/Arduino",
          "/var/cache/ccache", "/var/lib/velxio-build"]
  Anonymous volumes are now created automatically when the user runs
  the image without -v. They survive `docker stop`/`docker start`/
  `docker rm` (only `docker rm -v` or `docker volume prune` removes
  them). Users can still pass `-v` for named volumes — explicit
  mounts always win over the VOLUME directive.
- Replace `ccache --set-config max_size 8G` (RUN, written to
  /var/cache/ccache/ccache.conf which the volume mount masks at
  runtime) with `ENV CCACHE_MAXSIZE=8G` etc. — env vars override any
  conf-file value on every ccache invocation, so the 8 GB cap
  actually applies at runtime regardless of what's in the volume.

README.md
- Update both the quick-start docker run and the detailed self-host
  section to include all five volumes.
- Add a note explaining what each volume is for and that without them,
  cold compile times are 5-7 min vs the 5-30 s warm path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 23:19:34 +02:00
davidmonterocrespo24 e7157b7f05 fix(ccache): set max-size + compression via ENV (image-build config was shadowed)
The previous bump to 8G (PR #153) didn't take effect on prod. Verified
post-deploy:

    Cache size (GB):  2.0 / 2.0 (99.95%)

Cause: $CCACHE_DIR (/var/cache/ccache) is a named docker volume. Any
config we wrote into it via `ccache --set-config max_size 8G` during
the RUN step gets masked at runtime by the mount of the existing
volume — which still contained the original 2 GB config from when the
cache was first populated.

Fix: set CCACHE_MAXSIZE / CCACHE_COMPRESS / CCACHE_COMPRESSLEVEL as
ENV in the Dockerfile. ccache reads those at runtime and they
override any conf-file value, including whatever stale config is
sitting in the volume.

After this lands and the submodule bumps, `ccache -s` should report
the new 8 GB max immediately on container start without needing to
wipe or re-init the cache volume.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:11:49 +02:00
davidmonterocrespo24 c7b8964267 perf(ccache): bump cap 2G → 8G — cache was evicting before hits accumulate
Empirical confirmation from a smoke test on prod after PR #152
(persistent build dir + dedup) landed:

    Cache size (GB):  2.0 / 2.0 (99.96%)
    Hits:                1 / 86886 ( 0.00%)
    Misses:          86885 / 86886 (100.0%)

The cap was set to 2G when ccache was first wired in (PR #149) under
the assumption that base ESP-IDF would fit. With arduino-esp32 +
external Arduino libraries (Adafruit BMP280 + BusIO + Unified_Sensor +
GFX + …) the cacheable object set comfortably exceeds that — we
observed 86k misses while the cache was permanently at 99.96% full,
meaning entries get evicted before subsequent compiles can hit them.

Disk usage on the prod VPS at /var/cache/ccache will rise from ~2 GB
to ~6-8 GB. The host has plenty of headroom and the named volume sits
on the same partition as the rest of /var. Compression at level 6 is
already on, so the disk impact is the on-disk size after compression.

The 13-second warm compile we measured today already proves the bulk
of the perf win came from ninja's incremental cache + persistent build
dir; this is the cherry on top that lets ccache start contributing
across container restarts (when ninja's in-build-dir cache is fresh
but ccache should still hit at the C/C++ object level).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:58:44 +02:00
davidmonterocrespo24 c2fe1af250 perf(compile): dedup, concurrency limits, and persistent build dir for ESP-IDF
Three coordinated fixes that together close the "ESP-IDF compile takes
5-7 min every time" gap and prevent the failure mode where a user clicking
compile multiple times spawns six ninja processes that peel each other
apart on a modest VPS.

What was wrong
- /compile/start generated a fresh uuid4 every call, so 6 clicks = 6
  independent builds racing each other. Saw load average 30 on the prod
  VPS during a real BMP280 attempt today.
- No concurrency limit anywhere; asyncio.create_task() fired without
  gating.
- ccache was wired in last week (PR #149) but reported 18,350 cacheable
  calls and **0 hits** because the build dir was a fresh
  tempfile.TemporaryDirectory(prefix='espidf_') per compile. The random
  /tmp/espidf_<random>/ path baked into -I and -fmacro-prefix-map flags
  → different command line every compile → ccache hash miss every time.

What this PR does

1. Job deduplication (`backend/app/api/routes/compile.py`)
   - New `_job_key(files, board_fqbn)` returns SHA-256 of normalised file
     names + contents + board. Order-independent.
   - New `JOB_BY_KEY: dict[str, str]` indexes hash → job_id.
   - `compile_start` checks JOB_BY_KEY before spawning a new task; if a
     job for this exact content is already pending or running, returns
     the existing job_id (logs `[compile] dedup hit — reusing job <id>`).
   - `_purge_expired_jobs` evicts both COMPILE_JOBS and JOB_BY_KEY,
     keeping the index consistent. Edge case where two jobs share a key
     (old finished, new running) is handled — only evict the key entry
     if it still points at the purged job.

2. Concurrency control (`backend/app/api/routes/compile.py`)
   - `_COMPILE_SEMAPHORE = asyncio.Semaphore(2)` global cap on
     simultaneous compiles.
   - `_target_lock(board_fqbn)` returns a per-target asyncio.Lock so
     concurrent compiles to the SAME board (sharing the persistent build
     dir) serialise. Different boards still run in parallel up to the
     semaphore cap.
   - `_compile_job` acquires sema → per-target lock → flips state to
     `running` → calls `_run_compile`. Pending state now accurately
     reflects "queued waiting for resources".

3. Persistent build dir (`backend/app/services/espidf_compiler.py`)
   - New `_prepare_persistent_project_dir(idf_target)` materialises
     `/var/lib/velxio-build/<target>/project/` from the template on
     first use; on subsequent compiles it wipes only `main/` and
     `user_libs/` (the per-compile parts) and leaves `build/` alone so
     ninja's incremental cache + ccache .o files survive.
   - Toolchain version sentinel (`.idf_version`) wipes the whole target
     dir if the ESP-IDF or arduino-esp32 version changes — cached
     objects from the old toolchain are no longer ABI-compatible.
   - `compile()` is now a thin dispatcher: persistent path or fallback
     to the legacy `tempfile.TemporaryDirectory()` flow. The actual
     build logic was extracted into `_compile_in_dir()` so both paths
     share one implementation, no duplication.
   - Escape hatch: `VELXIO_PERSISTENT_BUILD_DIR=0` env var falls back
     to the tempfile path without rebuilding the image. Critical for
     production safety.

4. ccache normalisation (`Dockerfile.standalone`)
   - + `ENV CCACHE_BASEDIR=/var/lib/velxio-build` makes ccache canonicalise
     absolute paths under that prefix when computing the cache key.
     Robustens hits against any future subdir rearrangement.

5. Docker compose (`docker-compose.yml`)
   - + named volume `velxio-build:/var/lib/velxio-build` so the persistent
     build dir survives `docker compose up -d --build`.
   - + env `VELXIO_PERSISTENT_BUILD_DIR=1` (default ON; users disable
     without rebuilding).

Expected impact
- Cold first compile per container per target: unchanged (~5-7 min).
- Same sketch re-compiled: ~2-5 s (everything cached).
- Different sketch, same target: ~5-30 s (only user code + new lib steps
  rebuild; ESP-IDF base hits cache).
- Different sketch with new libraries: ~30-90 s (new lib component
  compiles; rest hits cache).
- Concurrent clicks on same example: 1 build, others poll the same
  job_id. No more six-ninja meltdown.

Tests
- `test/backend/unit/test_compile_dedup.py` covers `_job_key` stability +
  variance and `_purge_expired_jobs` consistency (including the
  "two jobs share a key" edge case).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 08:33:11 +02:00
davidmonterocrespo24 4908525692 perf(espidf): drop in ccache for ESP32 compiles (~10× warm speedup)
Cold first compile per container is unchanged (cache empty). Subsequent
compiles drop from ~5-7 minutes to ~30-60 seconds because every ESP-IDF
base object (FreeRTOS, lwIP, esp_wifi, libsodium, soc, hal, …) hits the
cache. The user's BMP280 example, which hangs on cold compile, completes
near-instantly on the second attempt.

Why a transparent cache is safe: ccache hashes the preprocessed source +
flags + compiler. A cache hit only happens when the input is byte-for-byte
identical to a prior compile. Different sketches with different libraries
still get correct cache misses; there is no path where one project's
output contaminates another.

Changes
- Dockerfile.standalone: install ccache, set CCACHE_DIR=/var/cache/ccache,
  IDF_CCACHE_ENABLE=1, configure 2 GB cap with compression. Compression
  (level 6) cuts cache disk usage by ~40% with negligible CPU overhead.
- docker-compose.yml: named volume `ccache:/var/cache/ccache` so the
  cache survives `docker compose up -d --build` (without it, every image
  rebuild discards the cache).
- backend/app/services/espidf_compiler.py: pass `-DCCACHE_ENABLE=1` to
  cmake when IDF_CCACHE_ENABLE is truthy. ESP-IDF's project.cmake
  (`set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)` on line 374)
  is what actually wires ccache in; without the cmake -D flag the env
  var alone has no effect because we don't go through idf.py.

Escape hatch: set IDF_CCACHE_ENABLE=0 in compose env to disable without
rebuilding the image.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 05:44:29 +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
David Montero Crespo b736aea86a refactor: rename deploy/ → docker/ (container build, not host deploy)
The folder only holds the in-container nginx + entrypoint that the
standalone Dockerfile copies. Calling it "deploy" implied host-level
production glue, which now lives in github.com/velxio/velxio-prod.
"docker/" makes the build-time vs deploy-time split obvious.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 00:05:55 -03:00
David Montero Crespo 531c337d19 fix(install): unblock self-hosting + drop forced wokwi clones
Resolves several install pain points reported by users (#108, #120) and
removes the obligatory upstream-clone step that confused contributors and
slowed down every Docker build.

Install fixes:
- nginx: server_name → catch-all default_server, drop Debian's stock site
  so reverse-proxied users no longer get the "Welcome to nginx" page.
- entrypoint: auto-generate SECRET_KEY at first boot, persisted under
  data/.secret_key. backend/.env is now optional in docker-compose.yml.
- backend: add greenlet>=3.0.0 (SQLAlchemy async dep that was missing on
  some Python builds — caused uvicorn startup failures on WSL).

Wokwi libs come from npm:
- @wokwi/elements 1.9.2, avr8js 0.21.0, rp2040js 1.3.2 are pinned in
  frontend/package.json. Vite aliases removed.
- Dockerfile.standalone no longer clones avr8js / rp2040js / wokwi-elements
  / wokwi-boards. Frontend stage is just COPY + npm install + build:docker.
- Board SVGs vendored under frontend/public/boards/ (10 deduped against
  existing files, 2 truly new). third-party/wokwi-* clones become reference-
  only credits — generate-component-metadata.ts skips gracefully when absent.

Production config split out:
- docker-compose.prod.yml, deploy/nginx.prod.conf, nginx-host-velxio*.conf,
  update-third-party.bat removed. Production deployment lives in its own
  repo: https://github.com/velxio/velxio-prod (host nginx + HTTPS + backups
  + pinned upstream commit).

Verified locally: 1161 frontend tests pass, build:docker completes clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 00:04:11 -03:00
David Montero Crespo 9cd5061732 refactor: rename wokwi-libs/ → third-party/
The directory grew well beyond Wokwi-only contents: it now hosts
lcgamboa's QEMU fork (qemu-lcgamboa), Espressif's esp32-camera, the
ngspice WASM build, fritzing-parts, picowi, an alternative QEMU
(qemu-esp32), the 100_Days_100_IoT_Projects examples repo, and
Wokwi's own avr8js/rp2040js/wokwi-elements/wokwi-features/wokwi-boards.
"wokwi-libs" was misleading — half the contents have nothing to do
with Wokwi. "third-party/" is the standard convention for vendored
external dependencies.

Mechanical changes:

  Path rename:
    wokwi-libs/ → third-party/
    update-wokwi-libs.bat → update-third-party.bat
    docs/WOKWI_LIBS.md → docs/THIRD_PARTY.md

  Submodule reconfiguration:
    .gitmodules — 4 path= and section names updated
    .git/modules/wokwi-libs/ → .git/modules/third-party/
    each submodule's .git file rewired to ../../.git/modules/third-party/<name>

  Reference updates (~80 files): vite.config.ts aliases, Dockerfile
    COPY paths, GH Actions workflow steps, build_qemu_*.sh, all
    docs/* and test/*/autosearch/* entries that mention the path,
    package-lock.json file: dependencies, .gitignore patterns,
    sitemap.xml + index.html SEO blurbs, scripts/generate-component-*,
    .dockerignore, .idea/vcs.xml. Bulk replaced both `wokwi-libs/`
    (path) and bare `wokwi-libs` (textual mentions in docs/comments).

Verified:
  - npx tsc -b --noEmit produces no new errors related to these paths
  - vite.config.ts aliases now point at ../third-party/avr8js etc.
  - All 4 git submodules (avr8js, rp2040js, wokwi-elements,
    wokwi-features) are linked under third-party/ with their
    worktrees re-populated and config files referencing the new path
  - `grep -r wokwi-libs` returns zero hits outside node_modules,
    .vite, frontend/dist, third-party/ (upstream submodule contents),
    *.pyc caches, and *.dll.pre-camera rollback binaries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:58:57 -03:00
David Montero Crespo 6a88375bc0 feat: persist multi-board projects + add auto-save
The project save/load pipeline only persisted a single `board_type`, so
multi-board workspaces silently lost every board except the active one
on save, and wires referencing the dropped boards' IDs orphaned to the
canvas corner on reload. An audit of the production backup found 74/306
projects (24%) with at least one orphaned wire and 174/301 non-trivial
projects whose code was still the default Blink template — strong signal
that users save once and never re-save.

Backend
- Add `boards_json` column on `projects` with idempotent ALTER TABLE in
  the lifespan migration list.
- New `FileGroup` schema + `file_groups` array on
  ProjectCreate/Update/Response. Legacy `files`/`code` kept for back-compat.
- `project_files.py` now uses `{pid}/{groupId}/{filename}` subdirs via
  `read_groups`/`write_groups`. Legacy flat layouts are auto-promoted on
  read; legacy single-list `files` only updates the active group, leaving
  other boards' files intact.
- `_persist_files_from_body` honors file_groups → files → code priority.

Frontend
- `useSimulatorStore.addBoard` accepts an optional `explicitId` so
  saved board IDs can be restored verbatim (wires reference IDs literally).
- New `loadProjectState({boards, fileGroups, components, wires,
  activeBoardId})` action: tears down current boards, recreates from the
  payload, restores file groups atomically, recalculates wire positions
  on the next frame, and refreshes the Interconnect.
- `useEditorStore.replaceFileGroups` for atomic multi-group restore.
- `SaveProjectModal` and `ProjectByIdPage`/`ProjectPage` now go through
  `buildSavePayload` / `buildLoadPayload` (handles pre-backfill projects
  by synthesising a default board from `board_type`).

Auto-save (#useAutoSaveProject hook)
- 2.5s debounced silent PUT triggered ONLY when an authenticated user
  has a `currentProject` with a UUID. State hash detects real changes
  vs. UI-only churn; baseline is reset on project load so the just-loaded
  state isn't immediately re-saved.
- `beforeunload` flush via `fetch keepalive: true` (supports PUT +
  credentials, survives unload).
- Compact status indicator in `AppHeader` (idle/dirty/saving/saved/error).

Backfill script (one-off, idempotent)
- `backend/scripts/backfill_boards_2026_05.py` populates `boards_json`
  for legacy projects. Heuristic per project, based on which board IDs
  the wires reference:
    Case A — wires only ref 'arduino-uno' but board_type ≠ uno:
             rename id→board_type and rewrite wire endpoints.
    Case B — single-board normal: keep verbatim.
    Case C — multi-board: recreate one board per distinct ref, infer
             kind by stripping trailing -N suffix.
  Also moves any flat files into the active board's group subdir.
  Stdlib-only, runs from host or `docker exec`.

Docker
- `Dockerfile.standalone` now copies `backend/scripts/` into the image
  so the backfill is callable via `docker exec velxio-app python
  /app/scripts/backfill_boards_2026_05.py --apply`.

Verified locally on the restored production backup (363 projects):
33 Case A, 316 Case B, 14 Case C, 135 wire endpoints renamed, 0 orphans.
Re-running the script after apply skips all 363 (idempotent).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:43:33 -03:00
David Montero Crespo 2927a3a2d7 feat: add scripts directory to Dockerfile and mark subproject commits as dirty 2026-04-14 17:38:03 -03:00
David Montero Crespo a3db014d2d feat: multi-arch Docker (amd64+arm64) and fix LED ground check
Docker multi-arch:
- Dockerfile downloads arch-specific QEMU .so via TARGETARCH
- docker-publish.yml adds setup-qemu-action and platforms: linux/amd64,linux/arm64
- qemu-lcgamboa submodule updated (matrix build for both architectures)

LED fix:
- LEDs now require cathode wired to GND (or LOW GPIO) to light up
- Previously LEDs turned on with anode HIGH regardless of cathode connection
- Updated tests to verify anode+cathode behavior
2026-04-07 03:58:52 -03:00
David Montero Crespo b73f28c174 feat: add pre-built ESP-IDF toolchain image workflow 2026-04-04 18:03:43 -03:00
David Montero Crespo 0a724b7566 feat: pre-built QEMU binaries from GitHub Release + WiFi SSID normalization
- Dockerfile: download pre-built .so + ROM from velxio public release
  instead of building from private qemu-lcgamboa source
- espidf_compiler: normalize any WiFi SSID → "Velxio-GUEST" for QEMU
  compatibility (channel 6, open auth)
- docker-compose.yml: unified dev/prod using Dockerfile.standalone
- .dockerignore: exclude qemu-lcgamboa source from Docker context
- .gitignore: ignore prebuilt/ binaries, keep .gitkeep

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 14:27:52 -03:00
David Montero Crespo f495a2ce3a feat: implement ESP32 QEMU backend manager and frontend simulation interface 2026-04-01 22:41:52 -03:00
David Montero ba8b56c647 fix: install ESP-IDF Python requirements in final Docker stage
ESP-IDF 4.4.x requires specific package versions (reedsolo<=1.5.4,
bitstring<4, pyparsing<2.4.0, kconfiglib==13.7.1, etc.) that conflict
with newer defaults. Installing requirements.txt after copying ESP-IDF
ensures the correct versions are used and cmake configure succeeds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 07:45:47 +02:00
David Montero 372e4aea38 fix: add git and packaging to runtime image for ESP-IDF cmake
- git is required by ESP-IDF cmake to resolve submodules and version info
- packaging Python package is required by ESP-IDF Python dependency checks
  during cmake configure step

Without these, ESP32 compilation fails at cmake configure with:
  "Some Python dependencies must be installed"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 07:39:46 +02:00
David Montero 447ab872f5 fix: add cmake, ninja-build and libusb to runtime image
- cmake and ninja are required by ESP-IDF compilation pipeline but were
  missing from the final stage, causing all ESP32 compilations to fail
- libusb-1.0-0 fixes the openocd-esp32 shared library warning on startup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 07:28:01 +02:00
David Montero Crespo 54f8f2782b feat: add ESP32 WiFi/BLE emulation with ESP-IDF compilation pipeline
Replace arduino-cli with ESP-IDF 4.4.7 for ESP32 compilation — Arduino-compiled
firmware crashes in QEMU (9-28 reboots) while ESP-IDF boots cleanly (0 reboots).
The new espidf_compiler translates Arduino WiFi/WebServer sketches to native
ESP-IDF C code, compiles with cmake+ninja, and merges into 4MB flash images.

Key changes:
- ESP-IDF compiler: translates WiFi.begin/WebServer to esp_wifi/esp_http_server
- ESP-IDF project template with QEMU-optimized sdkconfig (DIO, 40MHz, no WDT)
- WiFi status parser for ESP-IDF serial logs (wifi_status, ble_status events)
- IoT Gateway HTTP reverse proxy for ESP32 web servers
- WiFi/BLE auto-detection from sketch content + visual status icons
- Static IP 192.168.4.15 matching slirp DHCP first-client range
- Docker: new espidf-builder stage with ESP-IDF 4.4.7 toolchain
- 157 tests covering WiFi/BLE for both ESP32 (Xtensa) and ESP32-C3 (RISC-V)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 20:53:56 -03:00
David Montero Crespo 8f0c23c857 feat: optimize Dockerfile by deferring arduino-cli core installations to entrypoint.sh for a smaller image size 2026-03-24 18:20:38 -03:00
David Montero Crespo f4e4f863a9 feat: simplify Dockerfile by removing redundant cp command; mark subproject commits as dirty 2026-03-24 17:59:04 -03:00
David Montero Crespo 8ed0438abf feat: update docker-compose to include arduino-libs volume; mark subproject commits as dirty 2026-03-24 17:30:56 -03:00
David Montero Crespo 528287db95 feat: add libfdt1 dependency in Dockerfile; mark subproject commits as dirty 2026-03-24 17:28:56 -03:00
David Montero Crespo 5260261201 feat: Add wokwi-boards library to Dockerfile and mark subprojects as dirty 2026-03-23 11:34:23 -03:00
David Montero Crespo 7053b6f2c8 feat: update ESP32-C3 simulator and add emulation tests
- Updated components-metadata.json with new generated timestamp.
- Refactored Esp32C3Simulator.ts to remove unnecessary debug variables and logging, and added support for additional ROM functions.
- Modified useSimulatorStore.ts to clarify bridge usage for ESP32 boards.
- Updated submodules for QEMU and other libraries to indicate dirty state.
- Added test_esp32c3_emulation.py for end-to-end testing of ESP32-C3 emulation, including compilation, flash image merging, and GPIO event checking.
2026-03-18 23:30:45 -03:00
David Montero Crespo a99a9d512f feat: enhance ESP32 emulation with QEMU integration and update documentation 2026-03-14 14:35:59 -03:00
David Montero Crespo 03f2d7f22e fix: persist SQLite database via host bind mount
Mount ./data:/app/data in both compose files and set DATABASE_URL to
the absolute path /app/data/velxio.db so the database survives container
restarts and updates. Create /app/data in Dockerfile.standalone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 20:32:36 -03:00
David Montero Crespo 22de9173ba fix: update Dockerfile to clone wokwi-libs directly and streamline build process 2026-03-06 13:53:26 -03:00
David Montero Crespo 9c376e70d4 fix: update rp2040js build process to conditionally run based on package.json presence 2026-03-06 13:31:13 -03:00
David Montero Crespo 862d90c2b7 fix: streamline Docker build process and update build scripts 2026-03-06 13:17:14 -03:00
David Montero Crespo 2a8d126b2b fix: update build commands to run only if scripts are present 2026-03-06 13:01:13 -03:00
David Montero Crespo 16271c4bd1 fix: update frontend builder image to use full node:20 version 2026-03-06 12:09:55 -03:00
David Montero Crespo 0d5d440a56 fix: enhance Docker build process and improve file explorer resizing functionality 2026-03-06 11:20:47 -03:00
David Montero Crespo 34dd56b789 fix: retry full build install in docker 2026-03-06 11:05:55 -03:00
David Montero Crespo d0474a5e59 fix: explicit package installs for metadata generation 2026-03-06 11:02:22 -03:00
David Montero Crespo 8f0c431f8d fix: checkout submodules and use robust docker build stage 2026-03-06 10:59:22 -03:00
David Montero Crespo a5c6987aca feat: implement user authentication and project management features
- Add LoginPage and RegisterPage for user authentication.
- Create UserProfilePage to display user projects.
- Implement ProjectPage for viewing and editing individual projects.
- Introduce authService for handling user login, registration, and session management.
- Add projectService for managing project data retrieval and manipulation.
- Enhance EditorPage with file management capabilities and save prompts.
- Introduce Zustand stores for managing authentication, editor state, and project state.
- Add reserved usernames utility to prevent certain usernames during registration.
- Update compilation service to handle multiple files for Arduino sketches.
2026-03-06 10:14:50 -03:00