fusible 0.12.0

Electronic linter: a fuse that blows to block firmware code generation
# 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.

![demo1](assets/demo1.png)
![demo4](assets/demo4.png)

## Project status

| Phase | Scope | 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 --ventilation grid          # vent holes in the lid (grid | slots)
fusible body --wall-thickness 2.5 --height 25
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:

| Code | Meaning |
|---|---|
| `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)