# Tscircuit Board Viewer (`adom-tsci`)

Interactive tscircuit preview inside a Hydrogen webview panel.
First-class **3D / PCB / Schematic** tabs rendered from the tscircuit
build output, plus:

- **🔍 Inspect tool** (key `I`) — hover any feature for a labelled info
  card: chip refdes + footprint + pin count, pad → net routing
  ("Connects to MC1.pin1, TP1.pin1 via 2 direct traces"), machine-
  contact role (mechanical-only vs wired), board stackup + baked-
  texture resolution, silkscreen text.
- **📏 Measure tool** (key `M`) — Fusion 360-style picking (pad / hole
  / chip / pin / edge), precision + mm/inches/mils.
- **🎬 Walkthrough Demo** — narrated tour generated deterministically from
  `circuit.json` by `adom-tsci walkthrough-gen` (connectors → ICs → grouped
  passives → nets x-ray), with 3D flyover animations. Connector edges are
  computed (not hardcoded), long ref lists are capped, and it **finishes on
  the ViewCube Home view with the Nets/x-ray panel turned off**. Don't
  hand-edit `walkthrough.json` — it auto-regenerates on `circuit.json` drift.
- **⟳ Re-run autorouter** — one click touches `lib/index.tsx` + spawns
  `bunx tsci build --glbs --svgs`.
- **`--texture-resolution N`** — re-bakes the board-surface texture
  (silkscreen + copper pads + annular rings + solder mask) up to
  8192×8192 via `circuit-json-to-gltf`, cached by `circuit.json`
  content hash. Essential for readable silkscreen on big boards.
- **Stale-deps check** against `@tscircuit/cli` on startup.
- **Secondary tabs:** `tsci live` (raw RunFrame) and `3d compare`.

**Recent (1.4.x):**
- **Canonical Adom app-header** — one-row `brand · subject · actions` with a
  hover app-info popover (version / engine / wiki / GitHub), brand fonts
  (Familjen Grotesk / Satoshi / JetBrains Mono), Adom-teal accent.
- **Parts inspector lists EVERY component, grouped by kind** (`Resistors 92`,
  `LEDs 90`, …) with collapsible groups — passives are no longer hidden.
- **Ground plane is 65% transparent and self-heals** — the bottom layer /
  bottom silkscreen show through; a footer readout reports the live
  transparency so a regression to opaque is caught instantly.
- **Panel deep-linking** — `#3d` / `#pcb` / `#schematic` / `#parts` /
  `#tsci-live` URL hashes select a panel (shareable + AI-drivable).
- **`view home`** — CLI/HTTP preset for the ViewCube Home view.
- **TTY-gated CLI color** so `OK:`/`ERROR:`/`Hint:` never leak ANSI into pipes.

As of **v0.3.0** the 3D tab is fully **AI-drivable**: every toolbar
button has a CLI subcommand + HTTP endpoint, plus there's a `/eval`
channel for hot-patching the running JS and a `/console` forwarder so
Claude can read uncaught errors without clicking anything. **No Adom
Viewer service dependency** — the Adom 3D viewer library is bundled
into the binary.

As of **v0.4.1**, `adom-tsci start` does a pre-flight `/proc` sweep
for stale `bun tsci dev` processes and cleans them up before spawning,
plus holds an atomic PID lock at `/tmp/adom-tsci-<port>.pid`. No more
orphan tsci devs compounding across crashed restarts.

![3D tab — first-class, default](docs/screenshot-3d.png)

## Credit to Ray

This app is an **onboarding layer over Ray's authoritative tscircuit
workflow guide** at
`~/.claude/skills/adom/guides/adom-tscircuit-skill.md`. Everything
it does with tscircuit — the `<Molecule>` component,
`MachineContactMedium` placement rules, the sizing grid, the DRC
notes, the `bunx tsci build --glbs --svgs` flags, the project
skeleton — traces back to Ray's guide. Go read it for connector
families, molecule generators, review servers, and DRC tooling.
`adom-tsci` just makes the "show me what I'm building" step trivial.

## The UI

**Primary tabs:**

