This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Velxio — a fully local, open-source Arduino emulator
- GitHub: https://github.com/davidmonterocrespo24/velxio
- Frontend: React + Vite + TypeScript with Monaco Editor and visual simulation canvas
- Backend: FastAPI + Python for Arduino code compilation via arduino-cli
- Simulation: Real AVR8 emulation using avr8js with full GPIO/timer/USART support
- Components: Visual electronic components from wokwi-elements (LEDs, resistors, buttons, etc.)
- Auth: None — OSS is single-user anonymous. Accounts + OAuth live in the velxio-prod private overlay that powers velxio.dev.
- Project persistence:
.vlxfile export/import (utils/vlxFile.ts) — zero server-side state. Server-side persistence (SQLite or Postgres via SQLAlchemy) lives in the velxio-prod overlay.
The project uses local clones of official Wokwi repositories in third-party/ instead of npm packages.
Setup:
cd backend
python -m venv venv
venv\Scripts\activate # Windows
pip install -r requirements.txtRun development server:
cd backend
venv\Scripts\activate
uvicorn app.main:app --reload --port 8001Access:
- API: http://localhost:8001
- Docs: http://localhost:8001/docs
Setup:
cd frontend
npm installRun development server:
cd frontend
npm run devBuild for production:
cd frontend
npm run buildDocker build (skips tsc type-check, uses esbuild only):
npm run build:dockerLint:
cd frontend
npm run lintAccess:
@wokwi/elements, avr8js and rp2040js are pulled directly from the npm
registry — see version pins in frontend/package.json. No clone or local
build is required for the Docker image, manual install, or CI.
The folders under third-party/ are reference-only (credits / offline
hacking). The one exception is qemu-lcgamboa (real source dependency for
ESP32 emulation when rebuilding QEMU).
Bump a wokwi lib version: edit the version string in
frontend/package.json and run npm install in frontend/.
Adding new components to wokwi-elements: the metadata generator
(scripts/generate-component-metadata.ts) scans the upstream src/,
which the npm package doesn't ship. Clone wokwi-elements once into
third-party/wokwi-elements/ and run npm run generate:metadata. The
script gracefully skips when the clone is absent — components-metadata.json
is committed.
arduino-cli must be installed on your system:
# Verify installation
arduino-cli version
# Initialize (first time)
arduino-cli core update-index
arduino-cli core install arduino:avr- Code Editing: User writes Arduino code → Monaco Editor → Zustand store (
useEditorStore) - Compilation: Files → Frontend API call → Backend FastAPI → arduino-cli subprocess → Returns .hex file
- Simulation: .hex file → AVRSimulator.loadHex() → Parsed into Uint16Array → CPU execution loop
- Pin Updates: CPU writes to PORTB/C/D → Port listeners → PinManager → Component state updates
- Visual Updates: Component state changes → React re-renders → wokwi-elements update visually
1. Wokwi libs come from npm
@wokwi/elements, avr8js and rp2040js are listed as regular
dependencies in frontend/package.json. Vite resolves them from
node_modules like any other package — no aliases, no file: references.
2. Multi-File Workspace (useEditorStore)
The editor supports multiple files. useEditorStore holds:
interface WorkspaceFile { id: string; name: string; content: string; modified: boolean; }
// State:
files: WorkspaceFile[]
activeFileId: string
openFileIds: string[]
// Key operations:
createFile, deleteFile, renameFile, setFileContent, markFileSaved,
openFile, closeFile, setActiveFile, loadFiles, setCode (legacy)setCode is a legacy setter that writes to the active file's content — used by old call sites.
loadFiles replaces all files when loading a saved project.
3. Multi-File Compilation
The backend accepts an array of files, not a single code string:
// Frontend (compilation.ts)
interface SketchFile { name: string; content: string; }
compileCode(files: SketchFile[], board: string)
// sends: { files, board_fqbn: board }
// Backend (compile.py)
class SketchFile(BaseModel): name: str; content: str
class CompileRequest:
files: list[SketchFile] | None = None
code: str | None = None # legacy fallbackThe backend promotes the first .ino to sketch.ino and applies RP2040 Serial redirect only to sketch.ino.
4. AVR Simulation Loop
The simulation runs at ~60 FPS using requestAnimationFrame:
- Each frame executes ~267,000 CPU cycles (16MHz / 60fps)
- Port listeners fire when PORTB/C/D registers change
- PinManager maps Arduino pins to components (e.g., pin 13 → LED_BUILTIN)
5. State Management with Zustand
Main stores:
useEditorStore: Multi-file workspace (files[], activeFileId, openFileIds)useSimulatorStore: Simulation state, components, wires, compiled hex, serialMonitorOpenuseProjectStore: Current loaded project metadata (id, slug, name) — used by the.vlxexporter to pick a download filenameuseAuthStore(overlay-only) lives inpro/frontend/src/pro/store/in the velxio-prod repo. Pure OSS builds do not include it.
6. Component-Pin Mapping
Components are connected to Arduino pins via the PinManager:
- PORTB maps to digital pins 8-13 (pin 13 = built-in LED)
- PORTC maps to analog pins A0-A5
- PORTD maps to digital pins 0-7
7. Wire System
Wires are stored as objects with start/end endpoints:
{
id: string
start: { componentId, pinName, x, y }
end: { componentId, pinName, x, y }
color: string
signalType: 'digital' | 'analog' | 'power-vcc' | 'power-gnd'
}Wire positions auto-update when components move via updateWirePositions().
- backend/app/main.py - FastAPI app entry point, CORS, lifespan hooks
- backend/app/api/routes/compile.py - Compilation endpoints (multi-file, sync + async)
- backend/app/api/routes/compile_chip.py - Custom-chip WASM compile
- backend/app/api/routes/libraries.py - arduino-cli library search/install proxy
- backend/app/api/routes/simulation.py - WebSocket bridge to QEMU workers
- backend/app/api/routes/iot_gateway.py - HTTP proxy for ESP32 web servers
- backend/app/services/arduino_cli.py - arduino-cli wrapper
- backend/app/services/espidf_compiler.py - ESP-IDF compile wrapper
- backend/app/core/config.py - Minimal Settings (FRONTEND_URL only)
- backend/app/core/hooks.py - Extension hooks (record_compile, get_current_user_id, lifespan_startup) that the velxio-prod overlay fills in. OSS-default = no-op.
Removed in the OSS/pro split (Phase 1-4): auth.py, projects.py, admin.py, metrics.py, models/, schemas/, services/metrics.py, services/odoo_mail.py, services/project_files.py, database/session.py, core/dependencies.py, core/security.py, utils/{geo,slug,boards}.py. All of these live in velxio-prod's private overlay and are COPYed onto the image at Docker build time when deploying velxio.dev.
- frontend/src/App.tsx - Main app component, routing (with overlay route injection via
useProRoutes) - frontend/src/lib/proRoutes.ts - Registry for routes the overlay registers at runtime
- frontend/src/lib/proSession.ts - Optional session-check hook installed by the overlay
- frontend/src/lib/proSaveAction.ts - Save-button registry. Default = download
.vlx; overlay overrides with SaveProjectModal. - frontend/src/utils/vlxFile.ts - Portable project export/import (no server needed)
- frontend/src/store/useEditorStore.ts - Multi-file workspace state
- frontend/src/store/useSimulatorStore.ts - Simulation state, components, wires
- frontend/src/store/useProjectStore.ts - Current loaded project metadata
- frontend/src/components/editor/CodeEditor.tsx - Monaco editor (key={activeFileId} for per-file undo history)
- frontend/src/components/editor/EditorToolbar.tsx - Compile/Run/Stop buttons (reads files[], not code)
- frontend/src/components/editor/FileExplorer.tsx - Sidebar file list with SVG icons, rename, delete, save button
- frontend/src/components/editor/FileTabs.tsx - Open file tabs with unsaved-changes indicator and close dialog
- frontend/src/components/layout/AppHeader.tsx - Top header (no Save button — moved to FileExplorer)
- frontend/src/components/layout/SaveProjectModal.tsx - Save/update project (reads files[], uses sketch.ino content)
- frontend/src/components/layout/LoginPromptModal.tsx - Prompt anon users
- frontend/src/simulation/AVRSimulator.ts - AVR8 CPU emulator wrapper
- frontend/src/simulation/PinManager.ts - Maps Arduino pins to components
- frontend/src/utils/hexParser.ts - Intel HEX format parser
- frontend/src/components/simulator/SimulatorCanvas.tsx - Canvas + Serial button next to board selector
- frontend/src/pages/EditorPage.tsx - Main editor layout (resizable file explorer + panels)
- frontend/src/pages/LoginPage.tsx
- frontend/src/pages/RegisterPage.tsx
- frontend/src/pages/UserProfilePage.tsx - Profile with project grid
- frontend/src/pages/ProjectPage.tsx - Loads project into editor
frontend/index.html— Full SEO meta tags, OG, Twitter Card, JSON-LD. Domain ishttps://velxio.dev— update if domain changes.frontend/public/favicon.svg— SVG chip favicon (scales to all sizes)frontend/public/og-image.svg— 1200×630 social preview image (OG/Twitter). Export as PNG for max compatibility.frontend/public/robots.txt— Allow all crawlers, points to sitemapfrontend/public/sitemap.xml— All public routes with prioritiesfrontend/public/manifest.webmanifest— PWA manifest, theme color#007acc
- Dockerfile.standalone - Multi-stage Docker build
- .github/workflows/docker-publish.yml - Publishes to GHCR + Docker Hub on push to master
The simulation must call both avrInstruction() and cpu.tick() in the execution loop:
avrInstruction(this.cpu); // Execute the AVR instruction
this.cpu.tick(); // Update peripheral timers and cyclesPort listeners in AVRSimulator.ts are attached to AVRIOPort instances, NOT directly to CPU registers:
this.portB!.addListener((value, oldValue) => {
// value is the PORTB register value (0-255)
// Check individual pins: this.portB!.pinState(5) for pin 13
});Arduino compilation produces Intel HEX format. The parser in hexParser.ts:
- Parses lines starting with
: - Extracts address, record type, and data bytes
- Returns a
Uint8Arrayof program bytes - AVRSimulator converts this to
Uint16Array(16-bit words, little-endian)
To add a component to the simulation:
- Add it to the canvas in SimulatorCanvas.tsx
- Register a pin change callback in PinManager
- Update component state when pin changes
Backend allows specific Vite dev ports (5173-5175). Update backend/app/main.py if using different ports.
Wokwi elements are Web Components. React wrappers declare custom elements:
declare global {
namespace JSX {
interface IntrinsicElements {
'wokwi-led': any;
}
}
}The wire system reads pin coordinates via element.pinInfo from the rendered
DOM node (frontend/src/utils/pinPositionCalculator.ts:38). This only
works for real DOM custom elements (Web Components) — a plain React <svg>
component has no pinInfo, so every wire endpoint silently falls back to
(0, 0) of the board and visually attaches to the corner instead of the
pin. The user has reported this exact symptom multiple times.
Rule: any board or component that needs wire connections must be a Web
Component (class Foo extends HTMLElement) with a pinInfo getter. The
React .tsx file is a thin wrapper.
Reference implementations:
frontend/src/components/velxio-components/Esp32Element.ts(board)frontend/src/components/velxio-components/PiPicoWElement.ts(board)frontend/src/components/velxio-components/Attiny85Element.ts(board)frontend/src/components/velxio-components/Bmp280Element.ts(component)
Required shape:
class FooElement extends HTMLElement {
constructor() { super(); this.attachShadow({ mode: 'open' }); }
connectedCallback() { this.render(); }
get pinInfo() {
// Pin tip coordinates in CSS pixels relative to element top-left.
// `name` must match what examples reference in wires.
return [
{ name: 'GP0', x: 6, y: 24, description: 'UART0 TX' },
// …
];
}
private render() { /* shadowRoot.innerHTML = ... */ }
}
if (!customElements.get('velxio-foo')) {
customElements.define('velxio-foo', FooElement);
}The .tsx wrapper:
import './FooElement';
declare global {
namespace JSX { interface IntrinsicElements { 'velxio-foo': any; } }
}
export const Foo = ({ id, x, y }: Props) => (
<velxio-foo id={id} style={{ position: 'absolute', left: x, top: y }} />
);Verification before claiming a board/component is done: load an example
that wires to it, confirm wires terminate on the pin tips (not the corner),
and add the pin coords to BoardOnCanvas.tsx's BOARD_SIZE table if it's a
board.
frontend/public/components-metadata.json is produced by
scripts/generate-component-metadata.ts. Direct edits get wiped the
next time the generator runs (which happens on every third-party update,
plus anyone who runs npm run generate:metadata from frontend/).
For Velxio-native components that don't exist in wokwi-elements (custom
chips, ePaper panels, logic gates, voltmeters, …) add the entry to
scripts/component-overrides.json under the _customComponents
array. The generator copies them verbatim into the output and they
survive every regeneration.
For wokwi-elements-derived components that need a richer UI control
(e.g. LED color → dropdown, SSD1306 protocol → I2C/SPI selector), add a
keyed entry under the same file with properties + defaultValues
patches (see docs/wiki/component-metadata-generator.md).
To regenerate after editing the override file:
cd frontend
npm run generate:metadata(The script needs tsx and typescript resolvable; the npm script in
frontend/package.json:8 is the supported entry point — if it errors
with "Cannot find module 'typescript'", run with
NODE_PATH="$PWD/frontend/node_modules" npx tsx scripts/generate-component-metadata.ts
from the repo root.)
There are known pre-existing TS errors that do NOT block the app from running:
wokwi-elementsJSX custom element types (wokwi-led,wokwi-arduino-uno, etc.)@monaco-editor/reacttype compatibility with React 19- Test mock type mismatches in
AVRSimulator.test.ts
Do not fix these unless explicitly asked. They are suppressed in Docker builds by using build:docker which runs vite build only (no tsc -b). Local npm run build runs tsc -b and will show these errors.
Dockerfile.standalone does NOT clone any wokwi-* repos. The frontend stage
just does COPY frontend/ scripts/ then npm install && npm run build:docker,
which pulls @wokwi/elements, avr8js, rp2040js from npm. Board SVGs live
in frontend/public/boards/, component SVGs in frontend/public/component-svgs/,
and components-metadata.json is committed.
The frontend-tests CI workflow only clones wokwi-elements (for the
metadata staleness check), not the other two.
- RP2040 board manager: arduino-cli needs the earlephilhower URL before
rp2040:rp2040install:arduino-cli config add board_manager.additional_urls \ https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
Test compilation directly:
cd backend
python test_compilation.pyVitest is configured. Run tests:
cd frontend
npm test- Check if wokwi-elements has the component — either browse
https://github.com/wokwi/wokwi-elements or
ls third-party/wokwi-elements/src/if the optional clone is present - Create React wrapper in
frontend/src/components/components-wokwi/ - Add component type to
useSimulatorStoreinterface - Update SimulatorCanvas to render the component
- Register pin callbacks in PinManager if interactive
- Create route in
backend/app/api/routes/ - Include router in
backend/app/main.py - Add corresponding service in
backend/app/services/if needed - Create API client function in
frontend/src/services/
Common issues:
- LED doesn't blink: Check port listeners are firing (console logs), verify pin mapping
- Compilation fails: Check arduino-cli is in PATH, verify
arduino:avrcore is installed - CPU stuck at PC=0: Ensure
avrInstruction()is being called in execution loop - Wire positions wrong: Check
calculatePinPosition()uses correct component coordinates
Enable verbose logging:
- AVRSimulator logs port changes and CPU state every 60 frames
- Backend logs all compilation steps and arduino-cli output
Implemented:
- Full Arduino code editing with Monaco Editor
- Multi-file workspace — create, rename, delete, open/close tabs, unsaved-changes indicator
- Compilation via arduino-cli to .hex files (multi-file sketch support)
- Real AVR8 emulation with avr8js
- RP2040 emulation with rp2040js
- Pin state tracking and component updates
- Dynamic component system with 48+ wokwi-elements components
- Component picker modal with search and categories
- Component property dialog (single-click interaction)
- Component rotation (90° increments)
- Wire creation and rendering (orthogonal routing)
- Segment-based wire editing (drag segments perpendicular to orientation)
- Real-time wire preview with grid snapping (20px)
- Pin overlay system for wire connections
- Serial Monitor with baud rate detection and send
- ILI9341 TFT display simulation
- Library Manager (install/search arduino libraries)
- Example projects gallery
- Portable project persistence:
.vlxfile export/import — single-file JSON snapshot of the whole workspace, no server, no DB - Resizable file explorer panel (drag handle, collapse toggle)
- Docker standalone image published to GHCR + Docker Hub
- OSS / pro split: auth, accounts, public profiles, admin panel, server-side project URLs and analytics live in the private velxio-prod overlay that runs velxio.dev. OSS is single-user, anonymous, fully self-hostable.
In Progress:
- Functional wire connections (electrical signal routing)
- Wire validation and error handling
Electrical Simulation (Phase 8 — behind ⚡ toggle):
- ngspice-WASM engine via
eecircuit-engine(lazy-loaded, ~39 MB chunk). - Entry:
frontend/src/simulation/spice/SpiceEngine.lazy.ts - NetlistBuilder:
frontend/src/simulation/spice/NetlistBuilder.ts— Union-Find onwires[]→ SPICE cards viacomponentToSpice.ts. - Store:
useElectricalStore(separate fromuseSimulatorStore; feature-flagged). - UI:
<ElectricalModeToggle />in toolbar,<ElectricalOverlay />on canvas. - Probes:
instr-voltmeter,instr-ammetermetadata IDs. - Build-time flag:
VITE_ELECTRICAL_SIM=falseto disable completely. - Tests:
frontend/src/__tests__/spice-*.test.ts,netlist-builder.test.ts,component-to-spice.test.ts,instruments.test.ts(39+ tests). - Reference sandbox:
test/test_circuit/(47 tests proving the approach). - Docs:
docs/wiki/circuit-emulation.md(implementation details),docs/wiki/electrical-simulation-user-guide.md(user-facing).
Planned:
- Undo/redo functionality
- More boards (ESP32, Arduino Mega, Arduino Nano)
- Export/Import projects as files
- Main README: README.md
- Architecture Details: docs/ARCHITECTURE.md
- Wokwi Elements Repo: https://github.com/wokwi/wokwi-elements
- AVR8js Repo: https://github.com/wokwi/avr8js
- Arduino CLI Docs: https://arduino.github.io/arduino-cli/