# Fusible
> **The fuse for your firmware.** An electronic linter and tscircuit project manager in Rust:
> it reads the Circuit JSON generated by [tscircuit](https://tscircuit.com), checks the
> electrical and physical rules of the board, and when a rule fails, **the
> fuse blows**: the process exits with an error and firmware code generation
> never happens.
Fusible unifies hardware and firmware into a single monorepo contract using `fusible.yml`,
wrapping development servers, dark-theme web previewers, artifact exports, and firmware codegen.


## Project status
| 1 (MVP) | CLI, Circuit JSON parser, rules engine, diagnostics | shipped |
| 1.1 | Project manifest (`fusible.yml`), toolchain detection, package manager | shipped |
| 2 | Silicon pin-muxing (`src/silicon/`) for STM32, RP2040, ESP32-S3 | shipped |
| 2 | Firmware codegen (`src/codegen/pins.rs`) for Embassy / Cortex-M | shipped |
| 2.1 | Pure-Rust RS-274X Extended Gerber & Excellon drill generator | shipped |
| 2.2 | Pure-Rust vector documentation exporter (PCB & schematic SVGs) | shipped |
| 2.3 | Native Fusible Studio dev server with dark theme and live SSE reload | shipped |
| 2.4 | Deterministic autorouter and bidirectional KiCad syncer | shipped |
| 2.5 | Fuse-gated 3D enclosure generator on OpenCascade | shipped |
| 2.6 | Multi-board projects: named boards, firmware, and bodies | shipped |
## Prerequisites
For pure Circuit JSON projects, Gerber exports, SVG rendering, and linter checks, Fusible runs with **zero external dependencies**.
To compile React `.tsx` circuits or export 3D STEP models, install `tscircuit` globally or in your workspace:
```sh
# Using bun:
bun add -g tscircuit
# Or using npm:
npm install -g tscircuit
```
Inspect toolchain health anytime with `fusible doctor` (or run `fusible doctor --install` to provision it).
## Installation
```sh
cargo install fusible
```
## Quick start (monorepo)
Initialize a new hardware + firmware project:
```sh
fusible init --name sensor-node
```
This creates a minimal `fusible.yml`:
```yaml
name: sensor-node
boards:
main:
entry: hardware/index.tsx
out_dir: dist
port: 3020
mcu: stm32g431
firmware:
app:
board: main
path: firmware
framework: embassy
pins_rs: firmware/src/pins.rs
build_cmd: cargo build
```
### One project, three worlds
A Fusible project links circuit boards, firmware, and physical bodies. Each section holds named entries, and names are unique across the three, so any command takes one as its target:
```yaml
name: rover
boards:
main: { entry: boards/main/index.tsx, mcu: esp32-s3 }
sensor: { entry: boards/sensor/index.tsx, mcu: rp2040 }
firmware:
main-fw: { board: main, path: firmware/main, pins_rs: firmware/main/src/pins.rs }
sensor-fw: { board: sensor, path: firmware/sensor, pins_rs: firmware/sensor/src/pins.rs }
bodies:
case: { source: bodies/case.step } # drawn in FreeCAD, Fusion, Onshape...
knob: { source: bodies/knob.step, build: python bodies/knob.py } # code-CAD
lid: { generate: enclosure, around: sensor } # Fusible owns and regenerates it
```
- `fusible check` checks every board and reports all violations together. `fusible check sensor` checks one.
- `fusible build sensor-fw` checks the `sensor` board, writes its pin bindings, then builds that firmware.
- `fusible export main --preset fab` writes into `dist/main/fab/`. With several boards, `export` and `dev` need a board name.
- A firmware or generated body must name its board when the project has more than one.
- The single-board form (`hardware:`, `mcu: { chip }`, one `firmware:`, `mechanical:`) still loads. It becomes a board named `main`, a firmware named `firmware`, and a generated body named after the project, which keeps writing `dist/mechanical/<project>.<format>`.
## Commands
### 1. Electrical linter (`check`)
```sh
fusible check # uses fusible.yml entry
fusible check hardware/index.tsx # compiles .tsx on the fly and lints
fusible --input circuit.json # direct Circuit JSON input
fusible check --mcu stm32g431 # validates alternate functions against silicon
```
Compiling `.tsx` through tscircuit stops after 120 seconds. Set `FUSIBLE_BUILD_TIMEOUT_SECS` to change the limit for boards that take longer to route.
Clean circuit (exit code 0):
```
Circuit validated (fuse intact)
```
Rule violated (exit code 1):
```
the fuse blew: 1 electrical violation(s)
[unconnected_net] net "I2C_SDA" has only 1 connection(s) (minimum 2): it is floating
help: every net must connect at least two pads/ports: complete the routing in tscircuit
```
### 2. Fusible Studio dev server (`dev` / `view` / `webview`)
Starts the native dark-theme web previewer powered by an embedded Rust HTTP server and live-reload event streamer:
```sh
fusible dev # launches native Fusible Studio on port 3020
fusible view --port 3030 # custom port
fusible dev --legacy # fallback to legacy tsci dev server
```
Features:
- Instant startup with zero Node runtime required for Circuit JSON.
- Live-reload streaming via Server-Sent Events (SSE) with 150ms debounced filesystem watching.
- Vitrine brutalist dark UI palette (`#000000`, `#131313`, `#353535`, `#86A881`, `#A8B365`) and embedded `Undefined` monospace typography.
- Interactive pan and zoom vector canvas for multi-layer PCB layout (`01 // PCB LAYOUT`) and schematic diagrams (`02 // SCHEMATIC`).
- Interactive Three.js 3D board viewer (`03 // 3D VIEW`) rendering substrate, ENIG gold pads, copper vias, traces, and components with OrbitControls and view presets.
- `BODIES` tab showing the generated base and lid, with lid and explode toggles. The 3D view has an `ENCLOSURE` toggle that draws the shell translucent around the board. The studio builds the model on demand from the first generated body in `fusible.yml` and rebuilds it when the circuit changes. Needs `bun`.
- Real-time fuse status badge (glowing green when intact, pulsing red on blown fuse).
- Diagnostics inspector showing rule violations, severity, and remediation advice.
- PCB layer visibility toggles (top copper, bottom copper, ENIG pads, silkscreen).
### 3. Autorouter (`route`)
Multi-layer A* autorouter on pure Rust spatial R-trees (`rstar`). Nets route one at a time in a fixed order, and each finished trace becomes an obstacle for the next, so routed nets cannot short:
```sh
fusible route # routes entry circuit and writes dist/circuit.routed.json
fusible route --dry-run # verifies all nets can be routed without writing files
fusible route -o dist/routed.json # custom destination
fusible route --clearance 0.2 --layers 2 # custom clearance and layer count
```
When routing congestion prevents a trace from completing, **the fuse blows** (exit code 1) and lists unrouted nets to protect physical fabrication.
### 4. Bidirectional KiCad syncer (`diff` and `sync`)
Bi-directional layout reconciliation between graphical CAD tools (KiCad PCB) and declarative code (`tscircuit`):
```sh
# Compare component placement and report layout drift:
fusible diff path/to/board.kicad_pcb
# Synchronize physical moves and rotations back into manual-edits.json:
fusible sync path/to/board.kicad_pcb --write
# Dry run sync without modifying disk:
fusible sync path/to/board.kicad_pcb --dry-run
```
### 5. Build (`build`)
Evaluates the boards, lints electrical rules, generates typed firmware pin bindings (`pins.rs`), and compiles firmware only if every board keeps the fuse intact:
```sh
fusible build # every board, then every firmware
fusible build main-fw # one firmware and the board it runs on
```
### 6. Fuse-gated deliverables (`export`)
Exports fabrication-ready Gerbers, KiCad projects, SVGs, 3D models (STEP/GLB), or documentation.
All exports are **fuse-gated**: electrical rules are verified first. If a rule fails, the fuse blows and no artifacts are produced.
```sh
# Presets:
fusible export --preset fab # RS-274X Gerbers (.zip) + Circuit JSON via native Rust
fusible export --preset cad # KiCad project (.zip) + 3D STEP (.step)
fusible export --preset docs # Native PCB SVG + Schematic SVG (<5ms)
fusible export --preset all # Generates all export bundles
# Specific format exports:
fusible export -f gerbers -o dist/gerbers.zip
fusible export -f pcb-svg -o dist/pcb.svg
fusible export -f step
fusible export -f kicad_zip
# Bypass verification (for debugging/drafts):
fusible export --preset fab --skip-lint
```
### 7. Physical bodies (`body`)
Builds the bodies of the project. A body is either authored or generated:
- **Authored** (`source:`): a file you made in any CAD tool. Fusible checks that it exists and never overwrites it. With `build:`, Fusible first runs that command from the manifest directory, for code-CAD tools such as build123d, CadQuery, or OpenSCAD.
- **Generated** (`generate: enclosure`): a two-part enclosure (base + lid) around the board outline in Circuit JSON. Fusible owns the file and regenerates it on every run. Generation is fuse-gated like `export`.
```sh
fusible body # every body of the manifest
fusible body case # one body
fusible body sensor # the generated bodies around one board
fusible body -f all # step, stl, glb, and brep into dist/bodies/
fusible body --no-cutouts --no-mounting
```
A board without any body gets a default enclosure, so `fusible body` works without a `bodies:` section. `mechanical`, `case`, and `housing` remain aliases of `body`.
Enclosure details:
- The outline comes from `pcb_board` (`outline` points, or `width` x `height`).
- Each `pcb_hole` gets a screw boss with a pilot hole, and the board sits on the bosses.
- Components that reach the board edge get a wall notch from the board surface up.
- CLI flags override the body settings in `fusible.yml`, which override defaults.
- Requires `bun`. The first run installs `occt-wasm` into `~/.cache/fusible/`. IGES is not supported.
- `fusible dev` previews the first generated body in its `BODIES` tab and as a 3D overlay, without the fuse gate.
### 8. Hardware package management (`add`, `install`, `search`)
Manage tscircuit packages, community footprints, and components without touching npm or tsci directly:
```sh
# Search footprints and components in the tscircuit registry:
fusible search esp32-s3
# Add a community component or registry footprint to hardware/:
fusible add @tsci/seveibar.smd-resistor
# Install all hardware dependencies:
fusible install
```
### 9. Toolchain diagnostics (`doctor`)
Inspects local toolchain health (`bun`, `node`, `npm`, `npx`, `tsci`) and verifies tscircuit runner availability:
```sh
fusible doctor
fusible doctor --install
```
## Exit codes
The codes are part of the contract, so CI can tell failures apart:
| `0` | Fuse intact: circuit validated / command succeeded |
| `1` | Electrical violations: the fuse blew, generation is blocked |
| `2` | Tool or usage error (unreadable/malformed input, bad arguments) |
## Architecture
```
src/
├── main.rs # CLI entry point, exit code mapping
├── cli.rs # Clap command definitions
├── assets/ # Embedded dark-theme web dashboard & Undefined font
├── dev/ # Native Fusible Studio dev server (tiny_http, SSE, notify)
├── router/ # Deterministic multi-layer autorouter (rstar, A*)
├── kicad/ # S-expression parser & bidirectional layout drift syncer
├── commands/ # Subcommand execution pipelines (check, dev, route, diff, sync, export, build)
├── config/ # fusible.yml manifest loader and path resolution
├── gerber/ # Pure-Rust RS-274X Gerber and Excellon drill generator
├── svg/ # Pure-Rust vector renderers for PCB layout and schematics
├── silicon/ # Silicon database and alternate function pin multiplexer
├── codegen/ # Typed firmware pin bindings generator (pins.rs)
├── parser/ # Serde Circuit JSON parser and source span tracking
├── linter/ # Fail-closed rules engine and diagnostics
├── netlist/ # Bipartite graph IR of nets, components, ports, and traces
└── tscircuit/ # Runner discovery and external process control
```
## Development
```sh
mise install # installs lefthook
cargo test # test suite (85+ unit and integration tests)
cargo clippy --all-targets -- -D warnings
cargo fmt --all -- --check
```
## License
[MIT](LICENSE)