# Developing kicad-library-manager — architecture & gotchas

Read this before extending the app. Everything here is a real lesson from the build.

## Architecture

- **`server.js`** — pure-Node `http` server (no native deps). Serves the SPA + a REST API.
  In-memory `DB` persisted to `data/data.json` (debounced). `DB = { libs, backup, probe, sync }`.
- **`public/index.html`** — the entire frontend: one vanilla-JS SPA, dark Adom theme. Polls
  `GET /api/state` every ~3s and re-renders. Three modes over one parsed model.
- **`cli.js`** — thin wrapper (`serve`, `app`, `probe`).
- No build step. `node server.js` (port 7823, `KLM_PORT`).

### Data model
`libs = { symbolLibs[], footprintLibs[], models3d[], defaultFpLibs[] }`
- `symbolLibs[]`: `{ nick, path, header, inTable, isDefault, symbols[] }`; symbol = `{ id:"nick::name",
  name, ref, value, footprint, fpNick, fpName, datasheet, mpn, mpnField, description, raw, pendingAdd?,
  pendingDelete?, pendingEdit? }`. `raw` is the original `(symbol …)` block — edits are surgical
  (`setSymProp` / `renameSymInBlock`) so sync re-emits faithfully.
- `footprintLibs[]`: `{ nick, path, inTable, isDefault, footprints[] }`; footprint = `{ id, name, file,
  model, models[], usedBy[], useCount, added?, rawMod?, pendingDelete? }`.
- `models3d[]`: `{ id, name, path, folder, usedBy[], useCount, added?, _local?, pendingDelete? }`.

## The desktop bridge (most important)

Barrett's machine is **Linux**, `HOME=/home/barrett-land`. Always reach the filesystem through
`adom-desktop` — never the container FS.

- **This bridge has NO `read_file` / `write_file`** ("Unknown desktop command"). Read via
  `shell_execute` `cat` (text) or `base64 -w0` (binary); write via `send_files` (lands in
  `~/Downloads`) then `mv`. `pull_file` exists under `files` too.
- `shell_execute` runs `sh -c` on the user's machine; `$HOME`, `$DISPLAY` etc. are the user's.
- KiCad GUI bridge verbs (`kicad_open_symbol_editor`, `kicad_install_symbol`, `kicad_screenshot_all`)
  are **Windows/Mac-only** — they return "not implemented on 'linux'". There is no CLI way to open
  the standalone Symbol Editor at a library on Linux, so "open in KiCad" was dropped.
- **Server-restart trap:** killing by `pkill -f server.js` matches the kill command's own shell.
  Kill node procs by walking `/proc/<pid>/cmdline`, or `fuser -k 7823/tcp`, in a SEPARATE command
  from the start. Start detached: `setsid node server.js >log 2>&1 < /dev/null &`.

## Parsing KiCad files

- `.kicad_sym`: `parseSymbolLib` walks top-level `(symbol "…")` blocks at depth 1 (nested units
  `Name_0_1`/`Name_1_1` stay inside their parent). `renameSymInBlock` renames **units first, then the
  exact top-level name** — doing it the other way corrupts `_copy` → `_copy_copy`.
- `.kicad_mod`: only the `(model …)` ref is parsed at probe; full text fetched on demand for preview.
- lib-tables: tolerant regex for `(lib (name …)(uri …))`; `isStockUri` flags KiCad default libs
  (`${KICAD*_*_DIR}`, `/usr/share/kicad`, etc.).
- **LCSC PN only:** `mpnOf` reads only LCSC/JLC properties (or a `C#####` value); `reDeriveMpn` re-runs
  it on boot so rule changes apply without a re-probe.

## Previews (kicad-cli on the desktop)

- Symbol: write the current `(symbol …)` block to a temp `.kicad_sym`, `kicad-cli sym export svg
  --symbol <name>`, `cat` the SVG back. **Recolor** for the dark canvas (`themeSymbolSvg`): light body,
  teal pins, muted text, transparent fills.
- Footprint: `kicad-cli fp export svg --layers F.CrtYd,F.Fab,F.SilkS,F.Paste,F.Mask,F.Cu` — kicad-cli
  honors the layer ORDER, so courtyard sits below silk and **pads render on top of the gray lines**.
  `liftFootprintText` then moves the `<g class="stroked-text">` groups (pad numbers / ref / value)
  above everything so the numbers sit on the pads.
- 3D: resolve the footprint's STEP model (expand `${KICAD*_3DMODEL_DIR}` → `/usr/share/kicad/3dmodels`),
  `cat` it from the desktop (STEP is ASCII), `step2glb convert` → GLB, serve at `/api/model.glb` (path
  ends `.glb` so Babylon detects the loader). Viewer is the Adom Babylon bundle loaded by **dynamic
  `import()`** (it's an ES module with relative chunk imports + CORS `*` — a classic `<script>` tag
  silently fails). All previews cached in-memory.
- Live LCSC stock/price: `/api/lcsc?part=C#####` → the shared JLCPCB service (`JLCPCB_API`, default the
  `john/service-jlcpcb` URL); hover popover in symbol mode.

## Pending model + sync

- Delete **flags** (`pendingDelete`, toggles) instead of removing; add sets `pendingAdd`/`added`; edit
  sets `pendingEdit`. The header chip shows `N new · N deleted · N changed`. All flags live in `DB.libs`
  so they're covered by the undo snapshots.
- **Sync is a repeatable one-shot** (no longer a stop/undo toggle): writes symbol libs
  (`buildSymLibText` excludes `pendingDelete`), writes added footprints/models, trashes flagged files to
  a reversible `.klm-trash/`, unregisters flagged whole libraries from the lib-tables, then
  `pruneAfterSync` drops deleted items + clears flags, and resets the button to idle.
- **Backup** (immutable snapshot at probe) powers Revert. Undo/redo is a snapshot tree (`HIST`,
  in-memory; full-libs snapshots incl. pending-trash so undo reverses a queued delete).
- ⚠️ Never clobber a user lib: writes go through `.klm.bak` backups; deletions are reversible (trash).

## Publishing

`adompkg publish --org adom`. The `files` allowlist EXCLUDES `data/` (the user's library data must
never ship). Hero is a billboard composed from `docs/shot.png`.
