Export a project to .zip, start a new project, import the zip: the circuit comes back and the board does not. Two reporters; the second noticed it "only works with wokwi components", which is the shape of it — the passives are real Wokwi part types and survive, the board was not one. The bug was in the EXPORT, not the import. wokwiZip knew four boards and wrote `wokwi-arduino-uno`/`uno` for anything else, while the connections kept the board's real canvas id. The attached repro says so in six lines: parts declare an `uno`, connections name `esp32`. Of those four, only two round-tripped. 'arduino-mega' was missing from the export's board-id test, so a Mega's wires named `arduino-mega` while its part was `mega`. 'raspberry-pi-pico' was tested against 'nano-rp2040', an id nothing has been issued since boards became instances, and the import mapped it back to the same dead string. Nobody noticed because the module had no tests. "Velxio has four boards" was written down five times: two tables in the export, one in the import, a hand-copied union in the import dispatcher, and BoardType in the store — which setBoardType already ignored, casting to BoardKind to do its job. All five are gone, plus BOARD_FQBN and BOARD_LABELS, two more from the same era that nothing imported. A board may claim a Wokwi part type only when a Wokwi element draws it. That is not a preference: connections carry PIN NAMES, and the names a wire carries are whatever the element on the canvas calls its pins. Those four render through wokwi-elements, so an Uno wire says `GND.2`, `A4`, `5V` and the file opens over there. Every other board renders through a velxio-* element with our names — ours calls a pin `34` where Wokwi's esp32-devkit-v1 calls it `D34` — so claiming a Wokwi board for one would produce a file that loads in Wokwi and attaches every wire to a pin that does not exist. They travel as `board-velxio-<kind>`, which also round-trips the overlay's kinds, runtime strings this module cannot enumerate. Nothing is coerced any more. An unrecognised diagram returns a null board and says so instead of answering 'arduino-uno'; a second board in the diagram is reported rather than dropped; wires naming a part the diagram lacks are called out, which is what the reporter's own file now does when you import it. Two more halves surfaced only by driving the real editor: The board was still missing after import, because setBoardType re-kinds the ACTIVE board and a new project has none — nothing to re-kind, so the circuit arrived without its chip exactly as reported. The importers now add a board when there is none, activate it (addBoard promotes but does not sync the flat legacy fields), and retarget the wires to the id the board actually got. And the export read `boardType`, the flat mirror, which only tracks the active board through setActiveBoardId/setBoardType and reports every Raspberry Pi as an 'arduino-uno' on purpose. It now reads the board itself, so the kind and the canvas id come off one object and cannot disagree. Adversarial review of the fix caught six more, every one reproduced. The worst was not a loss but a corruption, and it was mine: a canvas with two boards wrote BOTH boards' wires against the exported board's part id, because for every non-Arduino family that id IS the first-of-kind canvas id. The second board's components came back attached to the first board's pins — a different circuit, from a file that was internally consistent, so nothing complained. My first attempt at a dangling-wire warning could not see it either: it allowed the board KIND as a defined id, which is precisely the id a second board writes. The check now reads the raw diagram, the export leaves other boards' wires out rather than mis-addressing them, and it says what did not travel. The rest: isKnownBoardKind used `in`, so 'toString' and '__proto__' passed a guard whose input comes from files people send each other; the importer's extension list admitted only the Arduino four, so a MicroPython project came back with an empty editor; the exporter never wrote libraries.txt though the importer has always read it and offered to install what it finds; and the unknown-board message claimed the circuit landed on the current board without retargeting its wires there. 41 tests, over every board kind, which is the assertion this module never had: it was written when Velxio had four boards and nothing failed when it grew to thirty. Verified in a real browser through the reporter's flow — build the circuit, File > Export, new project, import — for arduino-uno (unchanged), arduino-mega and raspberry-pi-pico (both previously broken) and esp32/esp32-s3. Before and after are identical in all five. Still lossy, and out of scope here: a .zip carries one board, so a multi-board project must be saved as .vlx to survive whole. Wire waypoints, one shade of green, and board-level state (boardOptions, SPIFFS, languageMode) do not travel either. |
||
|---|---|---|
| .. | ||
| public | ||
| scripts | ||
| src | ||
| .env.production | ||
| .gitignore | ||
| .prettierignore | ||
| .prettierrc.json | ||
| Dockerfile | ||
| README.md | ||
| eslint.config.js | ||
| esp32 plan.md | ||
| index.html | ||
| nginx.conf | ||
| package.json | ||
| tsconfig.app.json | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
| vitest.config.ts | ||
README.md
Arduino Emulator - Frontend
React + TypeScript + Vite frontend for the Arduino emulator with visual simulator and code editor.
Features
- Monaco Code Editor - Full VSCode-like Arduino code editing experience
- Dynamic Component System - 48+ wokwi-elements components with search and categories
- Visual Simulator Canvas - Interactive drag-and-drop circuit builder
- Component Property Dialog - Single-click component interaction (rotate, delete, view pins)
- Segment-Based Wire Editing - Drag wire segments perpendicular to orientation (like Wokwi)
- Real AVR8 Emulation - Actual ATmega328p emulation using avr8js
- Pin Management - Automatic pin mapping and state synchronization
- Grid Snapping - 20px grid alignment for clean circuit layouts
Tech Stack
- React 18 - UI framework
- TypeScript - Static typing
- Vite 5 - Build tool and dev server
- Monaco Editor - Code editor (VSCode engine)
- Zustand - State management
- Axios - HTTP client for backend API
- avr8js - AVR8 CPU emulator (npm package)
- @wokwi/elements - Electronic web components (npm package)
Development
Prerequisites
- Node.js 18+
- Backend running at http://localhost:8001
Install Dependencies
npm install
Run Development Server
npm run dev
The app will be available at http://localhost:5173
Build for Production
npm run build
Output will be in the dist/ directory.
Lint
npm run lint
Project Structure
frontend/
├── src/
│ ├── components/
│ │ ├── velxio-components/ # React wrappers for wokwi-elements + Velxio-original parts
│ │ ├── editor/ # Monaco Editor components
│ │ │ ├── CodeEditor.tsx
│ │ │ └── EditorToolbar.tsx
│ │ └── simulator/ # Simulation canvas components
│ │ ├── SimulatorCanvas.tsx
│ │ ├── WireLayer.tsx
│ │ ├── WireRenderer.tsx
│ │ ├── PinOverlay.tsx
│ │ ├── ComponentPropertyDialog.tsx
│ │ ├── ComponentPickerModal.tsx
│ │ └── ComponentPalette.tsx
│ ├── simulation/
│ │ ├── AVRSimulator.ts # AVR8 CPU wrapper
│ │ └── PinManager.ts # Pin mapping and callbacks
│ ├── store/
│ │ ├── useEditorStore.ts # Code editor state
│ │ └── useSimulatorStore.ts # Simulation state
│ ├── services/
│ │ ├── api.ts # Backend API client
│ │ └── ComponentRegistry.ts # Component metadata
│ ├── types/ # TypeScript definitions
│ ├── utils/
│ │ ├── hexParser.ts # Intel HEX parser
│ │ ├── wirePathGenerator.ts # Wire SVG path generation
│ │ └── wireSegments.ts # Segment-based wire editing
│ ├── App.tsx # Main app component
│ └── main.tsx # Entry point
├── public/ # Static assets
├── vite.config.ts # Vite configuration
└── package.json
Key Architecture Patterns
State Management (Zustand)
Two main stores:
- useEditorStore - Code content, theme, compilation state
- useSimulatorStore - Simulation running state, components, wires, compiled hex
Wokwi Libraries
@wokwi/elements, avr8js and rp2040js are regular npm dependencies resolved
from node_modules like any other package — no local clones required.
AVR Simulation Loop
- Runs at ~60 FPS using
requestAnimationFrame - Executes ~267,000 CPU cycles per frame (16MHz / 60fps)
- Port listeners fire when GPIO registers change
- PinManager routes pin states to component callbacks
Component System
Components are Web Components from wokwi-elements:
- React wrappers in
velxio-components/ - Dynamic loading via ComponentRegistry
- Pin info extracted from component metadata
- State updates via refs and callbacks
Wire Editing System
Segment-based editing (like Wokwi):
- Wires consist of orthogonal segments (horizontal/vertical)
- Drag segments perpendicular to orientation:
- Horizontal segments: move up/down (ns-resize)
- Vertical segments: move left/right (ew-resize)
- Local preview state during drag (requestAnimationFrame)
- Store update only on mouse up with grid snapping (20px)
Performance Optimizations
requestAnimationFramefor smooth wire dragging- Local state for real-time previews
- Memoized path generation and segment computation
- Store updates batched at interaction completion
API Integration
Backend endpoints (http://localhost:8001):
POST /api/compile- Compile Arduino code to .hexGET /api/compile/status/{task_id}- Check compilation statusGET /api/compile/download/{filename}- Download compiled .hex
See backend documentation for API details.
Component Development
Adding a New Component Type
-
Check if wokwi-elements has the component:
ls ../third-party/wokwi-elements/src/ -
Create React wrapper in
src/components/velxio-components/:import React, { useRef, useEffect } from 'react'; export const WokwiMyComponent: React.FC<Props> = ({ ... }) => { const elementRef = useRef<any>(null); useEffect(() => { if (elementRef.current) { elementRef.current.setAttribute('prop', value); } }, [value]); return <wokwi-my-component ref={elementRef} />; }; -
Add to ComponentRegistry metadata
-
Use in SimulatorCanvas or make available in ComponentPalette
Troubleshooting
Monaco Editor Not Loading
- Check if
monaco-editoris installed - Verify Vite worker configuration in vite.config.ts
Components Not Rendering
- Ensure wokwi-elements is built:
cd ../third-party/wokwi-elements && npm run build - Check browser console for Web Component registration errors
- Verify Vite alias paths in vite.config.ts
Wire Editing Performance Issues
- Ensure
requestAnimationFrameis being used - Check that store updates only happen on mouse up, not during drag
- Verify no unnecessary re-renders with React DevTools
Pin Alignment Issues
- Pin coordinates from wokwi-elements are in CSS pixels
- Do NOT multiply by MM_TO_PX conversion factor
- Verify component position + pin offset calculation
Compilation Fails
- Check backend is running at http://localhost:8001
- Verify arduino-cli is installed and
arduino:avrcore is available - Check CORS configuration in backend