- **3D (default).** Full-bleed canvas rendered by the Adom 3D
  Viewer (Babylon-based engine, bundled into the binary — no AV).
  Contact shadow + **65%-transparent ground plane** (the board's
  bottom layer / silkscreen show through; a footer readout reports
  the live transparency). The **ViewCube** (top-right) handles Home /
  Fit / view presets; a floating draggable toolbar over the canvas
  carries ▭ ground, ⎚ wireframe, axes, 📏 measure, inspect, and the
  walkthrough. Floating component panel lists every chip / passive /
  testpoint / contact from `circuit.json` — click a row to hide that
  component and see the traces routed under it.
- **PCB.** Inline `dist/lib/index/pcb.svg` in a panzoom container.
- **Schematic.** Inline `dist/lib/index/schematic.svg`, same panzoom.
- **Parts.** Inspector listing **every** component grouped by kind
  (Connectors / LEDs / Resistors / Capacitors / Machine pins …) —
  click any to verify its footprint ↔ 3D-body alignment.

![PCB tab](docs/screenshot-pcb.png)

![Schematic tab](docs/screenshot-schematic.png)

![Parts inspector — every component grouped by kind](docs/screenshot-parts.png)

**Secondary tab:**

- **tsci live.** The raw tscircuit RunFrame through a slingshot
  proxy. Escape hatch for features upstream tscircuit ships that
  we haven't wrapped yet. Dimmed 0.78 because RunFrame is
  hardcoded-light-theme.

## The Re-run autorouter button

Top-right of the header, right next to the GLB timestamp pill.

![Re-run autorouter flow](docs/screenshot-rerun.png)

Click it to:

1. Touch `lib/index.tsx` via the slingshot → `tsci dev` file server.
   Fires a `FILE_UPDATED` event, RunFrame's in-memory view refreshes.
2. Spawn `bunx tsci build lib/index.tsx --glbs --svgs` in the
   background. Regenerates the on-disk `3d.glb`, `pcb.svg`, and
   `schematic.svg`, which the first-class tabs poll for (`/glb/meta`
   every 3 s). Within ~30 s the 3D tab re-renders with the new
   layout and the PCB + Schematic tabs re-fetch on next click.
3. **Shift-click for a clean re-run** — also deletes
   `manual-edits.json` first so any interactive trace edits made in
   RunFrame's PCB editor are discarded.

Same from the CLI: `adom-tsci rerun` or `adom-tsci rerun --clean`.

## Stale-deps check

`adom-tsci start` fires a background query against
`https://registry.npmjs.org/@tscircuit/cli/latest` on launch and
compares to the version in
`<project>/node_modules/@tscircuit/cli/package.json`. If you're
behind, you get one `HINT:` line:

```
HINT: @tscircuit/cli is out of date (installed 0.1.1226, latest 0.1.1234).
Run `adom-tsci upgrade` to refresh.
```

Non-fatal, 3-second timeout, never blocks startup. tscircuit does
daily releases; this nudges you when you drift behind.

`adom-tsci upgrade [project-dir]` shells out to `bun update --latest`
in the target project.

## Quick start

```bash
# Install (one-time)
adom-wiki pkg install adom/adom-tsci

# From inside a tscircuit project directory:
adom-tsci start .

# Edit lib/index.tsx, click ⟳ Re-run autorouter in the header,
# watch the 3D / PCB / Schematic tabs re-render.

# When done:
adom-tsci stop
```

If you don't have a tscircuit project yet, see the `adom-tscircuit`
skill for the 4-file skeleton.

## Demo video

