Docs
/
build knowledge

Hardware

How Leumas takes a sentence ("a blinky board with an ESP32 and a temperature sensor") to a zip a fab house will build. Read this before touching Circuit Studio, the fabrication lane, or anything...

Hardware — the idea-to-reality pipeline (build knowledge)

How Leumas takes a sentence ("a blinky board with an ESP32 and a temperature sensor") to a zip a fab house will build. Read this before touching Circuit Studio, the fabrication lane, or anything under shared/engines/adapters/hardware.

The shape

IDEA ──► DESIGN ──────────► EDIT ─────────► CHECK ──────► PACKAGE ──────► PRICE ──► ORDER
        circuit-studio      live preview    validate      fab package     fab quote   (human,
        adapter (pure)      iframe          / DRC         (in-browser)    adapter     on vendor site)

  BROWSER                                   |   SERVER
  ─────────────────────────────────────────────────────────────────────────────────────────
  products/standalone/circuit-preview       |   shared/engines/adapters/hardware/*
    RunFrame (React 19 · three 0.165)       |     circuit-studio  26 tools (20 pure)
    evaluates the .circuit.tsx              |     fabrication      4 tools (all pure)
    owns the circuit-json                   |   shared/engines/workspace  (sandboxes, fork, snapshots)
    builds gerbers/BOM/PnP/SVG + thumbnail  |   products/leumas-api/src/routes/circuitStudio.js
        ▲ postMessage protocol ▼            |
  shared/packages/ui/src/circuit/CircuitCanvas.jsx
  products/leumas-studio/src/admin/hardware/circuit/  (gallery · editor · capabilities)

The evaluated circuit-json lives in the browser, so the manufacturing lane does too. That is the single design decision everything else follows from: no tsci binary, no server process, so the whole pipeline works for a hosted tenant exactly as it does on an appliance.

Where things live

ThingPath
Design/edit/validate toolsshared/engines/adapters/hardware/circuit-studio/ (index.js + services/*)
Fab quotingshared/engines/adapters/hardware/fabrication/ (index.js, drivers/{jlcpcb,pcbway}.js)
Preview + fab packageproducts/standalone/circuit-preview/src/{main.jsx,fabPackage.js}
Panels (data-agnostic)shared/packages/ui/src/circuit/ — Canvas · PhaseBar · PreviewToolbar · Source · Layout · Autoroute · DRC · Simulate · ExportWizard · FabricatePanel · ToolsDock
Studio surfaceproducts/leumas-studio/src/admin/hardware/circuit/CircuitGallery · CircuitEditor · useProjectSandbox · useCircuitCapabilities · phase.js
Projects on diskshared/engines/workspace/src/index.jsresolveSandbox · copySandbox · /api/projects (+ fork, snapshots, quota)
APIproducts/leumas-api/src/routes/circuitStudio.js (/api/circuit), routes/arduino.js (/api/arduino)
Agent toolsshared/engines/coding-agent/src/circuitToolset.js (12)

The host ⇄ frame protocol

The preview is an iframe because @tscircuit/runframe vendors react-dom@19 and targets [email protected], while the monorepo is React 18 / three 0.180. Isolation is the point — @leumas/ui keeps zero tscircuit dependencies.

→ circuit:render     { fsMap, mainComponentPath, activeTab }
→ circuit:export     { requestId, artifacts[], boardName }
→ circuit:thumbnail  { requestId }
← circuit:ready · circuit:json · circuit:edit-event · circuit:tab · circuit:error · circuit:run
← circuit:export-result { requestId, ok, base64, filename, manifest }
← circuit:thumbnail-result { requestId, dataUri }

circuit:json carries a summary, not the full graph (trace geometry is megabytes and it fires on every eval). The summary must stay circuit-json-shaped, because the host filters it by type: components for the Layout panel, pcb_board for dimensions, pcb_trace for "is it routed", nets for stats. An earlier version kept only components and silently reported every board as 0 nets, never routed and dimensionless. If you need a new fact on the host, add its element type to the summary.

Gating — the free tier is the product

CapabilityWho has itGates
hardware.studioeveryone (BASE_CAPABILITIES)/api/circuit, the gallery, the editor, export
hardware.maxProjects / maxSketches / snapshots3 free, 100/100/50 on the plan/api/projects create + fork
hardware.aiplanCircuit Agent, Leviathan design tools
hardware.fabplanfab package + quotes
hardware.cliplanthe 6 tsci-backed tools

/api/circuit mounts on every role. It used to sit inside if (caps.localHardware) and 404 for every hosted tenant; pnpm check:hardware fails the build if that returns. /api/arduino genuinely is machine-local (it flashes a board over USB) and stays behind caps.localHardware.

Quotas are only enforced when the capability key is actually present in the resolved map — limitFrom returns 0 both for "denied" and "never configured", and treating the second as a limit would lock everyone out at zero. That is why the coding-agent builder (builder.maxProjects, which nobody configures) stays unmetered.

Extending it

A new fab vendor — one file in fabrication/drivers/, exporting { id, label, site, currency, capabilities, quote(spec, {fetchJson, apiKey}), cartUrl(spec) }, plus a line in drivers/index.js. Two rules: report mode: 'estimate' | 'live' on every quote, and never add an order/checkout/payment call — check-hardware.mjs fails the build on either.

A new export artifact — add it to FAB_ARTIFACTS and give it a block in buildFabPackage, wrapped in attempt() so a board that defeats one converter still exports everything else. Verified converter names (they are NOT what you would guess): convertSoupToGerberCommands + stringifyGerberCommandLayers, stringifyExcellonDrill(convertSoupToExcellonDrillCommands({...})), convertBomRowsToCsv(await convertCircuitJsonToBomRows({circuitJson})), convertCircuitJsonToPickAndPlaceCsv(cj), convertCircuitJsonToPcbSvg(cj).

Do not add circuit-json-to-gltf. It pulls @resvg/resvg-js, a native .node addon Rollup cannot bundle, and it breaks the whole preview build. The 3D view already works — that is @tscircuit/3d-viewer inside RunFrame, a different library. Likewise circuit-json-to-spice pulls a spicets build that fails to import and takes @tscircuit/eval down with it.

A new Leviathan tool — add it to useCircuitCapabilities.js and wire it to the mutator the button already uses (runDesign, addPart, sb.write, post(...)). Never open a second persistence path; an AI edit must be indistinguishable from a hand edit.

Verify

pnpm check:hardware   # static: reach guarantee, pure/CLI tier split, no purchase path
pnpm smoke:hardware   # live API: free tier, quotas, fork, snapshots, fab quoting

Browser-verified end to end on 2026-08-03: create → design → live render → thumbnail + stats writeback → fork → fab package (9 gerber layers + drill + BOM + PnP + SVGs, 45KB) → quotes from both vendors.

Source shared/services/knowledge/build-knowledge/hardware-pipeline.md (no-git)markdownjson
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt