---
name: schematic-crud
description: Edit schematic JSON files in the Hydrogen project. Use when creating, parsing, or modifying schematic JSON: structure, MoleculeSymbol, Wire, NetLabel, symbolSource, pinOverrides, connectionData, or when the user asks how the schematic JSON format works.
---

# Schematic JSON

How the schematic file is structured and how to edit it. All edits are to the raw JSON; the app handles loading and resolving.

## Root structure

```json
{
  "title": "Schematic",
  "version": "2.0",
  "pages": [
    { "name": "Page 1", "components": [ /* component objects */ ] }
  ],
  "componentCount": 123
}
```

- **title** — Use `"Schematic"` for consistency with the app (optional; other titles load but this is the default).
- **version** — `"2.0"` for the current format.
- **Legacy:** Root may have `"components": [ ... ]` instead of `pages` (single page). When editing, prefer normalizing to `pages` with one entry.

Every component in `pages[i].components` must have:
- **`__name__`** — type: `"MoleculeSymbol"` | `"Wire"` | `"NetLabel"` | `"GND"` | `"PowerRail"` | etc.
- **`id`** — string (e.g. UUID). Must be unique. Wires and NetLabels reference other components by this.

---

## MoleculeSymbol

Two valid shapes. Use one or the other.

### Minimal (symbol from library)

Use when the symbol is identified by owner/molecule name; pin layout is not stored.

```json
{
  "__name__": "MoleculeSymbol",
  "id": "uuid",
  "enabled": true,
  "locked": false,
  "position": [x, y],
  "name": "Display name",
  "referenceDesignator": "U1",
  "symbolSource": {
    "owner": "string",
    "moleculeName": "string",
    "version": "v1"
  },
  "pinOverrides": {
    "left:0": "net_1",
    "right:3": "net_2"
  }
}
```

- **symbolSource** — Identifies which symbol to load. Required in this format.
- **pinOverrides** — Optional. Map **side:index** → net id or net name (e.g. `"left:0": "net_gnd"` or `"right:3": "wire-uuid"`). Values can be a **net name** (string like `"net_gnd"`, `"VCC"`) or a **wire id** (UUID of a Wire component). Use **side:index** when a symbol has duplicate pin names (e.g. many GNDs). Legacy files may use pin **name** as key; on load only the first pin per name is applied.
- **referenceDesignator** — e.g. "U1", "U2". Stable; wires/labels can reference the symbol by this.
- **symbolSource** — **owner**, **moleculeName**, and **version** must match exactly what exists in Curium; if the symbol file is missing, the symbol loads as a red box with no pins and pinOverrides are not applied.

### Full (embedded pins)

Use when the symbol was file-dropped or for legacy files. No `symbolSource`; full pin data is stored.

```json
{
  "__name__": "MoleculeSymbol",
  "id": "uuid",
  "enabled": true,
  "locked": false,
  "position": [x, y],
  "name": "Display name",
  "referenceDesignator": "U1",
  "numPins": 20,
  "leftPins": [
    { "name": "GND", "uid": "pin_xxx", "index": 0, "side": "left", "description": "", "holeType": "medium", "netID": "net_1" }
  ],
  "topPins": [],
  "rightPins": [],
  "bottomPins": [],
  "groups": [ { "name": "...", "side": "left", "pins": ["uid1", "uid2"] } ],
  "jumpers": [],
  "cornerPins": [ { "type": 0, "pinUid": "uid" } ]
}
```

- **leftPins / topPins / rightPins / bottomPins** — Arrays of pin objects. Each pin: **name**, **uid**, **index**, **side**, **description**, **holeType**, **netID** (optional).
- **groups / jumpers** — **name**, **side**, **pins** (array of pin uids).
- **cornerPins** — **type** (e.g. 0–3 for FL/FR/BL/BR), **pinUid**.

When editing full format, keep **uid** values consistent: wires and net labels reference pins by **uid** in this file. When editing minimal format, **pinOverrides** keys are **side:index** (e.g. `left:0`); wires still reference pins by **pinName** in connectionData.

---

## Wire