A 4-minute feature tour of everything in 1.2.0 is published at
[`apps/adom-tsci` on the wiki](https://wiki.adom.inc/adom/adom-tsci)
— 34 scenes with TTS narration, recorded against the RP2040 dev-board
molecule.

The full demo pipeline (scene script, TTS generation, per-scene
recording, mux + concat) lives under [`demo/`](demo/) and is
reproducible — see [`demo/README.md`](demo/README.md). Run it against
any tscircuit project to re-cut the tour.

## Examples

Ready-to-build example molecules live under [`examples/`](examples/)
— clone, `bun install`, and `adom-tsci start <example-dir>` to preview.

### `examples/SN65HVD230-Molecule/`

Small reference molecule: a **Texas Instruments SN65HVD230 CAN transceiver**
in SOIC-8, wrapped in a 16×16 mm molecule with power + CAN bus broken out
to six `MachineContactMedium` pins. Includes a 100 nF decoupling cap, the
120 Ω CAN bus termination resistor, and per-pin silkscreen labels.

![SN65HVD230 PCB with silkscreen labels](docs/example-sn65-pcb.png)

### `examples/BME680-Breakout-Molecule/`

Environmental-sensor breakout: a **Bosch BME680** four-in-one sensor
(temperature, humidity, barometric pressure, and a heated metal-oxide
gas-resistance plate that tracks VOC concentration) in a 3×3 mm LGA-8
package, wrapped in a 24×16 mm molecule. Power + I²C bus + the two
SPI-only pins (SDO, CSB) are broken out on six `MachineContactMedium`
pins. Internally: 100 nF decoupling on each supply rail (VDD and VDDIO),
10 kΩ pull-ups on SDA and SCL, and a third 10 kΩ from CSB→VDDIO so the
chip boots into I²C mode (address `0x77` by default; tie SDO low at the
host to switch to `0x76` and run two BME680s on one bus). One `<testpoint>`
per broken-out signal for probing-workcell access, and a bottom-silk ID
card (part number, function, I²C address, supply range, revision).

![BME680 PCB with silkscreen labels](docs/example-bme680-pcb.png)

### `examples/BHI360-Breakout-Molecule/`

Motion-sensor breakout: a **Bosch BHI360** 6-axis IMU (accelerometer +
gyroscope) with a built-in Fuser2 DSP core that runs orientation-fusion
firmware on-chip, so the host MCU can stay asleep while the sensor
tracks motion. LGA-20 (3×2.5 mm) package, wrapped in a 24×24 mm
molecule. Eight `MachineContactMedium` pins break out the SPI/I²C
interface (CSB / SCK / SDI / SDO, left edge) and power + interrupt
(INT / VDD / VDDIO / GND, right edge) — tie CSB high at the host to
select I²C, leave it driven for SPI. Internally: 100 nF decoupling on
each supply rail (VDD core + VDDIO I/O), silkscreen section headers
(`SPI/I2C` / `PWR`), a bottom-silk ID card, and one 0.8 mm
`<testpoint>` per broken-out signal for probing-workcell access.

Demonstrates an LGA-20 part on a footprinter that only ships
`qfn`/`son`/`dfn`/`bga` primitives — `qfn20_w4mm_h4mm_p0.5mm` is used
as the closest spatial match (QFN pads sit where the LGA pads land
on the bottom of the package).

![BHI360 3D breakout](docs/example-bhi360-3d.png)

![BHI360 PCB with silkscreen labels](docs/example-bhi360-pcb.png)

### `examples/iCE40HX1K-VQ100-Molecule/`

Big FPGA breakout: a **Lattice iCE40HX1K** in a 14×14 mm TQFP-100 (VQ100)
package, wrapped in a 56×56 mm molecule that breaks out **all 100 chip
pins** to `MachineContactMedium` contacts on the matching edge. Each pin
also gets a dedicated `<testpoint>` for probing-workcell access.

Silkscreen sections group the pins functionally:
`SPI` (programming), `PROG` (CRESET/CDONE), `IO BANK 0/1/2/3`, plus a
`NO USB / NO UART / NO SWD` reminder — the iCE40 is an FPGA, not an MCU,
so there's no debug port in the ARM sense; you program it through the
SPI pins (pins 67–71) via an iCELink/FT232 adapter and boot from a
downstream SPI flash.

![iCE40HX1K 3D breakout](docs/example-ice40-3d.png)

![iCE40HX1K PCB with silkscreen sections](docs/example-ice40-pcb.png)

### `examples/iCE40HX1K-USB-Molecule/`

Same iCE40HX1K FPGA, now with **USB-C programming built in** — an
"easy to use FPGA dev board." Plug a USB-C cable from your host into
the board and load a bitstream with `iceprog` or `fujprog`. No external
programmer, no JTAG dongle, no manual pin wiring.

Onboard stack:
- USB-C receptacle with ESD protection (USBLC6-2P6)
- **FT2232H** — dual-channel USB→SPI/UART bridge. Channel A in MPSSE
  mode talks to the iCE40's SPI programming pins (`iceprog`-compatible).
  Channel B is a USB-UART console you can attach to a soft-UART running
  inside your FPGA bitstream.
- **W25Q16JV** 2 MB SPI boot flash — the FT2232H writes your bitstream
  here, and the iCE40 reads from it on power-up so the board boots
  standalone without a host.
- **AP7333-33** (3.3 V) and **AP2112-1V2** (1.2 V) LDOs from USB 5 V
  for FPGA VCCIO + VCC core rails
- 12 MHz crystal for the FT2232H, 93LC56B EEPROM for its USB VID/PID
- 32 GPIO + 3 power contacts broken out on the molecule edge
- **Programming pass-through** — 6 edge contacts on the left side
  (`SCK / SS / SO / SI / CRESET / CDONE`, marked with a `→ VQ100 PROG`
  silkscreen callout) that carry the FT2232H's SPI bus out to mate with
  a downstream `iCE40HX1K-VQ100-Molecule`. Dock that molecule here and a
  single `iceprog` invocation reaches the bare FPGA through this board's
  onboard USB bridge.
- **16 test points** on the critical nets (VBUS, 3V3, 1V2, GND,
  USB D+/- on both sides of the ESD chip, the SPI programming bus,
  CRESET/CDONE, UART TX/RX) as SMT copper pads for a probing workcell's
  pogo pins — `footprintVariant="pad"`, 1 mm diameter, top layer

![iCE40HX1K USB dev board — 3D](docs/example-ice40-usb-3d.png)

![iCE40HX1K USB dev board — PCB](docs/example-ice40-usb-pcb.png)

## Architecture

```
+---------------------------+        +---------------------------+
|  Hydrogen webview tab     |        |  adom-tsci (Rust)         |
|  /proxy/8850/             |<------>|  tiny_http on 8850        |
|                           |        |                           |
|  +----------+             |        |  /              shell.html
|  |   3D     |<------------+------->|  /glb           file stream
|  +----------+             |        |  /pcb.svg       file stream
|  |  PCB     |             |        |  /schematic.svg file stream
|  +----------+             |        |                           |
|  | Schematic|             |        |  /runframe/     slingshot
|  +----------+             |        |  /runframe/api/*  (proxy)
|                           |        |  /runframe/standalone.min.js
|  +----------+             |        |                           |
|  | tsci live|<------------+------->|  /runframe/…    slingshot
|  +----------+             |        |                           |
|                           |        |  /basic-3d.html     (legacy)
|                           |        |  /js/adom-3d-viewer.min.js
|                           |        |  /js/environmentSpecular.env
|                           |        |                           |
|                           |        |  /console  /eval  /eval/* (AI bridges)
|                           |        |  /api/set-view /api/set-camera
|                           |        |  /api/toggle   /api/toggle-component
|                           |        |  /api/components   /api/tour
|                           |        |  /rerun /reload /shutdown /health
|  ⟳ Re-run autorouter -----+------->|                           |
+---------------------------+        +---------------------------+
                                              |
                                              v
                                    +--------------------+
                                    | bun tsci dev       |
                                    |   -p 3040          |
                                    | (process group)    |
                                    +--------------------+
```

### The slingshot fix (why this app exists)

`tsci dev` emits a hardcoded absolute URL in its HTML shell:

```js
window.TSCIRCUIT_FILESERVER_API_BASE_URL = "http://127.0.0.1:3040/api";
```

Inside a Hydrogen webview iframe, `127.0.0.1` is the user's machine,
not the container — so every fetch fails and RunFrame hangs at
`loading...` forever. `adom-tsci`'s slingshot rewrites that line (and
the absolute `/standalone.min.js` script tag) to **relative paths**,
then transparently proxies `/runframe/api/*` to tsci dev on port 3040.

