app
KiCad Library Manager
Public Made by Adomby adom
Organize your KiCad symbol / footprint / 3D libraries fast — three priority views, inline editing, live previews, and one-click sync.
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-Nodehttpserver (no native deps). Serves the SPA + a REST API. In-memoryDBpersisted todata/data.json(debounced).DB = { libs, backup, probe, sync }.public/index.html— the entire frontend: one vanilla-JS SPA, dark Adom theme. PollsGET /api/stateevery ~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? }.rawis 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 viashell_executecat(text) orbase64 -w0(binary); write viasend_files(lands in~/Downloads) thenmv.pull_fileexists underfilestoo. shell_executerunssh -con the user's machine;$HOME,$DISPLAYetc. 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.jsmatches the kill command's own shell. Kill node procs by walking/proc/<pid>/cmdline, orfuser -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:parseSymbolLibwalks top-level(symbol "…")blocks at depth 1 (nested unitsName_0_1/Name_1_1stay inside their parent).renameSymInBlockrenames 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 …));isStockUriflags KiCad default libs (${KICAD*_*_DIR},/usr/share/kicad, etc.). - LCSC PN only:
mpnOfreads only LCSC/JLC properties (or aC#####value);reDeriveMpnre-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>,catthe 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.liftFootprintTextthen 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),catit from the desktop (STEP is ASCII),step2glb convert→ GLB, serve at/api/model.glb(path ends.glbso Babylon detects the loader). Viewer is the Adom Babylon bundle loaded by dynamicimport()(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 thejohn/service-jlcpcbURL); hover popover in symbol mode.
Pending model + sync
- Delete flags (
pendingDelete, toggles) instead of removing; add setspendingAdd/added; edit setspendingEdit. The header chip showsN new · N deleted · N changed. All flags live inDB.libsso they're covered by the undo snapshots. - Sync is a repeatable one-shot (no longer a stop/undo toggle): writes symbol libs
(
buildSymLibTextexcludespendingDelete), writes added footprints/models, trashes flagged files to a reversible.klm-trash/, unregisters flagged whole libraries from the lib-tables, thenpruneAfterSyncdrops 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.bakbackups; 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.
# 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`.