Connects two endpoints (components + pins) and stores a path. **Wires attach to pins** via **connectionData**: the app uses it to snap the wire to the pin, draw the connection, and set the pin’s net (filled dot). **You must add Wire components** for visible segments; pinOverrides and NetLabels alone do not create wires.

**Attaching a wire to a pin:** Set **connectionData.startPin** and **connectionData.endPin** with **componentId** (or **referenceDesignator**) and **pinName**. **pinName must match the symbol’s pin name exactly** (e.g. "VCC", "GND", "VOUT", "DOUT", "SDA")—use names from the molecule’s symbol in Curium; wrong names will not attach. Use **pinIndex** (0-based among pins with that name) when the symbol has duplicate pin names (e.g. several "GND"). **uniqueId** can be omitted; the app resolves by pinName on load. **pinOverrides** on MoleculeSymbol can be omitted when every connection is made by a Wire; the app sets each pin’s net from the wire on load.

```json
{
  "__name__": "Wire",
  "id": "uuid",
  "enabled": true,
  "locked": false,
  "position": [0, 0],
  "x1": 100, "y1": 200, "x2": 300, "y2": 200,
  "color": "#ffffff",
  "lineWidth": 0.5,
  "segments": [ { "x": 100, "y": 200 }, { "x": 200, "y": 200 }, { "x": 300, "y": 200 } ],
  "bendDirection": "horizontal-first",
  "netName": "net_1",
  "connectionData": {
    "startPin": {
      "componentId": "uuid-of-MoleculeSymbol",
      "referenceDesignator": "U1",
      "pinName": "MC16",
      "pinIndex": 0,
      "uniqueId": "pin_xxx"
    },
    "endPin": {
      "componentId": "uuid-of-other-symbol",
      "referenceDesignator": "U2",
      "pinName": "HSDO",
      "pinIndex": 0,
      "uniqueId": "pin_yyy"
    }
  }
}
```

- **connectionData** — Required for the wire to attach to pins. **startPin** and **endPin** each have:
  - **componentId** — `id` of the component (e.g. MoleculeSymbol). Must match an existing component in the same file.
  - **referenceDesignator** — Optional but stable (e.g. "U1"). Use for readability and as fallback when componentId is missing.
  - **pinName** — Must match the symbol pin name exactly (wrong names prevent attachment).
  - **pinIndex** — Optional; use when the symbol has duplicate pin names (e.g. several GND).
  - **uniqueId** — Optional; app resolves by pinName on load. When editing JSON, you can leave it or set it from the symbol’s pin list in full format.
- **segments** — Array of `{ x, y }`. The drawn path. **x1,y1** and **x2,y2** usually match first and last segment (or endpoints).
- **netName** — Net this wire belongs to (for netlist/highlight).

**Pin names:** Wire **connectionData.pinName** must match the symbol pin list exactly (e.g. from Curium). Mismatched names prevent attachment; pins then stay unconnected (hollow). **pinOverrides** on MoleculeSymbol can be omitted when every connection is made by a Wire; the app sets pin nets from wires on load.

**Editing wires:** Use **pinName** from the symbol actual pin list (e.g. Control Panel: VCC, GND, LED_DATA, SERVO_OUT, US_TRIG, US_ECHO, I2C_SDA, I2C_SCL). **uniqueId** can be wrong after a symbol is re-loaded from symbolSource; the app resolves by pinName. When adding a new wire, copy **componentId** from the target MoleculeSymbol and **pinName** from that symbol’s pin list.

---

## NetLabel

Labels a net. Either attached to a pin or to a wire.

```json
{
  "__name__": "NetLabel",
  "id": "uuid",
  "enabled": true,
  "locked": false,
  "position": [x, y],
  "netName": "net_1",
  "labelId": 1,
  "color": "#ff0000",
  "showBorder": true,
  "direction": "right",
  "attachedWireId": null,
  "wireAttachmentPoint": 0.5,
  "attachedMoleculeId": "uuid-of-MoleculeSymbol",
  "attachedPinId": "pin_xxx",
  "attachedPinName": "MC16",
  "stubWireEndpoint": { "x": 120, "y": 200 },
  "pinOffset": { "x": 20, "y": 0 }
}
```