**v0.2 note:** the v0.1 proxy handler had a bug where route-match
query-string stripping also dropped the query string on forward,
breaking `files/get?file_path=...`. v0.2 ships a full method/body/
query-aware proxy handler. If you're upgrading from v0.1, this is
why your tabs suddenly work.

tsci dev uses HTTP polling (`/api/events/list?since=<id>`), no
WebSockets, so the slingshot is a plain HTTP forwarder.

## CLI commands

See [`SKILL.md`](SKILL.md) for the full reference. Summary:

| Command | Purpose |
|---|---|
| `start <dir>` | Spawn tsci dev, start slingshot, open webview tab |
| `stop` | Clean shutdown (kills whole tsci process group) |
| `status [--json]` | Query running instance state |
| `open` | Re-add or navigate webview tab |
| `reload` | Force first-class tabs to re-poll build outputs |
| `rerun [--clean] [--fast]` | Re-run the autorouter (touches .tsx + spawns build) |
| `upgrade [dir]` | `bun update --latest` in target project |
| `health` | Probe tsci_reachable |
| `install` | Drop `SKILL.md` into `~/.claude/skills/adom-tsci/` |
| **AI-drive the 3D viewer** |  |
| `view <preset>` | home (ViewCube Home)/front/back/left/right/top/bottom/iso |
| `camera [--alpha A] [--beta B] [--radius R]` | Raw camera position |
| `tour start \| stop` | Cinematic slow-orbit |
| `toggle <flag>` | Flip ground/wireframe/axes |
| `toggle-component <NAME> [--hide \| --show]` | Show/hide a component (e.g. `U1` to see traces) |
| `list-chips` | List components the UI discovered in the current GLB |
| `eval "<js>"` | Run JS inside the webview, print the result |
| `console [--follow]` | Tail the forwarded JS console (logs + uncaught errors) |

