velxio/frontend
David Montero 8f148e836d fix(sim): a net is a set of pins, and a part's own line belongs to the part
Hardening pass on 57325aa, which fixed the reported HC-SR04 divider but did it
by returning the first answer any branch produced. Five topologies people
really draw break under that rule, and each one now has a test that fails on
57325aa and passes here:

  - a ground tie point almost always carries something else (a button's
    pull-down). Reaching a rail has to END the walk, or a grounded pin resolves
    to whatever GPIO that pull-down hangs off. Same through a breadboard rail;
  - two custom chips on one wire stopped sharing a chipbus net key, because a
    nested frame answered with its own neighbour's per-endpoint key before the
    root could mint the net-canonical one;
  - a chip one hop away ended the search before a real GPIO on the same node
    was ever looked at;
  - a node first reached deep, where the hop budget cut it short, was never
    re-walked when a shorter route arrived.

So the walk now models what a net IS. It collects the whole NODE (every pin
joined without crossing a component: the wires, and a breadboard strip's
holes), then judges the set: a driven pad wins outright; a supply pad means the
node IS the rail and the walk stops there; only then does it cross a passive to
the next node, where a rail is the weaker answer that lets a level-shifted
sensor resolve. Wires, components and breadboard groups are indexed per store
array, and a driven pad short-circuits collection — the common case is now
0.08 ms/pin on a 300-wire canvas, better than before this work started, with
the shared-rail worst case at ~2 ms.

Second half: a part that models its own line keeps it, on every board.
connectDigitalInputsToMcu thresholds the solved node into every input pin, and
its `sourcedNets` gate only ever protected part-driven lines by accident — it
holds while no component sits on the net, and stops the moment the user adds
the pull-up or the divider real hardware needs. Then the connector pins the
line an HC-SR04 or a DHT22 is answering on. simulation/partPinOwnership records
what each part drives (implicitly, through the simulator it is handed) and the
connector skips those pins; the ESP32 bridge answers the same question for
sensors the backend or an engine emulates, through a new optional `ownsPin`
seam. Parts SPICE can describe never claim anything, so a mis-wired button
still reads stuck — that property is why spiceDrivenInputs exists.

Also: setSensors merges per FIELD, so the coarser registration path can no
longer drop the finer one's echo_pin; the ESP32 bridge's guard is narrowed to
the single-wire sensor types, since an ePaper's DC pin or a keypad row must
stay drivable; and the Mega and Pico W pin maps get the supply-pad guard the
other boards have, where '5V' parsed as D5 and '3V3' as D3.

Verified in a browser against a local build of this tree, driving the real app:
HC-SR04 and DHT22, direct wiring and level-shifted, on Uno (avr8js), Pico
(rp2040js), ESP32 (esp32js and QEMU) and ESP32-S3 (esp32s3js) — see
scripts/sensor-wiring-matrix.mjs in velxio-prod.
2026-08-23 01:23:41 +02:00
..
public chore(examples): refresh 1 thumb file(s) [auto] 2026-08-22 04:59:24 +02:00
scripts prerender: SSR bodies were never injected; examples gallery renders on the server 2026-08-18 18:10:36 +02:00
src fix(sim): a net is a set of pins, and a part's own line belongs to the part 2026-08-23 01:23:41 +02:00
.env.production
…
.gitignore
…
.prettierignore
…
.prettierrc.json
…
Dockerfile fix(install): unblock self-hosting + drop forced wokwi clones 2026-05-04 00:04:11 -03:00
README.md
…
eslint.config.js
…
esp32 plan.md
…
index.html seo/analytics: real GA attribution, tight robots.txt, no build-date lastmod, GA only on velxio.dev 2026-08-18 16:04:48 +02:00
nginx.conf
…
package.json test: declare `canvas` so the jsdom tests get a real 2D context 2026-08-18 02:05:01 +02:00
tsconfig.app.json
…
tsconfig.json
…
tsconfig.node.json
…
vite.config.ts
…
vitest.config.ts test(config): let overlay tests import the engine packages 2026-08-17 05:43:18 +02:00

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

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:

  1. React wrappers in velxio-components/
  2. Dynamic loading via ComponentRegistry
  3. Pin info extracted from component metadata
  4. 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

  • requestAnimationFrame for 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 .hex
  • GET /api/compile/status/{task_id} - Check compilation status
  • GET /api/compile/download/{filename} - Download compiled .hex

See backend documentation for API details.

Component Development

Adding a New Component Type

  1. Check if wokwi-elements has the component:

    ls ../third-party/wokwi-elements/src/
    
  2. 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} />;
    };
    
  3. Add to ComponentRegistry metadata

  4. Use in SimulatorCanvas or make available in ComponentPalette

Troubleshooting

Monaco Editor Not Loading

  • Check if monaco-editor is 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 requestAnimationFrame is 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:avr core is available
  • Check CORS configuration in backend

References