**Pin attachment** (label on a component pin):
- **attachedMoleculeId** — Component **id**.
- **attachedPinId** — Pin uid (in full-format symbols). Can become stale after load; app resolves by **attachedPinName**.
- **attachedPinName** — Pin name. **Stable**; always set this when editing so the label still works after load.
- **pinOffset** — `{ x, y }` offset from pin position to label position (keeps label in place when symbol moves).
- **stubWireEndpoint** — Pin position (or same as pin); can be `{ x, y }`.
- **attachedWireId** — null.

**Wire attachment** (label on a wire):
- **attachedWireId** — Wire **id**.
- **wireAttachmentPoint** — 0–1, position along wire.
- **stubWireEndpoint** — Point on wire.
- **attachedMoleculeId** / **attachedPinId** / **attachedPinName** — null.

**Editing net labels:** When attaching to a pin, set **attachedMoleculeId**, **attachedPinName**, and **pinOffset**; **attachedPinId** is optional (app can resolve by name). When renaming nets, update **netName** and any **pinOverrides** or wire **netName** that share that net.

---

## Editing rules

1. **IDs** — Every component has a unique **id**. Wires use **connectionData.startPin/endPin.componentId**; net labels use **attachedMoleculeId** or **attachedWireId**. Do not reuse ids; do not remove a component and leave wires/labels pointing at its id without updating or removing them.
2. **Stable identifiers** — Prefer **referenceDesignator** (e.g. "U1") and **pinName** when editing. **uniqueId** and **componentId** can change after load for symbols that use **symbolSource**; the app matches by refDes and pin name.
3. **MoleculeSymbol** — Use **minimal** (symbolSource + pinOverrides) when the symbol is from the library; use **full** only when the symbol has no symbolSource (e.g. file-dropped). Do not mix: either symbolSource is present (and no leftPins/topPins/…) or it is absent (and full pin arrays are present).
4. **Consistency** — After moving a symbol, **position** and **pinOffset** on attached net labels determine where the label sits; wire **segments** and **x1,y1,x2,y2** can be updated to match new endpoints, or leave for the app to recompute on load.
5. **Rename net** — Update **netName** on every Wire and NetLabel on that net, and every **pinOverrides** entry whose value is that net id.
6. **Delete** — Remove the component from **pages[i].components**. Remove or fix any Wire whose **connectionData** references that component, and any NetLabel whose **attachedMoleculeId** (or **attachedWireId**) references it.
7. **Default Control Panel** — Leave the Control Panel in its default position and locked. The app adds it as a MoleculeSymbol (U1, adom/Control Panel/v1) at **position `[-70, 500]`** and **locked: true**. When editing or generating JSON, keep that symbol at `"position": [-70, 500]` and `"locked": true` so it matches the default and stays in place.

**If pins look unconnected or everything looks “floating” after load:** (1) **pinOverrides** are applied only after the symbol loads from Curium—**symbolSource** (owner, moleculeName, version) must match exactly; wrong or missing molecules load as a red box with no pins. (2) **Wire** components are required for visible wire segments; without them you only have symbols and labels, so there are no lines between components. (3) Use root **title** `"Schematic"` and **version** `"2.0"` for consistency.

---

## Quick reference

| Item | Required / stable for editing |
|------|-------------------------------|
| Any component | **__name__**, **id** |
| MoleculeSymbol (minimal) | **symbolSource**, **position**, **referenceDesignator**, **pinOverrides** (side:index → net id) |
| MoleculeSymbol (full) | **position**, **referenceDesignator**, **leftPins**/etc., pin **uid** and **name** |
| Wire | **connectionData.startPin/endPin**: **componentId**, **pinName**; **segments**; **netName** |
| NetLabel (on pin) | **attachedMoleculeId**, **attachedPinName**, **pinOffset**, **netName** |
| NetLabel (on wire) | **attachedWireId**, **wireAttachmentPoint**, **netName** |