## HTTP API

Every state-mutating CLI command is a thin wrapper over an HTTP
endpoint. AI agents and shell scripts can `curl` them directly:

```bash
curl http://127.0.0.1:8850/health
curl http://127.0.0.1:8850/glb/meta
curl -X POST http://127.0.0.1:8850/rerun
curl -X POST "http://127.0.0.1:8850/rerun?clean=1"
curl -X POST http://127.0.0.1:8850/shutdown
```

See [`SKILL.md`](SKILL.md) for the full reference.

## Development

```bash
git clone https://github.com/adom-inc/adom-tsci
cd adom-tsci
cargo build --release
cp target/release/adom-tsci ~/.local/bin/
adom-tsci install
```

Rust 1.70+, standard `cargo` toolchain. Unit tests cover the
slingshot HTML rewrite (the load-bearing regex):

```bash
cargo test
```

## Known v0.3 limitations

- **Component mapping coverage is partial.** The UI groups GLB
  meshes by nearest pcb_component centroid; some multi-mesh
  components (especially SOIC/SOT bodies with many primitives) can
  end up split across neighbouring names or fall into the `Board`
  catch-all. Kind-aware tolerances (chip=10mm, testpoint=1.2mm,
  contact=2mm, …) get most of the obvious ones right; edge cases
  tracked for v0.4.
- **The tsci live tab is dimmed** (`filter: brightness(0.78)`)
  because tscircuit's RunFrame is hardcoded light-theme. When they
  ship a dark mode, the filter comes out.
- **Re-run autorouter rebuilds the whole circuit, not just the
  autorouter.** tscircuit doesn't expose a standalone "run just the
  autorouter" entry point — the only way to refresh route layouts
  is to re-evaluate the circuit, which runs the whole pipeline. On
  a single-chip molecule this takes ~30 seconds.
- **GLB is bundled into the binary** (the `adom-3d-viewer.min.js`
  library is 13 MB, `environmentSpecular.env` is ~270 KB), pushing
  the release binary to ~18 MB. Trade-off: self-contained, zero
  runtime dependencies, offline-friendly.

## Design document

The original design plan lives at
[`docs/design/valiant-herding-dove.md`](docs/design/valiant-herding-dove.md),
preserved from the v0.1 planning session. Every non-obvious choice
(Rust over Bun, three.js in the generic pane, port 8850, the
"don't touch Ray's guide" boundary, why the slingshot uses a regex
rewrite) is documented there. v0.2 adds the first-class tabs,
rerun button, upgrade check, and the renamed binary on top of that
foundation — see the commit history for the v0.1 → v0.2 evolution.

## Acknowledgements

- **Ray** — for the tscircuit workflow guide this app exists to
  support.
- **tscircuit** — for the DSL and the RunFrame bundle.
- **The Adom platform team** — for the webview panel API,
  Basic3dView, `serve_glb`, the shotlog service, the brand guide
  (icons must be monochrome `#e6edf3` — this app's favicon
  follows that rule), and the `hydrogen-tab-icons` skill that
  documents how tab icons actually work.

## License

MIT. See [LICENSE](LICENSE).
