Fusible
The fuse for your firmware. An electronic linter and tscircuit project manager in Rust: it reads the Circuit JSON generated by tscircuit, 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
| 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:
# Using bun:
# Or using npm:
Inspect toolchain health anytime with fusible doctor (or run fusible doctor --install to provision it).
Installation
Quick start (monorepo)
Initialize a new hardware + firmware project:
This creates a minimal fusible.yml:
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:
name: rover
boards:
main:
sensor:
firmware:
main-fw:
sensor-fw:
bodies:
case: # drawn in FreeCAD, Fusion, Onshape...
knob: # code-CAD
lid: # Fusible owns and regenerates it
fusible checkchecks every board and reports all violations together.fusible check sensorchecks one.fusible build sensor-fwchecks thesensorboard, writes its pin bindings, then builds that firmware.fusible export main --preset fabwrites intodist/main/fab/. With several boards,exportanddevneed a board name.- A firmware or generated body must name its
board:when the project has more than one. An authored body may name one too: it is then modeled in that board's coordinate frame, which is what the Studio overlay relies on.around:is still accepted as an alias ofboard:for bodies. - The single-board form (
hardware:,mcu: { chip }, onefirmware:,mechanical:) still loads. It becomes a board namedmain, a firmware namedfirmware, and a generated body named after the project, which keeps writingdist/mechanical/<project>.<format>.
Commands
1. Electrical linter (check)
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:
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 embeddedUndefinedmonospace 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. - A project tree on the left lists the boards, firmware, and bodies of
fusible.yml. Click a board to switch to it: schematic, PCB, 3D, parts, nets, and checks all follow. A dot shows each board's fuse state. Click a firmware to see the board it runs on, and a body to open theBODIEStab. The URL keeps the selection (?item=board:sensor), so a view can be bookmarked. The tree appears when the project has firmware, bodies, or several boards. - A
RUNStab startscheck,build, andbodyfrom the browser, on one item (the play button in the tree) or on the whole project. It re-runs this samefusiblebinary, so the log and the result (OK,FUSE BLOWN,FAILED) match the terminal. One run goes at a time. Runs accept only names fromfusible.yml, a JSON body, and a localOrigin. - A
PINStab opens when you select a firmware. It lists the pin map of the board the firmware runs on, built by the code that writespins.rs: each GPIO grouped by peripheral, with its signal, connected part, and Rust name (CAMERA_PINS.cam_d0). Pins with apin_muxconflict are marked in red with the reason. Click a pin to inspect its net or connected part.BUILDruns the firmware build, and the tab shows its last result and whetherpins.rsexists. The MCU comes from the board'smcu:setting. - The project tree collapses with
[or the chevron in its header, and Studio remembers the choice. Below 900px wide it becomes a modal drawer that opens from the button in the status bar: focus moves into it, the page behind it is inert, andEscor a selection closes it and returns focus to the button. The tree, run list, and pin rows are real buttons, so they work from the keyboard. A failed run request (for example, a run is already going) shows its message above the log without replacing it. BODIEStab showing the generated base and lid of the selected board, with lid and explode toggles. The 3D view has anENCLOSUREtoggle that draws the shell translucent around the board. The studio builds the model on demand from the first generated body of that board and rebuilds it when the circuit changes. Needsbun. Selecting an authored body (source:) in the tree draws that file instead, as one mesh in the board frame (mm, Z-up, X/Y equal to the PCB coordinates). GLB is served as is; STEP (.step,.stp) and STL are converted withocct-wasmand cached by path, modification time, and size, so an unchanged file is not converted again. The files are limited to 64 MiB. A missing file showsMISSINGin the tree. Authored GLB files must already use millimetres and Z-up.- 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:
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):
# Compare component placement and report layout drift:
# Synchronize physical moves and rotations back into manual-edits.json:
# Dry run sync without modifying disk:
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:
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.
# Presets:
# Specific format exports:
# Bypass verification (for debugging/drafts):
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. Withbuild:, 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 likeexport.
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.
Start from the generated body, or design your own
Generation is a feature, not a lock. Two commands hand the design over to you:
ejectis fuse-gated likebody(--skip-lintfor drafts). It stages the model next to its final path and moves it into place only when generation succeeds, never replaces an existing file, and replacesfusible.ymlin one step, keeping your comments. If the entry has a shape it cannot edit safely, it leaves the manifest alone, writes the model, and exits 2 with the lines to change by hand. Afterwards the body is authored: edit the file in your CAD tool and Fusible never touches it again.kitwriteskit.jsonfor people who model from scratch: boardoutlineandthickness, everyhole(x,y,diameter,plated), every component footprint (x,yis the center,wxhthe size,layer,touches_edge), and theopeningswhere a component reaches an outline edge (sideis+x,-x,+y, or-y). Each opening gives the cut as two points on the board edge (from,to). Thenotchblock holds the generator's constants (margin,edge_tolerance,clearance,extra_depth): the cut runs outward from the edge forclearance+ wall thickness +extra_depth. Holes with no diameter are left out and named inwarnings. Units are mm in the pcb frame the enclosure generator uses.board.stepis added when the board entry is a tscircuit source and a runner is installed; a runner that fails the export is a tool error (exit 2) afterkit.jsonis written. A bare circuit file goes tobodies/kit/<file name>/. The kit is fuse-gated.bodykeeps its plain form, soejectandkitare reserved words. A body namedejectorkitis rejected whenfusible.ymlloads.
Enclosure details:
- The outline comes from
pcb_board(outlinepoints, orwidthxheight). - Each
pcb_holegets 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 installsocct-wasminto~/.cache/fusible/. IGES is not supported. fusible devpreviews the first generated body in itsBODIEStab 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:
# Search footprints and components in the tscircuit registry:
# Add a community component or registry footprint to hardware/:
# Install all hardware dependencies:
9. Toolchain diagnostics (doctor)
Inspects local toolchain health (bun, node, npm, npx, tsci) and verifies tscircuit runner availability:
Parts and costs
Fusible lists the components placed on each board, grouped by kind, value, and part number, and adds each body of the manifest as a 3D print. Prices, picks, and extra parts live in bom.yml next to fusible.yml, created on the first read; the Studio PARTS & COSTS view edits the same file.
currency: EUR
print:
price_per_kg: 25
parts:
resistor-10kohm:
m3-screw:
chip-esp32-s3:
prints:
case:
A key that matches a detected part (the id of fusible bom -f json) overrides it; any other key is a manual part. Print cost is grams / 1000 * price_per_kg. A part without a price gets an estimate by kind and value (marked ~), and a body with an STL gets its weight from the model volume (PLA, 40% effective fill). Both yield to what you type in bom.yml.
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