velxio/frontend
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
..
public feat: add support for UC8159c (ACeP 7-colour) display 2026-04-30 00:27:40 -03:00
scripts refactor: rename components and update prefixes to 'velxio-' for consistency 2026-04-21 16:45:45 -03:00
src feat: persist multi-board projects + add auto-save 2026-05-01 13:43:33 -03:00
.env.production
.gitignore
.prettierignore refactor: rename components and update prefixes to 'velxio-' for consistency 2026-04-21 16:45:45 -03:00
.prettierrc.json refactor: rename components and update prefixes to 'velxio-' for consistency 2026-04-21 16:45:45 -03:00
Dockerfile
README.md refactor: rename components and update prefixes to 'velxio-' for consistency 2026-04-21 16:45:45 -03:00
eslint.config.js style: update ESLint rules and clean up code formatting across multiple files 2026-04-21 17:14:27 -03:00
esp32 plan.md
index.html SEO improvements 2026-04-30 00:27:39 -03:00
nginx.conf
package-lock.json feat(metrics): add usage analytics dashboard with country tracking 2026-04-25 19:47:40 -03:00
package.json feat(metrics): add usage analytics dashboard with country tracking 2026-04-25 19:47:40 -03:00
tsconfig.app.json
tsconfig.json
tsconfig.node.json
vite.config.ts feat: enhance electrical simulation and testing framework 2026-04-20 16:38:31 -03: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 (local clone)
  • @wokwi/elements - Electronic web components (local clone)

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

Local Wokwi Libraries

Vite aliases point to local clones instead of npm packages:

resolve: {
  alias: {
    'avr8js': path.resolve(__dirname, '../wokwi-libs/avr8js/dist/esm'),
    '@wokwi/elements': path.resolve(__dirname, '../wokwi-libs/wokwi-elements/dist/esm'),
  },
}

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 ../wokwi-libs/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 ../wokwi-libs/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