← Commit history

2.11.9: colon-name strip, title-verified evidence, rescan recovery, Demo in the Say card

John Lauer ·cbbed1b743 ·1mo ago ·parent 73ea6b8
7 files changed +60−35
Cargo.lock+1−1
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"  [[package]] name = "adom-lbr"-version = "2.11.8"+version = "2.11.9" dependencies = [  "altium-codec",  "anyhow",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "2.11.8"+version = "2.11.9" edition = "2021" description = "adom-lbr generator / EDA translator: the canonical adom-lbr JSON is the hub, with native converters to Altium (.SchLib/.PcbLib/.IntLib), EAGLE/Fusion (.lbr), and KiCad. Altium output is written natively — no Altium needed at conversion time." 
VERSION+1−1
@@ -1 +1 @@-2.11.8+2.11.9
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-lbr",-  "version": "2.11.8",+  "version": "2.11.9",   "type": "app",   "description": "adom-lbr \u2014 the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad \u21c4 Altium \u21c4 EAGLE/Fusion) through one canonical adom-lbr JSON \u2014 symbol + footprint + 3D + pin\u2194pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.",   "dependencies": {},
page.json+9−12
@@ -1,8 +1,8 @@ {   "slug": "adom-lbr",-  "version": "2.11.8",+  "version": "2.11.9",   "type": "app",-  "description": "adom-lbr — the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad ⇄ Altium ⇄ EAGLE/Fusion) through one canonical adom-lbr JSON — symbol + footprint + 3D + pin↔pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.",+  "description": "adom-lbr \u2014 the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad \u21c4 Altium \u21c4 EAGLE/Fusion) through one canonical adom-lbr JSON \u2014 symbol + footprint + 3D + pin\u2194pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.",   "dependencies": {},   "tags": [     "eda",@@ -52,24 +52,21 @@   ],   "files": [     "bin/**",-    "assets/**",     "SKILL.md",-    "skills/**",     "README.md",     "install.sh",     "uninstall.sh",-    "screenshots/**",-    "dockbar.json"+    "screenshots/**"   ],-  "discovery_pitch": "Convert electronic-component symbols + footprints between any two EDA tools — KiCad, Altium (written natively, no Altium needed), and EAGLE/Fusion — through one canonical adom-lbr JSON, silk/courtyard/fab layers preserved. Plus a Manager to browse and inspect any wiki part.",+  "discovery_pitch": "Convert electronic-component symbols + footprints between any two EDA tools \u2014 KiCad, Altium (written natively, no Altium needed), and EAGLE/Fusion \u2014 through one canonical adom-lbr JSON, silk/courtyard/fab layers preserved. Plus a Manager to browse and inspect any wiki part.",   "sample_prompts": [     {       "prompt": "Convert this KiCad symbol and footprint to an Altium SchLib and PcbLib",-      "label": "KiCad → Altium"+      "label": "KiCad \u2192 Altium"     },     {       "prompt": "Import this Altium footprint into the adom-lbr JSON",-      "label": "Altium → JSON"+      "label": "Altium \u2192 JSON"     },     {       "prompt": "Bundle this part into a single Altium IntLib",@@ -77,15 +74,15 @@     },     {       "prompt": "Convert this Altium SchLib and PcbLib to a KiCad symbol and footprint",-      "label": "Altium → KiCad"+      "label": "Altium \u2192 KiCad"     },     {       "prompt": "Export this adom-lbr JSON to a KiCad .kicad_sym and .kicad_mod",-      "label": "JSON → KiCad"+      "label": "JSON \u2192 KiCad"     },     {       "prompt": "Open the adom-lbr Manager to browse wiki components and view the layer stackup",       "label": "Browse parts"     }   ]-}\ No newline at end of file+}
src/manage.html+36−18
@@ -262,7 +262,6 @@     <div class="brand"><img src="favicon.svg" alt="">adom-lbr <small>Dashboard</small></div>     <div class="leds" id="leds" title="live status from /api/status"></div>     <span class="ver" id="ver"></span>-    <button id="btnDemo" title="A guided tour: the dashboard drives itself and shows you everything it can do"><svg viewBox="0 0 24 24" width="13" height="13" aria-hidden="true"><path fill="currentColor" d="M8 5v14l11-7z"/></svg>Demo</button>     <button class="iconbtn" id="btnSettings" title="Settings" aria-label="Settings"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M12 15.5A3.5 3.5 0 0 1 8.5 12A3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5a3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.64.07-.97s-.03-.66-.07-1l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.39-1.06-.73-1.69-.98l-.37-2.65A.506.506 0 0 0 14 2h-4c-.25 0-.46.18-.5.42l-.37 2.65c-.63.25-1.17.59-1.69.98l-2.49-1c-.22-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 11c-.04.34-.07.67-.07 1s.03.65.07.97l-2.11 1.66c-.19.15-.25.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1.01c.52.4 1.06.74 1.69.99l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.63-.26 1.17-.59 1.69-.99l2.49 1.01c.22.08.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64z"/></svg></button>   </header>   <nav id="sendbar" aria-label="Send the open part to your EDA tools">@@ -283,6 +282,7 @@   <section id="say" aria-labelledby="sayTitle" hidden>     <button class="iconbtn say-close" id="sayClose" aria-label="Hide the Say it strip" title="Hide this strip. It stays hidden until you turn it back on in Settings (Say it strip)."><svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg></button>     <div class="say-head"><h2 id="sayTitle">Say it to your AI</h2>+    <button id="btnDemo" title="A guided tour: the dashboard drives itself and shows you everything it can do"><svg viewBox="0 0 24 24" width="13" height="13" aria-hidden="true"><path fill="currentColor" d="M8 5v14l11-7z"/></svg>Demo</button>     <span class="say-sub">You do not drive this dashboard by hand. You ask your AI, using the word <b>lbr</b>. Click a phrase (or Try it) to copy it, then paste it into any Adom AI thread.</span></div>     <div class="say-list" id="phrases"></div>   </section>@@ -957,14 +957,22 @@ function findShots(o,out,depth){out=out||[];depth=depth||0;   if(typeof (o.thumbnailDataUrl||o.image||o.base64||o.png)==='string'&&(o.thumbnailDataUrl||o.image||o.base64||o.png).length>200)out.push(o);   else for(const v of Object.values(o))findShots(v,out,depth+1);   return out;}-async function kicadEvidence(label){-  // FINAL frames: ask KiCad for its window state with images after the chain lands-  sp.step('capturing evidence…');-  const r=await abVerb('kicad_state',{includeImages:true});-  const shots=findShots(r.j);-  if(!shots.length){sp.log('  (no evidence frames came back from kicad_state)');return;}-  shots.slice(0,4).forEach(x=>sp.shot(x.thumbnailDataUrl||x.image||x.base64||x.png,x.title||label));-  sp.log('  evidence: '+shots.length+' frame(s) rendered below');+// Evidence honesty (ab demo doctrine, John 2026-08-20): render ONLY the frames+// the verb returned FOR THIS REQUEST, and verify their titles actually name the+// part. Never screenshot "whatever KiCad is showing" - that is how a picture of+// the WRONG part ends up under a green tick.+function renderEvidence(frames,expectTokens,cmd){+  if(!frames.length){sp.log('  (no evidence frames came back from '+cmd+'; check the KiCad window itself)');return true;}+  let allVerified=true;+  frames.slice(0,4).forEach(x=>{+    const title=x.title||x.step||'frame';+    const hit=expectTokens.some(t=>t&&title.toLowerCase().includes(t.toLowerCase()));+    if(!hit)allVerified=false;+    sp.shot(x.thumbnailDataUrl||x.image||x.base64||x.png,(hit?'':'⚠ unverified: ')+title);+  });+  if(allVerified)sp.log('  evidence: '+frames.length+' frame(s), titles name the part');+  else sp.log('  WARNING: some evidence titles do not name this part; the editor may be showing something else');+  return allVerified; }  // ---- KiCad: install once per part, then the show verbs ----@@ -1002,15 +1010,25 @@ async function sendKicad(kind){   const [cmd,args]=shows[kind];   sp.bar(80);sp.step('opening in KiCad…');   sp.log(cmd+' '+JSON.stringify(args));-  const r=await abVerb(cmd,args);-  if(!(r.s===200&&r.j&&r.j.success!==false&&!r.j.errorCode))throw new Error(cmd+': '+((r.j&&(r.j.error||r.j.errorCode))||r.s));-  sp.log('  '+cmd+' ok');-  // the show verb may carry the final frames itself; else ask kicad_state-  const inline=findShots(r.j);-  if(inline.length){inline.slice(0,4).forEach(x=>sp.shot(x.thumbnailDataUrl||x.image||x.base64||x.png,x.title||kind));sp.log('  evidence: '+inline.length+' frame(s) from '+cmd);}-  else await kicadEvidence(kind.replace('kicad-',''));-  sp.bar(100);sp.step('done — open in KiCad');-  toast('Open in KiCad: '+kind.replace('kicad-',''),false,'success');+  let r=await abVerb(cmd,args);+  const failed=x=>!(x.s===200&&x.j&&x.j.success!==false&&!x.j.errorCode);+  if(failed(r)&&r.j&&(r.j.errorCode==='library_cache_stale'||String(r.j.error||'').includes('indexed its libraries'))){+    // The open editor indexed its libraries before our install. The bridge's+    // own remedy: rescan:true reopens the editor to re-index (~1 minute cold+    // library scan; it never closes a HUMAN-opened editor without this).+    sp.log('  editor library index is stale; reopening with rescan:true (the cold re-index takes about a minute)');+    sp.step('re-indexing KiCad’s libraries (about a minute)…');sp.bar(null);+    r=await abVerb(cmd,Object.assign({rescan:true},args));+  }+  if(failed(r)){+    if(r.j&&r.j._hint)sp.log('  hint: '+r.j._hint);+    throw new Error(cmd+': '+((r.j&&(r.j.error||r.j.errorCode))||r.s));+  }+  sp.log('  '+cmd+' ok'+((r.j&&r.j.autoRescanned)?' (editor re-indexed)':''));+  const bare=n=>String(n||'').split(':').pop();+  const verified=renderEvidence(findShots(r.j),[bare(k.symbol),k.footprint,k.lib+':'],cmd);+  sp.bar(100);sp.step(verified?'done — open in KiCad':'done, but verify the KiCad window yourself');+  toast('Open in KiCad: '+kind.replace('kicad-',''),false,verified?'success':undefined); } // ---- Fusion: one call builds + proves the library, then poll the verdict ---- async function sendFusion(){
src/manage.rs+11−1
@@ -1161,10 +1161,20 @@ fn export_part(slug: &str) -> Value {         return json!({"status":"error","error":format!("invalid slug: {slug:?}")});     }     let mpn = up(slug);-    let (part, src) = match load_part(slug, &mpn) {+    let (mut part, src) = match load_part(slug, &mpn) {         Ok(v) => v,         Err(e) => return json!({"status":"error","error":e}),     };+    // Wiki sources often carry a lib-qualified symbol name ("MCU_ST_STM32H5:+    // STM32H563RGTx"). Shipping the colon into the installed library breaks+    // every downstream search (KiCad's editor reads it as lib:name and can+    // never match it, which presented as an unfixable library_cache_stale on+    // AdomLapper, 2026-08-23). Strip to the bare name.+    if let Some(tail) = part.symbol.name.rsplit(':').next().map(String::from) {+        if !tail.is_empty() && tail != part.symbol.name {+            part.symbol.name = tail;+        }+    }     let sym_txt = export_kicad_symbol(&part.symbol);     let fp_txt = export_kicad_footprint(&part.footprint);     // pin->pad map from the hub's connect[] (only the disagreements matter)