The interactive web-based UI for the OpenHW Studio electronics simulation platform. Built with React + Vite, it renders a drag-and-drop circuit editor, streams live simulation state from the emulator, and drives Wokwi web components in real time.
- Overview
- Tech Stack
- Project Structure
- Pages
- Key Features
- Offline & Storage Features
- Setup & Running Locally
- Environment & Dependencies
- How It Works
OpenHW Studio Frontend is the visual client of the simulator platform. It allows users to:
- Design circuits by placing and wiring components on a canvas
- Write and edit Arduino C++ code in a built-in syntax-highlighted editor
- Compile and run simulations powered by the backend compiler and the in-browser Web Worker emulator
- Watch simulation output in real time (LEDs blinking, NeoPixels lighting up, servo movement, etc.)
- Save and load projects locally — guests included — using IndexedDB
It connects to one backend service:
- Compiler Backend (
http://localhost:5001) — compiles C++ code to.hex
The simulation itself runs entirely in the browser via a Web Worker powered by avr8js.
| Technology | Purpose |
|---|---|
| React 18 | UI framework |
| Vite 5 | Build tool and dev server |
| React Router DOM | Client-side routing |
| Axios | HTTP requests to compiler backend |
| avr8js | AVR CPU emulation (used in Web Worker) |
| intel-hex | Parsing .hex firmware files |
| Prism.js | Syntax highlighting in code editor |
| react-simple-code-editor | Embedded code editor component |
| @react-oauth/google | Google OAuth login |
| jwt-decode | Decoding JWT auth tokens |
| @openhw/emulator | Shared component definitions (workspace package) |
| JSZip | Client-side ZIP extraction for custom components |
| @babel/standalone | In-browser transpilation of custom component TypeScript/JSX |
| IndexedDB (native) | Project persistence, hex cache, offline upload queue |
| Service Worker (native) | App shell caching for offline support |
OpenHW-studio-frontend/
├── index.html # App entry HTML
├── vite.config.js # Vite configuration
├── package.json
└── src/
├── main.jsx # React app bootstrap + Service Worker registration
├── App.jsx # Route definitions
├── index.css # Global styles
├── pages/
│ ├── LandingPage.jsx # Public home/landing page
│ ├── LoginPage.jsx # Google OAuth login
│ ├── RoleSelectPage.jsx # Student / Teacher role selection
│ ├── SimulatorPage.jsx # Main circuit editor + simulation runner
│ ├── admin/ # Admin Portal (Login, Landing, & Dashboard)
│ ├── StudentDashboard.jsx
│ └── TeacherDashboard.jsx
├── context/
│ └── AuthContext.jsx # Global authentication state
├── services/
│ ├── authService.js # Login, logout, token management (localStorage)
│ ├── simulatorService.js # POST /api/compile and component API calls
│ ├── offlineCache.js # IndexedDB: compiled hex cache + ZIP upload queue
│ └── projectStore.js # IndexedDB: full project CRUD (save/load/list/delete)
├── worker/
│ ├── simulation.worker.ts # Web Worker entry point
│ └── execute.ts # AVR CPU execution loop inside worker
└── components/ # Shared UI components
Static files:
public/
├── sw.js # Service Worker (app shell caching, offline routing)
└── _redirects # Deployment redirect rules
The core of the application. Responsibilities include:
- Circuit Canvas — drag, drop, and wire Wokwi components
- Resizable Explorer — The file explorer panel is resizable for better code editing space
- Tabbed Interface — Right panel features high-visibility interactive tabs for Editor, Explorer, and Console
- Code Editor — write Arduino sketches with syntax highlighting;
diagram.jsonis protected as read-only - Undo/Redo — Quick toolbar actions for circuit design with updated iconography
- Run/Stop — triggers compilation →
.hexdelivery → Web WorkerSTARTmessage - Live State Rendering — receives
{ type: "state", pins: {...} }at 60 FPS and updates component visual attributes (LEDs, NeoPixels, etc.) - Component Registry — maps component type names to their imported index definitions from
@openhw/emulator - Project Save/Load — auto-saves to IndexedDB every 2.5 s; "My Projects" modal for named saves; auto-loads last project on mount
- Offline resilience — hex cache survives page refresh; ZIP uploads queue while offline
Google OAuth 2.0 login page. Decodes JWT and stores user info in AuthContext.
A powerful administrative hub for platform maintenance:
- 3-Column Management: Independent scrollable panels for Libraries, Pending Submissions, and Installed Components.
- Review Workflow: Admins can check submissions with live Transpile feedback, download source ZIPs, or open them in a live Simulator "Test" tab.
- Real-time Actions: Approval instantly moves components to the backend; rejection removes specific submissions (unique per upload).
- Library Manager: Search and uninstall system-level C++ libraries for the Arduino compiler.
Public-facing landing page describing the platform.
Role-specific dashboards shown after login.
The Web Worker runs the AVR CPU and posts state messages back to the main thread every frame. The frontend maps pin voltages to DOM attribute changes on Wokwi custom HTML elements.
{ "type": "state", "pins": { "D13": true, "D6": false }, "neopixels": [...] }- Wires NeoPixel components with
GND,VCC,DIN,DOUTpins - Sends matrix topology (component ID, Arduino pin, size) in the Web Worker
STARTmessage - Calls
element.setPixel(row, col, {r, g, b})directly on the Wokwi DOM element
A native high-performance <canvas> rendering engine tab traces simulated logic and analog signals. Users can dynamically specify which pins to track.
A built-in terminal streams AVRUSART traffic bidirectionally into the .hex loop.
The Arduino Uno Reset Button is fully interactive inside the workspace SVG visualizer, triggering a targeted runner.cpu.reset() reboot in the Web Worker.
AVR simulation runs entirely in-browser via src/worker/execute.ts inside a Web Worker, keeping the UI thread completely unblocked.
- Each Pico board folder now includes both
<boardId>.inoandmain.pystarter files. - File menu supports Disable file / Enable file by renaming with
.disabledsuffix. - Disabled files are excluded from compile and MicroPython source selection.
- If backend RP2040 Arduino core is missing,
.inocompile failures automatically fall back to MicroPython UF2 +main.py.
- Project files are normalized and deduplicated when loading saved projects to prevent duplicate file entries after refresh.
The simulator polls the backend every 12 seconds for newly approved community components:
- Dynamic Injection: New components are transpiled and injected into the registry and palette without a page refresh.
- Live Deletion: If a component is uninstalled from the admin panel, it is purged from all active simulator sessions automatically.
- Admin "Test" Mode: An isolated preview mechanism uses
sessionStorageto test pending components before approval.
- Google OAuth → JWT stored in context
- Role selection (Student / Teacher / Admin) → role-specific entry points
- Protected routes via
AuthContextandProtectedRoutecomponents
All storage features use the browser-native IndexedDB API and Service Worker API. No extra npm packages are required, and no backend changes are needed.
Every circuit is automatically saved to a local IndexedDB database every 2.5 seconds after any change. This works for both authenticated users and guest users with no login required.
| User type | Owner key | What happens |
|---|---|---|
| Guest (no login) | 'guest' |
Projects saved locally, visible under "My Projects" on every visit |
| Authenticated user | user.email |
Projects saved locally, scoped to that email |
Buttons added to the header:
- Save — opens a name dialog; pressing Enter or clicking Save commits the name
- My Projects — opens a modal listing all saved projects (name, board, components count, last-saved time) with Load and Delete actions
- New — starts a blank canvas (current project is preserved in IDB)
- Project name chip — shows the current project name in the header; click to rename
On page load, the most-recently saved project for the current user (or guest) is automatically restored.
Compiled .hex results are saved to IndexedDB after every successful compile. On subsequent runs:
- Check in-memory
lastCompiledRef(fastest — same session) - Check IndexedDB cache (survives page refresh and offline)
- Compile via
POST /api/compile(requires network)
This means: run your simulation offline after compiling at least once while online.
If you upload a custom component ZIP while offline:
- The component is injected into the local registry immediately (usable right away)
- The backend submission is queued in IndexedDB
- When the internet is restored, the queue is automatically drained
Cached on first load. Strategies:
| Request | Strategy |
|---|---|
Navigation (/, any route) |
Network-first → fallback to cached index.html |
| JS / CSS / images | Stale-while-revalidate (instant load + background update) |
| CDN scripts (wokwi-elements) | Stale-while-revalidate |
/api/* |
Network-only (never cached — hex caching is at the app layer) |
See OFFLINE_AND_STORAGE.md for full technical details.
- Node.js 18+
- npm 9+
- The Compiler Backend running at
http://localhost:5001 - For Pico
.inocompilation, backend must haverp2040:rp2040core installed.
cd OpenHW-studio-frontend
npm installDuring local development, you will want the frontend to immediately see changes you make to the emulator source code, without having to push those changes to GitHub first.
We achieve this using Vite Resolve Aliases, which tell the frontend to use your local emulator repository folder. This is controlled by the VITE_EMULATOR_PATH variable in your .env file and bypasses the need for npm link.
Set the path to your own emulator repo name. It can be relative or absolute, as long as it points at the emulator repository root.
To set up your local development:
- Ensure your
.envfile has the correct path:VITE_EMULATOR_PATH=../path-to-openhw-studio-emulator
- Restart the Vite dev server. The
@openhw/emulatorpackage will now point directly to your local source.
Note: You can still use npm link if preferred, but the environment variable approach is more portable across different machines.
For production builds (like on Vercel), the VITE_EMULATOR_PATH variable should be left unset. The vite.config.js is designed to automatically fallback to the @openhw/emulator package installed in node_modules (fetched from GitHub) if the local path is not found.
Note: Once deployed to Vercel/Netlify, these local symlinks will be ignored and the remote server will correctly fetch the package directly from GitHub.
npm run devThe app will be available at http://localhost:5173
npm run buildnpm run previewThe frontend uses Vite environment variables. Create a .env file in the project directory:
| Variable | Description | Default |
|---|---|---|
VITE_GOOGLE_CLIENT_ID |
Google OAuth 2.0 Client ID | — |
VITE_API_BASE_URL |
Base URL for the Backend API | http://localhost:5001/api |
VITE_EXAMPLES_BASE_URL |
Base URL for example assets served by the backend EXAMPLES_PATH route |
http://localhost:5001/examples |
VITE_EMULATOR_PATH |
Local path to the emulator repository root used by Vite aliasing | ../path-to-openhw-studio-emulator |
VITE_ADMIN_EMAILS |
Comma-separated list of admin emails | — |
VITE_GOOGLE_CLIENT_ID=your_id.apps.googleusercontent.com
VITE_API_BASE_URL=http://localhost:5001/api
VITE_EXAMPLES_BASE_URL=http://localhost:5001/examples
VITE_ADMIN_EMAILS=admin@example.com,user@example.com
VITE_EMULATOR_PATH=../path-to-openhw-studio-emulatorUser writes C++ code
│
▼
POST /api/compile ──► Compiler Backend (port 5001)
│
Returns .hex file
│ also cached to IndexedDB
▼
Frontend sends START + .hex + wiring topology
│
Web Worker (browser)
│
Runs AVR CPU at 16 MHz in-browser
Streams pin states at 60 FPS
│
▼
Frontend updates Wokwi component DOM attributes
(LEDs, NeoPixels, Servo, etc.)
Part of the OpenHW Studio platform. See also: openhw-studio-backend and openhw-studio-emulator.