← Commit history

1.2.4: orderable identity on every symbol - mpn, distributors {mouser, digikey, lcsc} and wikiUrl in the create JSON become MPN / Mouser / DigiKey / LCSC / Adom Wiki properties; create says NOT ORDERABLE YET when any are missing (John, 2026-09-19)

John Lauer ·52d6d94ad4 ·24d ago ·parent 3947afd
8 files changed +96−17
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-symbol"-version = "1.2.3"+version = "1.2.4" edition = "2021" description = "Create and preview KiCad/Fusion 360 schematic symbols with interactive viewer" 
SKILL.md+13
@@ -109,6 +109,19 @@ markings / other top-face features. The tool cannot see that; you can. If it did Re-run `create` after the revision and look again. Ship only when the thumbnail reads cleanly. Report which source and placement you shipped in one line. +**Orderable identity (1.2.4).** The create JSON carries `mpn` (the orderable manufacturer+part number, e.g. VL53L5CXV0GC/1), `manufacturer`, `datasheetUrl`, `description`,+`distributors {mouser, digikey, lcsc}` and `wikiUrl` (the part's Adom wiki component page,+https://wiki.adom.inc/<owner>/<slug>). They become the properties MPN / Manufacturer /+Datasheet / Description / Mouser / DigiKey / LCSC / "Adom Wiki" on the symbol; adom-lbr+carries them into Altium parameters (+ Supplier N links) and Fusion attributes, and+`kicad-cli sch export bom --fields` reads them straight off a schematic. `create` prints+NOT ORDERABLE YET naming whatever is missing. One lookup fills the numbers:+`adom-parts-search search <MPN>` (Mouser + DigiKey + JLCPCB/LCSC, stock and price). A+distributor that genuinely does not stock the part is left out and said so on the wiki+page. Why (John, 2026-09-19): the BOM and CPL the fab receives with the gerbers are built+from these fields, and the wiki link is how the next EE finds the reviewed data.+ **Outline stroke (1.2.3).** Baked chip-outline polylines are written with a 0.0254 mm (1 mil) stroke. KiCad draws any stroke at least one pixel wide, so the dense thumbnailer art reads as a crisp single-pixel line at every zoom; the old icon/120 rule (0.14-0.17 mm) smeared it into a
VERSION+1−1
@@ -1 +1 @@-1.2.3+1.2.4
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-symbol",-  "version": "1.2.3",+  "version": "1.2.4",   "type": "app",   "description": "KiCad symbol creator with interactive viewer and delivery to KiCad/Fusion 360",   "dependencies": {
page.json+3−3
@@ -1,6 +1,6 @@ {   "slug": "adom-symbol",-  "version": "1.2.3",+  "version": "1.2.4",   "type": "app",   "description": "KiCad symbol creator with interactive viewer and delivery to KiCad/Fusion 360",   "dependencies": {@@ -40,7 +40,7 @@   "org": "adom",   "updated_at": "2026-06-28T20:46:29.458Z",   "source_visibility": "public",-  "readme": "# adom-symbol\n\nStandalone Rust CLI + live app for **KiCad schematic symbols**. Generates\n`.kicad_sym` files natively in Rust, renders them as gorgeous dark-themed SVGs\nthrough the shared **service-kicad** container, and serves an AI-drivable live\nviewer with a full layout studio. No Node, no gallia, no local KiCad install.\n\n![The adom-symbol live viewer — an ADC symbol with its 3D-chip outline lasered into the body, beside the docked Layout studio](docs/screenshot-viewer.png)\n\n## What it does\n\n- **Generate** `.kicad_sym` natively in Rust — rectangle-body ICs with grouped,\n  side-sorted pins (Category A) and discrete passives derived from a KiCad\n  baseline symbol (Category B).\n- **Render** the symbol to a cropped, dark-themed SVG via service-kicad\n  (`POST /kicad/sch/export/svg`) — the same render path the chip-fetcher\n  dashboard uses, so the thumbnail and the app match exactly. The default render\n  **inlines** the part's 3D-chip **vector outline** (with the part number lasered\n  on) into the body centre as real `<polyline>`s — crisp in every renderer\n  (browser, librsvg thumbnailer, anything), not a rasterisable `<image>`.\n- **Browse + lay out** a whole chip-fetcher library in the **live viewer**: a\n  left chip-library rail (with project filters), a docked **Layout studio** for\n  the source / pin arrangement / designator placement / 3D-chip style, drag-to-\n  place labels, one-step **Undo**, and **Send to** KiCad / Fusion 360 / Altium /\n  OrCAD.\n- **Emit an outline contract** — alongside `<mpn>-symbol.svg`, `render` writes\n  `<mpn>-symbol-outline.mm.svg` + `<mpn>-symbol-outline.json` so **adom-lbr** can\n  draw the exact same chip outline onto the EAGLE/Fusion 360 symbol with **zero\n  guessing**. See [3D-chip outline contract](#3d-chip-outline-contract-for-adom-lbr).\n- **Serve** a live, AI-drivable app: the frontend stays connected to the Rust\n  server (`GET /state` poller + `POST /eval` channel), so the AI can push a new\n  symbol, hot-patch the UI, or read the console in real time.\n\n## The live viewer\n\n`adom-symbol serve` opens an interactive, AI-drivable viewer over the whole\nchip-fetcher library. The **Layout studio is always open** on the right — it's\nwhere you shape the symbol — and a collapsible chip-library rail sits on the\nleft.\n\n**Browse the library.** Every chip in the library root, with chip-fetcher's\nproject filters; ↑/↓ to step through, and each chip auto-saves the look you give\nit (per-symbol; your last-used choices fill in untouched chips).\n\n![The chip-library rail (74 chips) on the left, a symbol with its shaded 3D chip in the body, and the docked Layout studio on the right](screenshots/app-chip-rail.png)\n\n**Shape it in the Layout studio.** Pick the **source** (auto-grouped from ds2sf,\nor any manufacturer / Altium / Fusion / KiCad library variant), the pin\n**layout** (left-right or all-sides) and grouping, the **3D-chip** overlay style\n(off / shaded iso / thin / bold / blue / teal / dark) and size, where the chip\nname shows (lasered on the chip, in the symbol, both, or hidden), and then\n**Send to** a desktop EDA tool.\n\n![The shaded photoreal \"iso\" 3D chip composited into the symbol body](screenshots/app-3d-iso.png)\n\n**Place the labels — auto or by hand.** The Reference (`U?`/`Y?`) and chip-name\ndesignators don't have to overlap the body or pins. **Auto-place** centres both\nclear of everything; flip on **Manual** and each label gets a drag-outline you\ngrab and drop exactly where you want — it re-renders live and auto-saves to that\nchip. Any change (layout, 3D look, size, auto-place, a label drag) steps back\nwith **↶ Undo** or Ctrl/⌘+Z.\n\n![Manual label mode on a crystal — the Reference (Y?) and chip name (Crystal_GND24) each wrapped in a drag-outline you position by hand](screenshots/app-label-placement.png)\n\n## Install\n\n```\nadompkg install adom-symbol\n```\n\n## Quick start\n\n```bash\n# Create a symbol from structured pin JSON (stdin or --file)\nadom-symbol create --file rp2040.json --out RP2040\n\n# Preview an existing .kicad_sym as a themed viewer\nadom-symbol preview --file RP2040/RP2040.kicad_sym\n\n# Render a .kicad_sym to a themed thumbnail SVG (+ outline contract for adom-lbr)\nadom-symbol render --file RP2040/RP2040.kicad_sym --out RP2040-symbol.svg\n\n# Run the live app (AI-drivable: /state, /load, /eval, /console)\nadom-symbol serve --port 8781 --file RP2040/RP2040.kicad_sym\n```\n\nOpen the served app through the Adom proxy URL\n(`https://<slug>.adom.cloud/proxy/8781/`), never `localhost:8781` — the webview\nrenders outside the container.\n\n## CLI reference\n\n| Command | Description |\n|---|---|\n| `create --file <json> [--out <dir>]` | Generate `.kicad_sym` + SVG + viewer HTML from pin JSON |\n| `preview --file <kicad_sym> [--out <dir>]` | Render an existing symbol to a themed viewer |\n| `render --file <kicad_sym> --out <svg> [--iso <png>] [--chip-size large\\|medium\\|small]` | Render to a themed thumbnail SVG; auto-composites the 3D-chip outline + emits the outline contract |\n| `embed --file <kicad_sym> [--out <html>]` | Export a self-contained interactive HTML viewer (pan/zoom + pin info) |\n| `serve [--port 8781] [--file <kicad_sym>]` | Run the live AI-drivable viewer + layout studio |\n\n## 3D-chip outline contract (for adom-lbr)\n\nThe default `render` composites the part's **3D-chip vector outline** (white\n`heavy` style, with the part number lasered on the top face) into the symbol\nbody — sized to **0.62×** the body's smaller side, fitted from the source\n`320×320` outline canvas and centred on the body rectangle.\n\n![A symbol rendered with its 3D chip outline inlined into the body as crisp vector, the part number lasered on the top face](screenshots/app-outline-in-body.png)\n\nBecause that exact placement was previously re-guessed downstream (adom-lbr fit\nthe outline to ~0.38× of a different reference frame, so it drifted ~30% small),\n`render` now **exposes the placement** so it can be transcribed 1:1. Next to\n`<mpn>-symbol.svg` it writes:\n\n| File | What it is |\n|---|---|\n| `<mpn>-symbol-outline.mm.svg` | The outline strokes **only**, as decimated (lite) `<polyline>`s, in the **same units / origin / viewBox** as `<mpn>-symbol.svg` (mm, Y-down). A drop-in overlay; each stroke keeps the source style's real colour/width/dash (and the neon glow `<defs>`, blur scaled to mm). |\n| `<mpn>-symbol-outline.json` | A self-describing sidecar: the canonical `default_variant`, the source viewBox, segment count, and the exact transform in **both** frames — EAGLE/KiCad **Y-up** (what adom-lbr maps to) and the symbol-svg **Y-down** overlay frame. |\n\n`adom-lbr generate --symbol-art <mpn>-symbol-outline.mm.svg` detects the contract\n(via the sibling `.json`), derives the svg→EAGLE offset/flip from the two frame\ncentres, and emits the outline as layer-94 `<wire>`s — no re-fitting, no\nre-decimation, so the art lands exactly where adom-symbol drew it.\n\n> The **canonical embedded variant is `heavy`** (white `#e6edf3`, single visible\n> layer) — clean as schematic wires. The multi-layer styles (`blueprint`/`xray`\n> add hidden dashed edges, `neon` is a 2-layer glow) are deliberately avoided for\n> embedding.\n\n### Outline placement (the math `render` exposes)\n\nFor a source outline point `(px, py)` in the `320×320` canvas:\n\n```\nscale      = min(body_w, body_h) · 0.62 / 320          # mm per source unit\neagle_x    = center_x + (px − 160) · scale             # KiCad/EAGLE Y-up\neagle_y    = center_y − (py − 160) · scale             # (note the Y flip)\n```\n\n`center_x/center_y` (and the Y-down `symbol.svg`-frame equivalents) are written\nverbatim into the sidecar, so a consumer never has to reconstruct them.\n\n### The contract, proven\n\nThe emitted `.mm.svg` (recoloured magenta) placed over the live `-symbol.svg` —\ndead-centre in the body, framed by the real pins, tracing the chip body, side\npads and lasered name. Zero drift.\n\n![Magenta outline contract centered in the symbol body, framed by the pins](screenshots/outline-alignment.png)\n\nReal EAGLE `.lbr` output: **left** transcribed 1:1 from the contract (exact 0.62\nfit, centred); **right** the legacy guess — too small *and* off-centre, because\nit fit the source's ink bbox instead of the canvas.\n\n![Side-by-side EAGLE symbols: the contract fit centred vs the smaller, off-centre legacy guess](screenshots/outline-lbr-contract-vs-legacy.png)\n\n![The outline-contract JSON with placement in both coordinate frames](screenshots/outline-contract-json.png)\n\n## Architecture\n\nPure Rust. Symbol generation (`src/sym_gen.rs`, `src/discrete_gen.rs`) is a\nbyte-for-byte port of the original generator, **verified against golden files**\nin `tests/`. The only network dependency is **service-kicad** for SVG rendering\n— a lightweight proxy to the shared KiCad-10 container, so nothing installs\nKiCad locally. The live app server (`src/server.rs`) follows the Adom\napp-creator contract: `GET /state`, `POST /load`, `POST /eval` +\n`GET /eval/pending` + `POST /eval/:id/result`, `GET/POST /console`,\n`POST /shutdown`, `GET /favicon.svg`, and best-effort HD port registration.\nPer-symbol layout (including dragged label positions) persists to each chip's\n`info.json` and bakes into its `.kicad_sym` on save.\n\n## Build from source\n\nThe full source ships on the wiki app page — anyone in the community can build\nit locally:\n\n```bash\n# Fetch the source bundle from the wiki\nadompkg source adom-symbol            # downloads + unpacks the repo\n\ncargo build --release                 # standalone — only needs Rust + cargo\ncargo test --release                  # runs the golden-file parity tests\ncp target/release/adom-symbol ~/.local/bin/\n```\n\nThe only runtime dependency is network access to a service-kicad endpoint\n(`KICAD_SERVICE_API`, defaults to the shared container).\n\n## Wiki\n\nhttps://wiki.adom.inc/adom/adom-symbol\n\n## Contributing & forking — open source under the Adom org\n\n**adom-symbol** is **open source** under the **adom** org on the [Adom Wiki](https://wiki.adom.inc/adom/adom-symbol). The full source lives in this page's repo (public) — open the **Files** tab, or `adompkg source adom-symbol`. Third-party developers are welcome to modify it and contribute back.\n\n**Modify it**\n- Get the source from the Files tab (or `adompkg source adom-symbol`), make your changes, run `cargo test --release` (keep the golden tests green), and rebuild.\n\n**Contribute back to Adom — two paths**\n1. **Pull request (preferred).** The wiki runs a PR-per-page flow: propose your change on this page and the maintainer reviews + merges it — no fork needed.\n2. **Fork + breadcrumb.** Prefer your own fork or add-on? Publish it as your own wiki page, then drop a **breadcrumb on _this_ page** pointing at your fork so it's discoverable:\n   - Read existing trails: `GET https://wiki.adom.inc/api/v1/pages/adom-symbol/breadcrumbs`\n   - Attach a breadcrumb from your fork's page targeting this anchor (a short label + your slug).\n\n   The breadcrumb leaves a **linked path back here**, so **Adom's AI can auto-discover your fork** and surface it to users — the trail is followable both ways. Your fork stays yours; the original stays discoverable.\n\n## License\n\n**MIT** — © 2026 Adom Industries Inc. Open source; see [LICENSE](LICENSE). Modify, fork, and redistribute freely.\n",+  "readme": "# adom-symbol\n\nStandalone Rust CLI + live app for **KiCad schematic symbols**. Generates\n`.kicad_sym` files natively in Rust, renders them as gorgeous dark-themed SVGs\nthrough the shared **service-kicad** container, and serves an AI-drivable live\nviewer with a full layout studio. No Node, no gallia, no local KiCad install.\n\n![The adom-symbol live viewer \u2014 an ADC symbol with its 3D-chip outline lasered into the body, beside the docked Layout studio](docs/screenshot-viewer.png)\n\n## What it does\n\n- **Generate** `.kicad_sym` natively in Rust \u2014 rectangle-body ICs with grouped,\n  side-sorted pins (Category A) and discrete passives derived from a KiCad\n  baseline symbol (Category B).\n- **Render** the symbol to a cropped, dark-themed SVG via service-kicad\n  (`POST /kicad/sch/export/svg`) \u2014 the same render path the chip-fetcher\n  dashboard uses, so the thumbnail and the app match exactly. The default render\n  **inlines** the part's 3D-chip **vector outline** (with the part number lasered\n  on) into the body centre as real `<polyline>`s \u2014 crisp in every renderer\n  (browser, librsvg thumbnailer, anything), not a rasterisable `<image>`.\n- **Browse + lay out** a whole chip-fetcher library in the **live viewer**: a\n  left chip-library rail (with project filters), a docked **Layout studio** for\n  the source / pin arrangement / designator placement / 3D-chip style, drag-to-\n  place labels, one-step **Undo**, and **Send to** KiCad / Fusion 360 / Altium /\n  OrCAD.\n- **Emit an outline contract** \u2014 alongside `<mpn>-symbol.svg`, `render` writes\n  `<mpn>-symbol-outline.mm.svg` + `<mpn>-symbol-outline.json` so **adom-lbr** can\n  draw the exact same chip outline onto the EAGLE/Fusion 360 symbol with **zero\n  guessing**. See [3D-chip outline contract](#3d-chip-outline-contract-for-adom-lbr).\n- **Serve** a live, AI-drivable app: the frontend stays connected to the Rust\n  server (`GET /state` poller + `POST /eval` channel), so the AI can push a new\n  symbol, hot-patch the UI, or read the console in real time.\n\n## The live viewer\n\n`adom-symbol serve` opens an interactive, AI-drivable viewer over the whole\nchip-fetcher library. The **Layout studio is always open** on the right \u2014 it's\nwhere you shape the symbol \u2014 and a collapsible chip-library rail sits on the\nleft.\n\n**Browse the library.** Every chip in the library root, with chip-fetcher's\nproject filters; \u2191/\u2193 to step through, and each chip auto-saves the look you give\nit (per-symbol; your last-used choices fill in untouched chips).\n\n![The chip-library rail (74 chips) on the left, a symbol with its shaded 3D chip in the body, and the docked Layout studio on the right](screenshots/app-chip-rail.png)\n\n**Shape it in the Layout studio.** Pick the **source** (auto-grouped from ds2sf,\nor any manufacturer / Altium / Fusion / KiCad library variant), the pin\n**layout** (left-right or all-sides) and grouping, the **3D-chip** overlay style\n(off / shaded iso / thin / bold / blue / teal / dark) and size, where the chip\nname shows (lasered on the chip, in the symbol, both, or hidden), and then\n**Send to** a desktop EDA tool.\n\n![The shaded photoreal \"iso\" 3D chip composited into the symbol body](screenshots/app-3d-iso.png)\n\n**Place the labels \u2014 auto or by hand.** The Reference (`U?`/`Y?`) and chip-name\ndesignators don't have to overlap the body or pins. **Auto-place** centres both\nclear of everything; flip on **Manual** and each label gets a drag-outline you\ngrab and drop exactly where you want \u2014 it re-renders live and auto-saves to that\nchip. Any change (layout, 3D look, size, auto-place, a label drag) steps back\nwith **\u21b6 Undo** or Ctrl/\u2318+Z.\n\n![Manual label mode on a crystal \u2014 the Reference (Y?) and chip name (Crystal_GND24) each wrapped in a drag-outline you position by hand](screenshots/app-label-placement.png)\n\n## Install\n\n```\nadompkg install adom-symbol\n```\n\n## Quick start\n\n```bash\n# Create a symbol from structured pin JSON (stdin or --file)\nadom-symbol create --file rp2040.json --out RP2040\n\n# Preview an existing .kicad_sym as a themed viewer\nadom-symbol preview --file RP2040/RP2040.kicad_sym\n\n# Render a .kicad_sym to a themed thumbnail SVG (+ outline contract for adom-lbr)\nadom-symbol render --file RP2040/RP2040.kicad_sym --out RP2040-symbol.svg\n\n# Run the live app (AI-drivable: /state, /load, /eval, /console)\nadom-symbol serve --port 8781 --file RP2040/RP2040.kicad_sym\n```\n\nOpen the served app through the Adom proxy URL\n(`https://<slug>.adom.cloud/proxy/8781/`), never `localhost:8781` \u2014 the webview\nrenders outside the container.\n\n## CLI reference\n\n| Command | Description |\n|---|---|\n| `create --file <json> [--out <dir>]` | Generate `.kicad_sym` + SVG + viewer HTML from pin JSON |\n| `preview --file <kicad_sym> [--out <dir>]` | Render an existing symbol to a themed viewer |\n| `render --file <kicad_sym> --out <svg> [--iso <png>] [--chip-size large\\|medium\\|small]` | Render to a themed thumbnail SVG; auto-composites the 3D-chip outline + emits the outline contract |\n| `embed --file <kicad_sym> [--out <html>]` | Export a self-contained interactive HTML viewer (pan/zoom + pin info) |\n| `serve [--port 8781] [--file <kicad_sym>]` | Run the live AI-drivable viewer + layout studio |\n\n## 3D-chip outline contract (for adom-lbr)\n\nThe default `render` composites the part's **3D-chip vector outline** (white\n`heavy` style, with the part number lasered on the top face) into the symbol\nbody \u2014 sized to **0.62\u00d7** the body's smaller side, fitted from the source\n`320\u00d7320` outline canvas and centred on the body rectangle.\n\n![A symbol rendered with its 3D chip outline inlined into the body as crisp vector, the part number lasered on the top face](screenshots/app-outline-in-body.png)\n\nBecause that exact placement was previously re-guessed downstream (adom-lbr fit\nthe outline to ~0.38\u00d7 of a different reference frame, so it drifted ~30% small),\n`render` now **exposes the placement** so it can be transcribed 1:1. Next to\n`<mpn>-symbol.svg` it writes:\n\n| File | What it is |\n|---|---|\n| `<mpn>-symbol-outline.mm.svg` | The outline strokes **only**, as decimated (lite) `<polyline>`s, in the **same units / origin / viewBox** as `<mpn>-symbol.svg` (mm, Y-down). A drop-in overlay; each stroke keeps the source style's real colour/width/dash (and the neon glow `<defs>`, blur scaled to mm). |\n| `<mpn>-symbol-outline.json` | A self-describing sidecar: the canonical `default_variant`, the source viewBox, segment count, and the exact transform in **both** frames \u2014 EAGLE/KiCad **Y-up** (what adom-lbr maps to) and the symbol-svg **Y-down** overlay frame. |\n\n`adom-lbr generate --symbol-art <mpn>-symbol-outline.mm.svg` detects the contract\n(via the sibling `.json`), derives the svg\u2192EAGLE offset/flip from the two frame\ncentres, and emits the outline as layer-94 `<wire>`s \u2014 no re-fitting, no\nre-decimation, so the art lands exactly where adom-symbol drew it.\n\n> The **canonical embedded variant is `heavy`** (white `#e6edf3`, single visible\n> layer) \u2014 clean as schematic wires. The multi-layer styles (`blueprint`/`xray`\n> add hidden dashed edges, `neon` is a 2-layer glow) are deliberately avoided for\n> embedding.\n\n### Outline placement (the math `render` exposes)\n\nFor a source outline point `(px, py)` in the `320\u00d7320` canvas:\n\n```\nscale      = min(body_w, body_h) \u00b7 0.62 / 320          # mm per source unit\neagle_x    = center_x + (px \u2212 160) \u00b7 scale             # KiCad/EAGLE Y-up\neagle_y    = center_y \u2212 (py \u2212 160) \u00b7 scale             # (note the Y flip)\n```\n\n`center_x/center_y` (and the Y-down `symbol.svg`-frame equivalents) are written\nverbatim into the sidecar, so a consumer never has to reconstruct them.\n\n### The contract, proven\n\nThe emitted `.mm.svg` (recoloured magenta) placed over the live `-symbol.svg` \u2014\ndead-centre in the body, framed by the real pins, tracing the chip body, side\npads and lasered name. Zero drift.\n\n![Magenta outline contract centered in the symbol body, framed by the pins](screenshots/outline-alignment.png)\n\nReal EAGLE `.lbr` output: **left** transcribed 1:1 from the contract (exact 0.62\nfit, centred); **right** the legacy guess \u2014 too small *and* off-centre, because\nit fit the source's ink bbox instead of the canvas.\n\n![Side-by-side EAGLE symbols: the contract fit centred vs the smaller, off-centre legacy guess](screenshots/outline-lbr-contract-vs-legacy.png)\n\n![The outline-contract JSON with placement in both coordinate frames](screenshots/outline-contract-json.png)\n\n## Architecture\n\nPure Rust. Symbol generation (`src/sym_gen.rs`, `src/discrete_gen.rs`) is a\nbyte-for-byte port of the original generator, **verified against golden files**\nin `tests/`. The only network dependency is **service-kicad** for SVG rendering\n\u2014 a lightweight proxy to the shared KiCad-10 container, so nothing installs\nKiCad locally. The live app server (`src/server.rs`) follows the Adom\napp-creator contract: `GET /state`, `POST /load`, `POST /eval` +\n`GET /eval/pending` + `POST /eval/:id/result`, `GET/POST /console`,\n`POST /shutdown`, `GET /favicon.svg`, and best-effort HD port registration.\nPer-symbol layout (including dragged label positions) persists to each chip's\n`info.json` and bakes into its `.kicad_sym` on save.\n\n## Build from source\n\nThe full source ships on the wiki app page \u2014 anyone in the community can build\nit locally:\n\n```bash\n# Fetch the source bundle from the wiki\nadompkg source adom-symbol            # downloads + unpacks the repo\n\ncargo build --release                 # standalone \u2014 only needs Rust + cargo\ncargo test --release                  # runs the golden-file parity tests\ncp target/release/adom-symbol ~/.local/bin/\n```\n\nThe only runtime dependency is network access to a service-kicad endpoint\n(`KICAD_SERVICE_API`, defaults to the shared container).\n\n## Wiki\n\nhttps://wiki.adom.inc/adom/adom-symbol\n\n## Contributing & forking \u2014 open source under the Adom org\n\n**adom-symbol** is **open source** under the **adom** org on the [Adom Wiki](https://wiki.adom.inc/adom/adom-symbol). The full source lives in this page's repo (public) \u2014 open the **Files** tab, or `adompkg source adom-symbol`. Third-party developers are welcome to modify it and contribute back.\n\n**Modify it**\n- Get the source from the Files tab (or `adompkg source adom-symbol`), make your changes, run `cargo test --release` (keep the golden tests green), and rebuild.\n\n**Contribute back to Adom \u2014 two paths**\n1. **Pull request (preferred).** The wiki runs a PR-per-page flow: propose your change on this page and the maintainer reviews + merges it \u2014 no fork needed.\n2. **Fork + breadcrumb.** Prefer your own fork or add-on? Publish it as your own wiki page, then drop a **breadcrumb on _this_ page** pointing at your fork so it's discoverable:\n   - Read existing trails: `GET https://wiki.adom.inc/api/v1/pages/adom-symbol/breadcrumbs`\n   - Attach a breadcrumb from your fork's page targeting this anchor (a short label + your slug).\n\n   The breadcrumb leaves a **linked path back here**, so **Adom's AI can auto-discover your fork** and surface it to users \u2014 the trail is followable both ways. Your fork stays yours; the original stays discoverable.\n\n## License\n\n**MIT** \u2014 \u00a9 2026 Adom Industries Inc. Open source; see [LICENSE](LICENSE). Modify, fork, and redistribute freely.\n",   "discovery_pitch": "Generate, preview, and lay out KiCad/EAGLE schematic symbols in an interactive studio, then deliver them straight to KiCad, Fusion 360, Altium, or OrCAD.",   "discovery_triggers": [     "create a kicad symbol",@@ -67,4 +67,4 @@       "prompt": "open my RP2040.kicad_sym in the interactive symbol viewer"     }   ]-}\ No newline at end of file+}
src/ds2sf.rs+2
@@ -163,6 +163,8 @@ pub fn build_ic_input(      let input = IcInput {         symbol_name: sym.symbol_name.clone(),+        mpn: String::new(),+        wiki_url: String::new(),         manufacturer: sym.manufacturer.clone(),         package: sym.package.clone(),         description: sym.description.clone(),
src/main.rs+57−6
@@ -150,6 +150,23 @@ struct CreateInput {     symbol_name: String,     #[serde(default)]     manufacturer: String,+    /// Orderable manufacturer part number (VL53L5CXV0GC/1, not the family name).+    /// Written as the MPN property. Every EE attaches it before a part reaches a+    /// design: the BOM/CPL the fab gets with the gerbers is built from it.+    #[serde(default)]+    mpn: String,+    /// Distributor part numbers: {"mouser": "511-...", "digikey": "497-...-ND",+    /// "lcsc": "C..."} -> Mouser / DigiKey / LCSC properties. `adom-parts-search+    /// search <MPN>` returns all three in one call. Same shape as+    /// preferences.distributorPn, which still works.+    #[serde(default)]+    distributors: Option<serde_json::Map<String, serde_json::Value>>,+    /// The part's Adom wiki component page (https://wiki.adom.inc/<owner>/<slug>),+    /// written as the "Adom Wiki" property so every EDA tool carries the link back to+    /// the one place that holds the reviewed symbol, footprint, 3D, datasheet and+    /// provenance (John, 2026-09-19: the wiki is where EEs go for component data).+    #[serde(default, rename = "wikiUrl")]+    wiki_url: String,     #[serde(default)]     package: String,     #[serde(default)]@@ -515,6 +532,42 @@ fn annotate_contract(json: &str, kicad_sym: &str, name: &str) -> String {     serde_json::to_string_pretty(&v).unwrap_or_else(|_| json.to_string()) } +/// Canonical distributor property names from `distributors` (top level) and the older+/// `preferences.distributorPn`: mouser -> Mouser, digikey -> DigiKey, lcsc/jlcpcb -> LCSC.+fn distributor_props(input: &CreateInput) -> Vec<(String, String)> {+    let mut out: Vec<(String, String)> = Vec::new();+    for map in [input.distributors.as_ref(), input.preferences.distributor_pn.as_ref()].into_iter().flatten() {+        for (k, v) in map {+            let val = v.as_str().unwrap_or("").trim().to_string();+            if val.is_empty() { continue; }+            let label = match k.to_ascii_lowercase().as_str() {+                "mouser" => "Mouser", "digikey" | "digi-key" => "DigiKey", "lcsc" | "jlcpcb" => "LCSC",+                other => { out.push((other.to_string(), val)); continue; }+            };+            if !out.iter().any(|(l, _)| l == label) { out.push((label.to_string(), val)); }+        }+    }+    out+}++/// The orderable-identity check every symbol gets on create.+fn sourcing_hint(input: &CreateInput) {+    let dp = distributor_props(input);+    let has = |l: &str| dp.iter().any(|(k, _)| k == l);+    let mut missing = Vec::new();+    if input.mpn.trim().is_empty() { missing.push("mpn"); }+    if input.manufacturer.trim().is_empty() { missing.push("manufacturer"); }+    if input.wiki_url.trim().is_empty() { missing.push("wikiUrl (the Adom wiki component page, https://wiki.adom.inc/<owner>/<slug>)"); }+    for (l, key) in [("Mouser", "distributors.mouser"), ("DigiKey", "distributors.digikey"), ("LCSC", "distributors.lcsc")] {+        if !has(l) { missing.push(key); }+    }+    if missing.is_empty() {+        println!("_hint: ORDERABLE: MPN, Manufacturer, Mouser, DigiKey, LCSC and the Adom Wiki link are on the symbol as properties; adom-lbr carries them into Altium parameters (+ Supplier links) and Fusion attributes, and KiCad's BOM export (`kicad-cli sch export bom --fields`) picks them up.");+    } else {+        println!("_hint: NOT ORDERABLE YET: the create JSON has no {} - every EE attaches the manufacturer part number and the Mouser / DigiKey / LCSC numbers to a library part, because the BOM and CPL the fab gets with the gerbers are built from them. One lookup fills all three: `adom-parts-search search {}` (Mouser + DigiKey + JLCPCB/LCSC with stock and price), then set `mpn`, `distributors {{mouser, digikey, lcsc}}` and `wikiUrl` in the create JSON and re-run. If a distributor genuinely does not stock the part, leave it out and say so on the wiki page (component.distributor_links.<dist> = false with a dated note).", missing.join(", "), if input.mpn.trim().is_empty() { input.symbol_name.as_str() } else { input.mpn.as_str() });+    }+}+ fn cmd_create(file: Option<PathBuf>, out: Option<PathBuf>, no_chip_outline: bool, chip_outline_flag: bool, chip_size_flag: Option<String>, outline_svg_flag: Option<PathBuf>, outline_name_flag: Option<String>) -> Result<()> {     let file_dir: Option<PathBuf> = file.as_ref().and_then(|p| p.parent().map(|d| d.to_path_buf()));     let body = match file {@@ -527,6 +580,7 @@ fn cmd_create(file: Option<PathBuf>, out: Option<PathBuf>, no_chip_outline: bool         }     };     let input: CreateInput = serde_json::from_str(&body).context("parsing create JSON")?;+    sourcing_hint(&input);     if input.symbol_name.is_empty() {         return Err(anyhow!("symbolName is required"));     }@@ -619,12 +673,9 @@ fn cmd_create(file: Option<PathBuf>, out: Option<PathBuf>, no_chip_outline: bool             chip_name_centered: input.preferences.chip_name_centered,             ref_pos: "top".to_string(),             value_pos: "out-bot".to_string(),-            distributor_pn: input-                .preferences-                .distributor_pn-                .as_ref()-                .map(|m| m.iter().map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string())).collect())-                .unwrap_or_default(),+            mpn: input.mpn.clone(),+            wiki_url: input.wiki_url.clone(),+            distributor_pn: distributor_props(&input),             chip_outline,         };         sym_gen::generate_ic_sym(&icinput)
src/sym_gen.rs+18−5
@@ -201,6 +201,10 @@ pub struct Pin { }  pub struct IcInput {+    /// Orderable manufacturer part number -> the MPN property (empty: not written).+    pub mpn: String,+    /// Adom wiki component page URL -> the "Adom Wiki" property (empty: not written).+    pub wiki_url: String,     pub symbol_name: String,     pub manufacturer: String,     pub package: String,@@ -813,12 +817,19 @@ pub fn generate_ic_sym(input: &IcInput) -> String {         bake_chip_outline(co, &input.symbol_name, [body_left, body_bottom, body_right, body_top], free, &designators)     }); -    for (prop, val) in [+    let mut props: Vec<(&str, &str)> = vec![         ("Footprint", input.package.as_str()),         ("Datasheet", input.datasheet_url.as_str()),         ("Description", input.description.as_str()),         ("Manufacturer", input.manufacturer.as_str()),-    ] {+    ];+    if !input.mpn.trim().is_empty() {+        props.push(("MPN", input.mpn.as_str()));+    }+    if !input.wiki_url.trim().is_empty() {+        props.push(("Adom Wiki", input.wiki_url.as_str()));+    }+    for (prop, val) in props {         lines.push(format!("    (property \"{prop}\" \"{val}\""));         lines.push("      (at 0 0 0)".to_string());         lines.push("      (effects (font (size 1.27 1.27)) (hide yes))".to_string());@@ -934,6 +945,8 @@ mod tests {         let input = IcInput {             symbol_name: "TST-IC8".into(),             manufacturer: "Adom Test".into(),+            mpn: String::new(),+            wiki_url: String::new(),             package: "SOIC-8".into(),             description: "Parity-harness test IC".into(),             datasheet_url: "https://example.com/ds.pdf".into(),@@ -960,7 +973,7 @@ mod tests {     }     fn input_clone(i: &IcInput) -> IcInput {         IcInput {-            symbol_name: i.symbol_name.clone(), manufacturer: i.manufacturer.clone(), package: i.package.clone(),+            symbol_name: i.symbol_name.clone(), mpn: i.mpn.clone(), wiki_url: i.wiki_url.clone(), manufacturer: i.manufacturer.clone(), package: i.package.clone(),             description: i.description.clone(), datasheet_url: i.datasheet_url.clone(), reference_prefix: i.reference_prefix.clone(),             pins: i.pins.clone(), pin_layout: i.pin_layout.clone(), chip_name_centered: i.chip_name_centered,             ref_pos: i.ref_pos.clone(), value_pos: i.value_pos.clone(), distributor_pn: i.distributor_pn.clone(), chip_outline: i.chip_outline.clone(),@@ -969,7 +982,7 @@ mod tests {     #[test]     fn generic_outline_lands_inside_the_free_rect() {         let input = IcInput {-            symbol_name: "TST-OUT".into(), manufacturer: "".into(), package: "".into(), description: "".into(),+            symbol_name: "TST-OUT".into(), mpn: String::new(), wiki_url: String::new(), manufacturer: "".into(), package: "".into(), description: "".into(),             datasheet_url: "".into(), reference_prefix: "U".into(),             pins: vec![pin("SDA", "1", "bidirectional", "I2C"), pin("SCL", "2", "input", "I2C"), pin("VDD", "3", "power_in", "Power"), pin("GND", "4", "power_in", "Power")],             pin_layout: "left-right".into(), chip_name_centered: false, ref_pos: "top".into(), value_pos: "out-bot".into(),@@ -1005,7 +1018,7 @@ mod tests {     fn tst_all_matches_golden() {         let p = |name:&str,number:&str,ptype:&str,group:&str| Pin{name:name.into(),number:number.into(),ptype:ptype.into(),group:Some(group.into()),side:None};         let input = IcInput {-            symbol_name: "TST-ALL".into(), manufacturer: "Adom Test".into(), package: "QFN-16".into(),+            symbol_name: "TST-ALL".into(), mpn: String::new(), wiki_url: String::new(), manufacturer: "Adom Test".into(), package: "QFN-16".into(),             description: "all-sides coverage".into(), datasheet_url: "https://x/y.pdf".into(),             reference_prefix: "U".into(),             pins: vec![