← Commit history

Publish 1.1.0

barrett-land ·13058ef79d ·3mo ago ·parent bd620a2
4 files changed +35−14
package.json+2−1
@@ -4,8 +4,9 @@   "slug": "kicad-library-manager",   "title": "KiCad Library Manager",   "brief": "Organize your KiCad symbol / footprint / 3D libraries fast — three priority views, inline editing, live previews, and one-click sync.",-  "version": "1.0.1",+  "version": "1.1.0",   "visibility": "public",+  "changelog": "## 1.1.0\n- Footprint previews: pads render above silkscreen with pad numbers on top\n- 3D model preview (STEP→GLB) for footprints and a dedicated 3D Models view\n- Live JLCPCB stock + price on hover over an LCSC part number\n- Pending changes: deletes flag red, adds green, edits outline the changed field orange; header chip shows new / deleted / changed counts\n- Sync is a repeatable one-shot (resets after) with per-library sync; deleted libraries get unregistered from the lib-tables\n- Tabs renamed to Symbols / Footprints / 3D Models",   "discovery_triggers": [     "kicad library manager",     "organize my kicad libraries",
page.json+3−2
@@ -4,7 +4,7 @@   "slug": "kicad-library-manager",   "title": "KiCad Library Manager",   "brief": "Organize your KiCad symbol / footprint / 3D libraries fast — three priority views, inline editing, live previews, and one-click sync.",-  "version": "1.0.1",+  "version": "1.1.0",   "description": "A fast workbench for your personal KiCad libraries. Probes your machine via adom-desktop for your sym-lib-table / fp-lib-table, then gives you three views — Symbol priority (inline-editable ref / footprint / datasheet / LCSC PN / description, with footprint autocomplete and live JLCPCB stock+price on hover), Footprint priority (3D model + the symbols using each footprint), and 3D-model priority. Add parts from the Adom wiki or blank, duplicate symbols, flag deletes (red) and adds (green) with an undo/redo tree, preview symbols + footprints + 3D models with kicad-cli rendering, and sync changes back to disk with reversible backups.",   "dependencies": {},   "tags": [@@ -61,5 +61,6 @@     "preview symbol footprint 3d model",     "lcsc stock and price",     "klm"-  ]+  ],+  "changelog": "## 1.1.0\n- Footprint previews: pads render above silkscreen with pad numbers on top\n- 3D model preview (STEP→GLB) for footprints and a dedicated 3D Models view\n- Live JLCPCB stock + price on hover over an LCSC part number\n- Pending changes: deletes flag red, adds green, edits outline the changed field orange; header chip shows new / deleted / changed counts\n- Sync is a repeatable one-shot (resets after) with per-library sync; deleted libraries get unregistered from the lib-tables\n- Tabs renamed to Symbols / Footprints / 3D Models" }\ No newline at end of file
public/index.html+13−7
@@ -124,6 +124,11 @@   .fp-tag.best{color:var(--accent-bright);border:1px solid var(--accent-border);background:var(--accent-dim)}   .fp-tag.ext{color:var(--text-dim);border:1px solid var(--border)}   input.cell.fpdef{background:rgba(140,107,247,.09);border-color:rgba(140,107,247,.22)}+  /* orange outline on a field changed since the last sync */+  input.cell.edited{border-color:#e8923c;box-shadow:0 0 0 2px rgba(232,146,60,.35)}+  input.cell.edited:focus{border-color:#e8923c;box-shadow:0 0 0 3px rgba(232,146,60,.45)}+  .ds-icon.edited{border-color:#e8923c;box-shadow:0 0 0 2px rgba(232,146,60,.35)}+  .desc-cell.edited{outline:2px solid rgba(232,146,60,.55);outline-offset:2px;border-radius:6px}   input.cell.fpdef:focus{border-color:rgba(140,107,247,.5);box-shadow:0 0 0 2px rgba(140,107,247,.14)}   input.cell.short{max-width:150px}   td.col-ref{width:70px}@@ -243,7 +248,7 @@     <button class="btn warn" id="revertBtn" title="Revert all libraries to the initial probed state" disabled>⟲ Revert to backup</button>     <span class="pending-chip" id="pendingChip" style="display:none"></span>     <button class="btn primary" id="syncBtn" title="Write ALL your edits to disk">⇅ Sync all to disk</button>-    <button class="btn" id="probeBtn" title="Scan your computer for KiCad libraries">◎ Probe libraries</button>+    <button class="btn" id="probeBtn" title="Scan your computer for KiCad libraries">⌖ Probe libraries</button>   </header>    <div class="sync-banner" id="syncBanner"></div>@@ -487,13 +492,14 @@ function renderSymbol(tbody){     for(const s of rows){       const tr=document.createElement('tr'); tr.className='item'+(curSel().has(s.id)?' sel':'')+pendClass(s); tr.dataset.id=s.id;       const fpDefault = s.fpNick && (STATE.libs.defaultFpLibs||[]).some(n=>n.toLowerCase()===s.fpNick.toLowerCase());+      const ec=f=>(s.editedFields&&s.editedFields[f])?' edited':'';       tr.innerHTML = selBox(s.id)+-        `<td><div class="namewrap"><input class="cell name" data-id="${esc(s.id)}" data-field="name" value="${esc(s.name)}" spellcheck="false"><button class="dup-btn" data-previewsym="${esc(s.id)}" title="Preview symbol + footprint">${MAG_SVG}</button><button class="dup-btn" data-dup="${esc(s.id)}" title="Duplicate this symbol">⧉</button></div></td>`+-        `<td class="col-ref"><input class="cell ref" data-id="${esc(s.id)}" data-field="ref" value="${esc(s.ref)}" spellcheck="false"></td>`+-        `<td><input class="cell${fpDefault?' fpdef':''}" data-id="${esc(s.id)}" data-field="footprint" value="${esc(s.footprint)}" spellcheck="false" placeholder="lib:footprint" title="${fpDefault?'Linked footprint — KiCad default library':'Linked footprint (lib:name)'}"></td>`+-        `<td class="col-ds"><span class="ds-icon ${isUrl(s.datasheet)?'on':''}" data-dsedit="${esc(s.id)}" title="${s.datasheet?'Datasheet: '+esc(s.datasheet)+' — click to edit':'No datasheet — click to add'}">${DS_SVG}</span></td>`+-        `<td><input class="cell short" data-id="${esc(s.id)}" data-field="mpn" value="${esc(s.mpn)}" spellcheck="false" placeholder="—"></td>`+-        `<td><div class="desc-cell"><span class="desc-text" title="${esc(s.description)}">${s.description?esc(s.description):'<span class=muted>—</span>'}</span><button class="desc-exp" data-desc="${esc(s.id)}" title="View / edit full description">⤢</button></div></td>`;+        `<td><div class="namewrap"><input class="cell name${ec('name')}" data-id="${esc(s.id)}" data-field="name" value="${esc(s.name)}" spellcheck="false"><button class="dup-btn" data-previewsym="${esc(s.id)}" title="Preview symbol + footprint">${MAG_SVG}</button><button class="dup-btn" data-dup="${esc(s.id)}" title="Duplicate this symbol">⧉</button></div></td>`++        `<td class="col-ref"><input class="cell ref${ec('ref')}" data-id="${esc(s.id)}" data-field="ref" value="${esc(s.ref)}" spellcheck="false"></td>`++        `<td><input class="cell${fpDefault?' fpdef':''}${ec('footprint')}" data-id="${esc(s.id)}" data-field="footprint" value="${esc(s.footprint)}" spellcheck="false" placeholder="lib:footprint" title="${fpDefault?'Linked footprint — KiCad default library':'Linked footprint (lib:name)'}"></td>`++        `<td class="col-ds"><span class="ds-icon ${isUrl(s.datasheet)?'on':''}${ec('datasheet')}" data-dsedit="${esc(s.id)}" title="${s.datasheet?'Datasheet: '+esc(s.datasheet)+' — click to edit':'No datasheet — click to add'}">${DS_SVG}</span></td>`++        `<td><input class="cell short${ec('mpn')}" data-id="${esc(s.id)}" data-field="mpn" value="${esc(s.mpn)}" spellcheck="false" placeholder="—"></td>`++        `<td><div class="desc-cell${ec('description')}"><span class="desc-text" title="${esc(s.description)}">${s.description?esc(s.description):'<span class=muted>—</span>'}</span><button class="desc-exp" data-desc="${esc(s.id)}" title="View / edit full description">⤢</button></div></td>`;       tbody.appendChild(tr);     }   }
server.js+17−4
@@ -171,6 +171,19 @@ function mpnOf(props) {   for (const [k, v] of Object.entries(props)) { const t = (v || '').trim(); if (!t) continue; if (/lcsc|jlc/i.test(k) || /^C\d{3,}$/.test(t)) return { field: k, value: t }; }   return { field: null, value: '' }; }+// Backfill per-field "changed" flags for symbols edited before field-tracking existed: diff each+// pending-edit symbol against its backup counterpart (matched by LCSC PN, else name).+const VIS_FIELDS = ['name', 'ref', 'footprint', 'datasheet', 'mpn', 'description'];+function backfillEditedFields() {+  if (!DB.backup) return;+  const bAll = DB.backup.symbolLibs.flatMap(l => l.symbols);+  for (const lib of DB.libs.symbolLibs) for (const s of lib.symbols) {+    if (!s.pendingEdit || s.editedFields || s.pendingAdd) continue;+    const b = (s.mpn && bAll.find(x => (x.mpn || '').toUpperCase() === s.mpn.toUpperCase())) || bAll.find(x => x.name === s.name);+    const ef = {}; if (b) for (const f of VIS_FIELDS) if ((s[f] || '') !== (b[f] || '')) ef[f] = true;+    s.editedFields = ef;+  }+} // Re-derive the LCSC/JLC PN for every loaded symbol from its raw block (used after the rule changes). function reDeriveMpn() {   for (const lib of DB.libs.symbolLibs) for (const s of lib.symbols) {@@ -455,7 +468,7 @@ function editSymbolField(id, field, value) {     s[field] = value.trim();     if (field === 'footprint') { s.fpNick = value.includes(':') ? value.split(':')[0] : ''; s.fpName = value.includes(':') ? value.split(':').slice(1).join(':') : value.trim(); }   }-  if (!s.pendingAdd) s.pendingEdit = true;   // mark as changed (unless it's a brand-new symbol)+  if (!s.pendingAdd) { s.pendingEdit = true; s.editedFields = s.editedFields || {}; s.editedFields[field] = true; }  // track which fields changed   recomputeUsage();   commit('edit ' + field + ' · ' + s.name);   save(); return s;@@ -751,7 +764,7 @@ async function restoreTrash() { // pending-add (green) flags on everything that was just written. function pruneAfterSync() {   DB.libs.symbolLibs = DB.libs.symbolLibs.filter(l => !l.pendingDelete);-  for (const l of DB.libs.symbolLibs) { l.symbols = l.symbols.filter(s => !s.pendingDelete); for (const s of l.symbols) { delete s.pendingAdd; delete s.pendingEdit; } }+  for (const l of DB.libs.symbolLibs) { l.symbols = l.symbols.filter(s => !s.pendingDelete); for (const s of l.symbols) { delete s.pendingAdd; delete s.pendingEdit; delete s.editedFields; } }   DB.libs.footprintLibs = DB.libs.footprintLibs.filter(l => !l.pendingDelete);   for (const l of DB.libs.footprintLibs) { l.footprints = l.footprints.filter(f => !f.pendingDelete); for (const f of l.footprints) f.added = false; }   DB.libs.models3d = DB.libs.models3d.filter(m => !m.pendingDelete); for (const m of DB.libs.models3d) m.added = false;@@ -866,7 +879,7 @@ async function syncOneLibrary(mode, nick) {       log.push('deleted symbol library ' + nick);     } else {       log.push(await writeSymLib(lib));-      lib.symbols = lib.symbols.filter(s => !s.pendingDelete); for (const s of lib.symbols) delete s.pendingAdd;+      lib.symbols = lib.symbols.filter(s => !s.pendingDelete); for (const s of lib.symbols) { delete s.pendingAdd; delete s.pendingEdit; delete s.editedFields; }     }   } else if (mode === 'footprint') {     const lib = findFpLib(nick); if (!lib) throw new Error('footprint library not found: ' + nick);@@ -1261,7 +1274,7 @@ const server = http.createServer(async (req, res) => {  // rebuild usage + history root on boot if we have persisted libs if (DB.libs && (DB.libs.symbolLibs.length || DB.libs.footprintLibs.length || DB.libs.models3d.length)) {-  reDeriveMpn(); recomputeUsage(); historyInit(DB.probe && DB.probe.probedAt ? 'restored' : 'initial');+  reDeriveMpn(); recomputeUsage(); backfillEditedFields(); historyInit(DB.probe && DB.probe.probedAt ? 'restored' : 'initial'); } server.listen(PORT, () => {   console.log(`kicad-library-manager listening on http://localhost:${PORT}`);