{
  "schema": "leumas.docs.page/1",
  "id": "how-to:hardware-pipeline",
  "slug": "how-to/hardware-pipeline",
  "kind": "pages",
  "bucket": "how-to",
  "title": "Hardware — the idea-to-reality pipeline (build knowledge)",
  "name": "Hardware",
  "eyebrow": "build knowledge",
  "chip": null,
  "summary": "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...",
  "keywords": [
    "hardware-pipeline",
    "blinky",
    "esp32",
    "fabrication",
    "sensor",
    "leumas hardware pipeline",
    "how to hardware pipeline",
    "temperature"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "# Hardware — the idea-to-reality pipeline (build knowledge)\n\nHow Leumas takes a sentence (\"a blinky board with an ESP32 and a temperature sensor\") to a zip a fab\nhouse will build. Read this before touching Circuit Studio, the fabrication lane, or anything under\n`shared/engines/adapters/hardware`.\n\n## The shape\n\n```\nIDEA ──► DESIGN ──────────► EDIT ─────────► CHECK ──────► PACKAGE ──────► PRICE ──► ORDER\n        circuit-studio      live preview    validate      fab package     fab quote   (human,\n        adapter (pure)      iframe          / DRC         (in-browser)    adapter     on vendor site)\n\n  BROWSER                                   |   SERVER\n  ─────────────────────────────────────────────────────────────────────────────────────────\n  products/standalone/circuit-preview       |   shared/engines/adapters/hardware/*\n    RunFrame (React 19 · three 0.165)       |     circuit-studio  26 tools (20 pure)\n    evaluates the .circuit.tsx              |     fabrication      4 tools (all pure)\n    owns the circuit-json                   |   shared/engines/workspace  (sandboxes, fork, snapshots)\n    builds gerbers/BOM/PnP/SVG + thumbnail  |   products/leumas-api/src/routes/circuitStudio.js\n        ▲ postMessage protocol ▼            |\n  shared/packages/ui/src/circuit/CircuitCanvas.jsx\n  products/leumas-studio/src/admin/hardware/circuit/  (gallery · editor · capabilities)\n```\n\n**The evaluated circuit-json lives in the browser, so the manufacturing lane does too.** That is the\nsingle design decision everything else follows from: no `tsci` binary, no server process, so the whole\npipeline works for a hosted tenant exactly as it does on an appliance.\n\n## Where things live\n\n| Thing | Path |\n|---|---|\n| Design/edit/validate tools | `shared/engines/adapters/hardware/circuit-studio/` (`index.js` + `services/*`) |\n| Fab quoting | `shared/engines/adapters/hardware/fabrication/` (`index.js`, `drivers/{jlcpcb,pcbway}.js`) |\n| Preview + fab package | `products/standalone/circuit-preview/src/{main.jsx,fabPackage.js}` |\n| Panels (data-agnostic) | `shared/packages/ui/src/circuit/` — Canvas · PhaseBar · PreviewToolbar · Source · Layout · Autoroute · DRC · Simulate · **ExportWizard** · **FabricatePanel** · ToolsDock |\n| Studio surface | `products/leumas-studio/src/admin/hardware/circuit/` — `CircuitGallery` · `CircuitEditor` · `useProjectSandbox` · `useCircuitCapabilities` · `phase.js` |\n| Projects on disk | `shared/engines/workspace/src/index.js` — `resolveSandbox` · `copySandbox` · `/api/projects` (+ fork, snapshots, quota) |\n| API | `products/leumas-api/src/routes/circuitStudio.js` (`/api/circuit`), `routes/arduino.js` (`/api/arduino`) |\n| Agent tools | `shared/engines/coding-agent/src/circuitToolset.js` (12) |\n\n## The host ⇄ frame protocol\n\nThe preview is an iframe because `@tscircuit/runframe` vendors react-dom@19 and targets three@0.165,\nwhile the monorepo is React 18 / three 0.180. Isolation is the point — `@leumas/ui` keeps **zero**\ntscircuit dependencies.\n\n```\n→ circuit:render     { fsMap, mainComponentPath, activeTab }\n→ circuit:export     { requestId, artifacts[], boardName }\n→ circuit:thumbnail  { requestId }\n← circuit:ready · circuit:json · circuit:edit-event · circuit:tab · circuit:error · circuit:run\n← circuit:export-result { requestId, ok, base64, filename, manifest }\n← circuit:thumbnail-result { requestId, dataUri }\n```\n\n`circuit:json` carries a **summary**, not the full graph (trace geometry is megabytes and it fires on\nevery eval). The summary must stay circuit-json-*shaped*, because the host filters it by `type`:\ncomponents for the Layout panel, `pcb_board` for dimensions, `pcb_trace` for \"is it routed\", nets for\nstats. An earlier version kept only components and silently reported every board as 0 nets, never\nrouted and dimensionless. If you need a new fact on the host, add its element type to the summary.\n\n## Gating — the free tier is the product\n\n| Capability | Who has it | Gates |\n|---|---|---|\n| `hardware.studio` | **everyone** (`BASE_CAPABILITIES`) | `/api/circuit`, the gallery, the editor, export |\n| `hardware.maxProjects` / `maxSketches` / `snapshots` | 3 free, 100/100/50 on the plan | `/api/projects` create + fork |\n| `hardware.ai` | plan | Circuit Agent, Leviathan design tools |\n| `hardware.fab` | plan | fab package + quotes |\n| `hardware.cli` | plan | the 6 `tsci`-backed tools |\n\n`/api/circuit` mounts on **every** role. It used to sit inside `if (caps.localHardware)` and 404 for\nevery hosted tenant; `pnpm check:hardware` fails the build if that returns. `/api/arduino` genuinely\nis machine-local (it flashes a board over USB) and stays behind `caps.localHardware`.\n\nQuotas are only enforced when the capability key is actually present in the resolved map —\n`limitFrom` returns 0 both for \"denied\" and \"never configured\", and treating the second as a limit\nwould lock everyone out at zero. That is why the coding-agent builder (`builder.maxProjects`, which\nnobody configures) stays unmetered.\n\n## Extending it\n\n**A new fab vendor** — one file in `fabrication/drivers/`, exporting\n`{ id, label, site, currency, capabilities, quote(spec, {fetchJson, apiKey}), cartUrl(spec) }`, plus a\nline in `drivers/index.js`. Two rules: report `mode: 'estimate' | 'live'` on every quote, and never\nadd an order/checkout/payment call — `check-hardware.mjs` fails the build on either.\n\n**A new export artifact** — add it to `FAB_ARTIFACTS` and give it a block in `buildFabPackage`,\nwrapped in `attempt()` so a board that defeats one converter still exports everything else. Verified\nconverter names (they are NOT what you would guess): `convertSoupToGerberCommands` +\n`stringifyGerberCommandLayers`, `stringifyExcellonDrill(convertSoupToExcellonDrillCommands({...}))`,\n`convertBomRowsToCsv(await convertCircuitJsonToBomRows({circuitJson}))`,\n`convertCircuitJsonToPickAndPlaceCsv(cj)`, `convertCircuitJsonToPcbSvg(cj)`.\n\n**Do not add `circuit-json-to-gltf`.** It pulls `@resvg/resvg-js`, a native `.node` addon Rollup\ncannot bundle, and it breaks the whole preview build. The 3D view already works — that is\n`@tscircuit/3d-viewer` inside RunFrame, a different library. Likewise `circuit-json-to-spice` pulls a\n`spicets` build that fails to import and takes `@tscircuit/eval` down with it.\n\n**A new Leviathan tool** — add it to `useCircuitCapabilities.js` and wire it to the mutator the\nbutton already uses (`runDesign`, `addPart`, `sb.write`, `post(...)`). Never open a second\npersistence path; an AI edit must be indistinguishable from a hand edit.\n\n## Verify\n\n```sh\npnpm check:hardware   # static: reach guarantee, pure/CLI tier split, no purchase path\npnpm smoke:hardware   # live API: free tier, quotas, fork, snapshots, fab quoting\n```\n\nBrowser-verified end to end on 2026-08-03: create → design → live render → thumbnail + stats\nwriteback → fork → fab package (9 gerber layers + drill + BOM + PnP + SVGs, 45KB) → quotes from both\nvendors.\n",
  "source": {
    "path": "shared/services/knowledge/build-knowledge/hardware-pipeline.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 7434,
    "hash": "c30b834838e2980fcfb270848ae00e1b7c06dda7"
  },
  "urls": {
    "html": "/p/how-to/hardware-pipeline",
    "json": "/docs/how-to/hardware-pipeline.json",
    "md": "/docs/how-to/hardware-pipeline.md"
  },
  "links": {
    "composes": [],
    "usedBy": [],
    "product": [],
    "howTo": [],
    "skills": []
  }
}
