app
Adom Library
Public Made by Adomby adom
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.
← Commit history
2.11.8: Demo tour; Send-to progress panel (live console + evidence); Say strip margins
8 files changed
+254−32
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-lbr"-version = "2.11.7"+version = "2.11.8" dependencies = [ "altium-codec", "anyhow",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "2.11.7"+version = "2.11.8" 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."
README.md+4−1@@ -58,7 +58,10 @@ this dashboard by hand: you ask your AI, using the word **lbr** ("validate my lbr for fusion"). Click a phrase to copy it; **Try it** stages it, unsent, into this app's own AI thread. Click a phrase in the Say it strip to copy it. The cog opens Settings: whose boards count as My projects, the wiki list cache, and package Update/Uninstall with a live-console. The launch contract lives in+console. A **Demo** button in the+header runs a guided tour that drives the whole dashboard for you, and every+Send-to run shows a live panel: progress bar, streaming console, and evidence+screenshots of what actually opened. The launch contract lives in `dockbar.json` at the root of this page repo; the bundled `adom-lbr-dockbar` skill documents every piece of it for your AI.
VERSION+1−1@@ -1 +1 @@-2.11.7+2.11.8
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-lbr",- "version": "2.11.7",+ "version": "2.11.8", "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.7",+ "version": "2.11.8", "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" } ]-}+}
skills/adom-lbr-dockbar/SKILL.md+11−1@@ -120,7 +120,17 @@ one shared verb surface. The chains: `kicad_show_symbol` / `kicad_show_footprint` / `kicad_show_3d_chip` / `kicad_show_library` (the all-three proof). - Fusion: `fusion_create_library {libraryPath, stepPath, name}` (shape A, one- call = one consent), then poll `fusion_prove_result` for the verdict.+ call = one consent), then poll `fusion_prove_result` for the verdict; the+ final fetch passes `{inlineFrames:true}` for the four evidence frames+ (symbol, footprint, 3D chip, library) as browser-renderable thumbnails.++Every send runs inside a PROGRESS PANEL (bottom right): a progress bar, the+live streaming console of each verb and its result, and evidence screenshots,+matching ab's own web-control demo so the user is never running blind.+Evidence is FINAL frames only, never a mid-operation screenshot (a step shot+usually still shows the previous part and reads as "you opened the wrong+thing"; John, 2026-08-20). KiCad evidence comes from the show verb's own+reply, else `kicad_state {includeImages:true}`. Detection (`GET /health`) lights the toolbar's dot. It works when the page is served at its 127.0.0.1 address, which is exactly how the dock opens it; a
src/manage.html+226−14@@ -37,16 +37,16 @@ .iconbtn:active{transform:translateY(1px) scale(.96)} .iconbtn:focus-visible{outline:2px solid var(--acc);outline-offset:2px} /* ---- "Say it": the family card (adom-parts-search look), dismissible via the X / Settings ---- */- #say{position:relative;background:var(--elevated);border:1px solid var(--border);border-left:3px solid color-mix(in srgb,var(--accent) 45%,transparent);border-radius:12px;margin:10px 12px 2px;padding:12px 44px 12px 14px;animation:fadein .25s ease}+ #say{position:relative;background:var(--elevated);border:1px solid var(--border);border-left:3px solid color-mix(in srgb,var(--accent) 45%,transparent);border-radius:12px;margin:16px 18px 14px;padding:16px 48px 16px 20px;animation:fadein .25s ease} #say[hidden]{display:none} @keyframes fadein{from{opacity:0}to{opacity:1}} .say-close{position:absolute;top:8px;right:8px;width:28px;height:28px;color:var(--text-2)} .say-close svg{width:16px;height:16px}- .say-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 12px;margin-bottom:8px}+ .say-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 12px;margin-bottom:11px} .say-head h2{margin:0;font:600 15px var(--brand-heading);color:var(--text)} #say .say-sub{font-size:12.5px;color:var(--text-2);max-width:80ch} #say .say-sub b{color:var(--acc)}- .say-list{display:flex;flex-wrap:wrap;gap:8px}+ .say-list{display:flex;flex-wrap:wrap;gap:9px} .phrase{display:inline-flex;align-items:stretch;border:1px solid var(--border);border-radius:999px;background:#0d0f14;overflow:hidden;font-size:13px;transition:border-color .15s ease} .phrase:hover{border-color:var(--accent)} .phrase .txt{padding:6px 10px 6px 14px;cursor:copy;color:var(--ink);background:none;border:none;font:500 13px var(--brand-body);transition:background .15s}@@ -54,8 +54,43 @@ .phrase .try{padding:6px 12px 6px 9px;border-left:1px solid var(--border);color:var(--acc);cursor:pointer;font-weight:600;background:transparent;border-top:none;border-right:none;border-bottom:none;font-size:11px} .phrase .try:hover{background:#12302f} .phrase .txt:active,.phrase .try:active{transform:translateY(1px)}+ /* ---- Send progress panel: bar + live console + final-frame evidence ---- */+ #sendpanel{position:fixed;right:14px;bottom:14px;z-index:1500;width:min(480px,92vw);background:var(--elevated);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:12px;box-shadow:0 16px 48px #000c;display:none}+ #sendpanel.show{display:block}+ #sendpanel .sp-head{display:flex;align-items:center;gap:10px;padding:10px 12px 8px 14px}+ #sendpanel .sp-head h3{margin:0;font:600 13.5px var(--brand-heading);color:var(--text);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}+ #sendpanel .sp-body{padding:0 14px 12px}+ .sp-bar{height:6px;border-radius:999px;background:var(--overlay);overflow:hidden;margin:2px 0 4px}+ .sp-bar i{display:block;height:100%;background:var(--accent);border-radius:999px;width:0%;transition:width .4s ease}+ .sp-bar.ind i{width:30%;animation:spslide 1.2s ease-in-out infinite alternate}+ @keyframes spslide{from{margin-left:0}to{margin-left:70%}}+ .sp-step{font:11.5px var(--brand-body);color:var(--text-2);margin-bottom:6px;min-height:15px}+ #sp-console{background:#0b0e13;border:1px solid var(--border);border-radius:8px;padding:8px 10px;font:11px/1.5 ui-monospace,monospace;color:#c9d1d9;max-height:150px;overflow:auto;white-space:pre-wrap;margin-bottom:8px}+ #sp-shots{display:flex;gap:8px;flex-wrap:wrap}+ #sp-shots figure{margin:0;width:104px}+ #sp-shots img{width:104px;height:70px;object-fit:cover;border:1px solid var(--border);border-radius:6px;background:#0d0f14;cursor:zoom-in;display:block}+ #sp-shots img:hover{border-color:var(--accent)}+ #sp-shots figcaption{font:10px var(--brand-body);color:var(--text-2);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}+ #sp-zoom{position:fixed;inset:0;z-index:4000;background:rgba(0,0,0,.75);display:none;align-items:center;justify-content:center;cursor:zoom-out}+ #sp-zoom img{max-width:92vw;max-height:92vh;border:1px solid var(--border);border-radius:8px}+ /* ---- Demo tour: spotlight ring + narration card ---- */+ #tour-ring{position:fixed;z-index:3000;border:2px solid var(--accent);border-radius:10px;box-shadow:0 0 0 100vmax rgba(0,0,0,.55),0 0 18px var(--accent);pointer-events:none;transition:all .35s ease;display:none}+ #tour-card{position:fixed;z-index:3001;width:min(400px,90vw);background:var(--elevated);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:12px;padding:14px 16px;box-shadow:0 16px 48px #000c;display:none}+ #tour-card h3{margin:0 0 6px;font:700 15px var(--brand-heading);color:var(--text);display:flex;align-items:center;gap:8px}+ #tour-card .step-n{font:600 10px var(--brand-body);color:var(--on-accent);background:var(--accent);border-radius:999px;padding:2px 8px}+ #tour-card p{margin:0 0 12px;font:13px/1.55 var(--brand-body);color:var(--text-body)}+ #tour-card p b{color:var(--text)}+ #tour-card .row{display:flex;gap:8px;align-items:center}+ #tour-card .row .spacer{flex:1;font:11px var(--brand-body);color:var(--text-2)}+ #tour-card button{background:#1a2130;border:1px solid var(--border);color:var(--ink);border-radius:7px;padding:6px 14px;font:600 12.5px var(--brand-heading);cursor:pointer}+ #tour-card button:hover{border-color:var(--accent)}+ #tour-card button.primary{background:var(--accent);color:var(--on-accent);border-color:var(--accent)}+ #tour-card button:active{transform:translateY(1px)}+ #btnDemo{display:inline-flex;align-items:center;gap:6px;background:#12302f;border:1px solid var(--acc);color:var(--acc);border-radius:8px;padding:4px 12px;font:700 12px var(--brand-heading);cursor:pointer;transition:filter .12s,transform .06s}+ #btnDemo:hover{filter:brightness(1.2)}+ #btnDemo:active{transform:translateY(1px)} /* ---- Send-to toolbar: first-class web-control buttons (KiCad / Fusion) ---- */- #sendbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:6px 14px;background:var(--panel);border-bottom:1px solid var(--edge);font-size:12px}+ #sendbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 18px;background:var(--panel);border-bottom:1px solid var(--edge);font-size:12px} #sendbar .grp{display:flex;align-items:center;gap:6px} #sendbar .lab{color:var(--mut);font:600 11px var(--brand-heading);text-transform:uppercase;letter-spacing:.05em} #sendbar button{background:#1a2130;border:1px solid var(--edge);color:var(--ink);border-radius:7px;padding:5px 10px;font-size:12px;cursor:pointer;transition:border-color .12s,background .12s,transform .06s}@@ -227,6 +262,7 @@ <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">@@ -268,6 +304,27 @@ <main id="view"><div id="hint">← pick a board, then a component, to open its viewer</div></main> </div> <div id="toast" role="status" aria-live="polite"></div>+ <div id="sendpanel" role="status" aria-live="polite">+ <div class="sp-head"><h3 id="sp-title"></h3><button class="iconbtn" id="sp-close" aria-label="Close" title="Close"><svg viewBox="0 0 24 24" width="16" height="16" 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>+ <div class="sp-body">+ <div class="sp-bar" id="sp-bar"><i></i></div>+ <div class="sp-step" id="sp-step"></div>+ <pre id="sp-console"></pre>+ <div id="sp-shots"></div>+ </div>+ </div>+ <div id="sp-zoom"><img alt="evidence, full size"></div>+ <div id="tour-ring"></div>+ <div id="tour-card" role="dialog" aria-labelledby="tour-title">+ <h3><span class="step-n" id="tour-n"></span><span id="tour-title"></span></h3>+ <p id="tour-text"></p>+ <div class="row">+ <button id="tour-back">Back</button>+ <span class="spacer" id="tour-progress"></span>+ <button id="tour-exit">Exit</button>+ <button class="primary" id="tour-next">Next</button>+ </div>+ </div> <dialog id="dlgSettings" class="settings" tabindex="-1" aria-labelledby="settingsTitle"> <div class="settings-head" id="settingsHead">@@ -848,11 +905,13 @@ async function stageFile(dir,name,b64){ return r.j.path||path; } let offset=0,i=0,total=Math.ceil(b64.length/CHUNK_B64),finalPath=path;+ sp.log(' '+name+' is large; sending '+total+' chunks via write_file_chunk'); while(i<total){ const slice=b64.substr(i*CHUNK_B64,CHUNK_B64),last=(i===total-1); const r=await abVerb('write_file_chunk',{path,dataB64:slice,offset,final:last}); if(!(r.s===200&&r.j&&r.j.success))throw new Error('write_file_chunk '+name+' @'+offset+': '+((r.j&&(r.j.error||r.j.errorCode))||r.s)); finalPath=r.j.path||finalPath; offset=r.j.nextOffset; i++;+ sp.step('staging '+name+': chunk '+i+'/'+total); } return finalPath; }@@ -862,28 +921,77 @@ async function stagePart(slug){ const dir='~/Documents/adom-lbr/'+ex.mpn+'/'; toast('Staging files onto your PC (the Bridge will ask your permission)…'); const out={mpn:ex.mpn,paths:{}};+ const names=Object.keys(ex.files);+ let n=0; for(const [name,f] of Object.entries(ex.files)){- out.paths[name.split('.').pop()]=await stageFile(dir,name,f.b64);+ sp.step('staging '+name+' ('+(n+1)+'/'+names.length+')');+ const path=await stageFile(dir,name,f.b64);+ out.paths[name.split('.').pop()]=path;+ sp.log(' staged '+name+' -> '+path); n++;+ sp.bar(10+Math.round(30*n/names.length)); } stagedCache[slug]=out; return out; }+// ---- The progress panel: bar + streaming console + FINAL-frame evidence ----+// Matches ab's own web-control demo: the user is never running blind. Evidence+// is FINAL frames only; a mid-operation screenshot usually still shows the+// previous part and reads as "you opened the wrong thing" (John, 2026-08-20).+const sp={+ open(title){this.reset();document.getElementById('sp-title').textContent=title;document.getElementById('sendpanel').classList.add('show');},+ reset(){document.getElementById('sp-console').textContent='';document.getElementById('sp-shots').innerHTML='';this.bar(0);this.step('');},+ close(){document.getElementById('sendpanel').classList.remove('show');},+ bar(pct){const b=document.getElementById('sp-bar');if(pct==null){b.classList.add('ind');}else{b.classList.remove('ind');b.firstElementChild.style.width=Math.max(0,Math.min(100,pct))+'%';}},+ step(t){document.getElementById('sp-step').textContent=t||'';},+ log(line){const c=document.getElementById('sp-console');c.textContent+=(c.textContent?'\n':'')+line;c.scrollTop=c.scrollHeight;},+ shot(img,label){const src=img.startsWith('data:')?img:'data:image/png;base64,'+img;+ const f=document.createElement('figure');const im=document.createElement('img');im.src=src;im.alt=label;+ im.onclick=()=>{const z=document.getElementById('sp-zoom');z.querySelector('img').src=src;z.style.display='flex';};+ const cap=document.createElement('figcaption');cap.textContent=label;cap.title=label;+ f.appendChild(im);f.appendChild(cap);document.getElementById('sp-shots').appendChild(f);},+};+document.getElementById('sp-close').onclick=()=>sp.close();+document.getElementById('sp-zoom').onclick=e=>{e.currentTarget.style.display='none';};+// Recursively find screenshot-shaped objects ({image|base64|png, title?}) in a verb reply.+function findShots(o,out,depth){out=out||[];depth=depth||0;+ if(!o||typeof o!=='object'||depth>6)return out;+ 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');+}+ // ---- KiCad: install once per part, then the show verbs ---- const kicadCache={}; async function kicadInstall(slug){ if(kicadCache[slug])return kicadCache[slug]; const st=await stagePart(slug);- toast('Installing into KiCad’s Adom library…');+ sp.bar(50);sp.step('installing the symbol into KiCad…');+ sp.log('kicad_install_symbol '+st.paths.kicad_sym); const rs=await abVerb('kicad_install_symbol',{filePath:st.paths.kicad_sym,quietInstall:true}); if(!(rs.s===200&&rs.j&&rs.j.success))throw new Error('kicad_install_symbol: '+((rs.j&&rs.j.error)||rs.s)); const info={lib:rs.j.libraryName||'Adom',symbol:(rs.j.symbolNames&&rs.j.symbolNames[0])||st.mpn};+ sp.log(' installed into the \''+info.lib+'\' library'+(info.symbol!==st.mpn?' as \''+info.symbol+'\'':''));+ sp.bar(65);sp.step('installing the footprint (3D model wired by the bridge)…'); const fpArgs={filePath:st.paths.kicad_mod,quietInstall:true}; if(st.paths.step)fpArgs.model={filePath:st.paths.step};+ sp.log('kicad_install_footprint '+st.paths.kicad_mod+(st.paths.step?' + model':'')); const rf=await abVerb('kicad_install_footprint',fpArgs); if(!(rf.s===200&&rf.j&&rf.j.success))throw new Error('kicad_install_footprint: '+((rf.j&&rf.j.error)||rf.s)); info.footprint=rf.j.footprintName||st.mpn;+ sp.log(' footprint registered as \''+info.footprint+'\''); kicadCache[slug]=info; return info; } async function sendKicad(kind){+ sp.open('Sending '+String(currentPart).toUpperCase()+' to KiCad — '+kind.replace('kicad-',''));+ sp.bar(3);sp.step('preparing files…');+ sp.log('GET api/export/'+currentPart); const k=await kicadInstall(currentPart); const shows={ 'kicad-symbol': ['kicad_show_symbol', {symbolName:k.symbol,libraryName:k.lib}],@@ -892,29 +1000,53 @@ async function sendKicad(kind){ 'kicad-library': ['kicad_show_library', {libraryName:k.lib,partName:k.symbol}], }; const [cmd,args]=shows[kind];- toast('Opening in KiCad…');+ 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));- toast('Open in KiCad: '+(kind.replace('kicad-','')));+ 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'); } // ---- Fusion: one call builds + proves the library, then poll the verdict ---- async function sendFusion(){+ sp.open('Sending '+String(currentPart).toUpperCase()+' to Fusion — library');+ sp.bar(3);sp.step('preparing files…');+ sp.log('GET api/export/'+currentPart); const st=await stagePart(currentPart); if(!st.paths.lbr)throw new Error('no .lbr could be generated for this part'); const args={libraryPath:st.paths.lbr,name:st.mpn}; if(st.paths.step)args.stepPath=st.paths.step;- toast('Asking the Fusion bridge to build the library…');+ sp.bar(45);sp.step('Fusion bridge: convert → bind 3D → prove…');+ sp.log('fusion_create_library '+st.paths.lbr+(st.paths.step?' + step':'')); const r=await abVerb('fusion_create_library',args); if(!(r.s===200&&r.j&&(r.j.started||r.j.success)))throw new Error('fusion_create_library: '+((r.j&&(r.j.error||r.j.errorCode))||r.s));+ sp.bar(null); // the chain runs in the background with its own pacing let lastStage='';- for(let i=0;i<60;i++){+ for(let i=0;i<80;i++){ await new Promise(res=>setTimeout(res,3000)); const pr=await abVerb('fusion_prove_result',{}); const j=pr.j||{};- if(j.stage&&j.stage!==lastStage){lastStage=j.stage;toast('Fusion: '+j.stage);}- if(j.verdict){toast('Fusion library '+(j.verdict==='proven'?'built and PROVEN':'finished: '+j.verdict),j.verdict!=='proven');return;}- if(j.stage==='done')return void toast('Fusion chain finished');+ if(j.stage&&j.stage!==lastStage){lastStage=j.stage;sp.step('Fusion: '+j.stage);sp.log(' '+j.stage);}+ if(j.verdict||j.stage==='done'){+ // FINAL fetch with inlineFrames: the four evidence frames (symbol,+ // footprint, 3D chip, library) as browser-renderable thumbnails+ sp.step('fetching the evidence frames…');+ const fin=await abVerb('fusion_prove_result',{inlineFrames:true});+ const shots=findShots(fin.j);+ shots.slice(0,6).forEach(x=>sp.shot(x.thumbnailDataUrl||x.image||x.base64||x.png,x.title||x.step||'frame'));+ if(shots.length)sp.log(' evidence: '+shots.length+' frame(s) rendered below');+ const v=j.verdict||(fin.j&&fin.j.verdict);+ sp.bar(100);sp.step(v?('verdict: '+v):'chain finished');+ toast('Fusion library '+(v==='proven'?'built and PROVEN':('finished: '+(v||'done'))),v!=='proven',v==='proven'?'success':undefined);+ return;+ } }+ sp.step('still working — check the Fusion window'); toast('Fusion is still working; check the Fusion window',true); } document.querySelectorAll('#sendbar button').forEach(b=>{@@ -922,11 +1054,91 @@ document.querySelectorAll('#sendbar button').forEach(b=>{ if(!currentPart){toast('Open a part first (Parts tab, or a board’s component)',true);return;} b.dataset.busy='1';updateSendbar(); try{ b.dataset.send==='fusion-library'?await sendFusion():await sendKicad(b.dataset.send); }- catch(e){toast(String(e.message||e),true);}+ catch(e){sp.log('FAILED: '+String(e.message||e));sp.bar(0);sp.step('failed — see the console above');toast(String(e.message||e),true);} b.dataset.busy='';updateSendbar(); }; }); +// ===================== The Demo: a guided, self-driving tour =====================+// John, 2026-08-23: "even i don't really know how to use this app" - the Demo+// button walks a newbie through EVERYTHING, driving the real UI as it goes.+const SAMPLE='stm32h563rgtx';+function demoEl(sel){return document.querySelector(sel);}+async function demoOpenSample(){+ if(currentPart!==SAMPLE){ try{ await openPart(SAMPLE); }catch(e){} }+}+function demoHoverPin(){+ // light a real pin the way a mouse hover does, so the highlight + tooltip show+ const hit=document.querySelector('#view .card .body [data-num]');+ if(!hit)return null;+ document.querySelectorAll('#view .pinhi').forEach(x=>x.classList.remove('pinhi'));+ const num=hit.dataset.num;+ document.querySelectorAll('#view [data-num="'+CSS.escape(num)+'"]').forEach(x=>x.classList.add('pinhi'));+ const tip=document.getElementById('tip');+ if(tip){const r=hit.getBoundingClientRect();tip.innerHTML='<b>'+num+'</b> '+(hit.dataset.name||'');tip.style.display='block';tip.style.left=(r.right+10)+'px';tip.style.top=(r.top-6)+'px';}+ return hit;+}+function demoUnhover(){document.querySelectorAll('#view .pinhi').forEach(x=>x.classList.remove('pinhi'));const tip=document.getElementById('tip');if(tip)tip.style.display='none';}+const TOUR=[+ {sel:'#hdr .brand',title:'This is adom-lbr',text:'The <b>EDA library converter</b>: symbols, footprints and 3D chips, moved between <b>KiCad, Fusion and Altium</b>. Everything you see comes live from the wiki, which is the library. This tour drives the app for real; you just read along.'},+ {sel:'#say',title:'You ask your AI',text:'You do not drive this dashboard by hand. You ask your AI using the word <b>lbr</b>. These are real prompts: click one (or Try it) and it is copied for you to paste into any Adom AI thread.',pre:async()=>{if(settings&&settings.show_say_strip===false){save({show_say_strip:true});}}},+ {sel:'#modes',title:'Projects and Parts',text:'<b>Projects</b> lists your boards from the wiki, each opening into its component list. <b>Parts</b> browses every component page on the wiki directly, nothing needs downloading or owning first.',pre:async()=>{setMode('projects');}},+ {sel:'#rows',title:'The whole wiki library',text:'Every part page on the wiki, searchable. Type a part number or keyword to narrow it. Watch: the tour opens an industrial MCU, an STM32 H-line, for you.',pre:async()=>{setMode('parts');}},+ {sel:'#view',title:'The part, in full',text:'One click renders everything from the part\'s canonical data: the schematic <b>symbol</b>, the <b>footprint</b>, and the <b>3D model</b>. Drag to pan, scroll to zoom on any of them; double-click resets.',pre:demoOpenSample},+ {sel:'#view .card:nth-of-type(1)',title:'Hover any pin',text:'Pins and pads know who they are. Hover one and it lights up in BOTH the symbol and the footprint, with its number and name. The tour is holding one lit for you now.',pre:async()=>{await demoOpenSample();demoHoverPin();},post:demoUnhover},+ {sel:'#view .lfilter',title:'Layer filter',text:'The footprint\'s layers are live chips: copper, courtyard, silkscreen, fab. Untick one and that layer hides, so you can inspect exactly the geometry you care about.'},+ {sel:'#view model-viewer, #view .card:nth-of-type(3)',title:'The 3D chip',text:'The part\'s real STEP model, rendered in the page. Orbit it with a drag; it auto-rotates when idle. The same model is what gets sent to your desktop tools.'},+ {sel:'#view table',title:'The layer stackup',text:'The honest map of what each footprint element becomes <b>per EDA tool</b>: courtyard to F.CrtYd in KiCad and Mechanical 15 in Altium, and so on. This is the translator showing its work.',pre:async()=>{const t=demoEl('#view table');if(t)t.scrollIntoView({behavior:'smooth',block:'center'});}},+ {sel:'#sendbar',title:'Send it to your tools',text:'First-class buttons: install this part into <b>KiCad</b> (symbol, footprint, 3D wired) and pop it open in the editor, or have the <b>Fusion</b> bridge build and prove the library. Adom Bridge asks your permission first, and a live panel shows the <b>progress bar, streaming console and evidence screenshots</b> as it lands, so you are never running blind.'},+ {sel:'#leds',title:'Live health',text:'Six LEDs, straight from the dashboard\'s own status: the wiki link, your sign-in, the library cache, the 3D viewer assets, the dock. Hover any LED for the detail, and the fix when one is not green.'},+ {sel:'#btnSettings',title:'Settings',text:'The cog holds the rest: the Say it strip switch, whose boards count as My projects, the list cache, and package Update/Uninstall with a live console. Every section explains itself.'},+ {sel:'#say',title:'That is the tour',text:'From here: browse <b>Parts</b>, open anything, and when you want real work done, copy a phrase and ask your AI, the word is <b>lbr</b>. Enjoy.'}+];+let tourI=-1;+function tourStop(){+ tourI=-1;demoUnhover();+ document.getElementById('tour-ring').style.display='none';+ document.getElementById('tour-card').style.display='none';+}+async function tourShow(i){+ const prev=TOUR[tourI];+ if(prev&&prev.post)try{prev.post();}catch(e){}+ if(i<0||i>=TOUR.length){tourStop();return;}+ tourI=i;const st=TOUR[i];+ if(st.pre){try{await st.pre();}catch(e){}}+ const el=demoEl(st.sel);+ const ring=document.getElementById('tour-ring'),card=document.getElementById('tour-card');+ if(el){el.scrollIntoView({behavior:'smooth',block:'nearest'});}+ await new Promise(r=>setTimeout(r,380));+ const el2=demoEl(st.sel);+ if(el2){+ const r=el2.getBoundingClientRect();+ ring.style.display='block';+ ring.style.left=(r.left-6)+'px';ring.style.top=(r.top-6)+'px';+ ring.style.width=(r.width+12)+'px';ring.style.height=(r.height+12)+'px';+ } else { ring.style.display='none'; }+ document.getElementById('tour-n').textContent=(i+1)+' / '+TOUR.length;+ document.getElementById('tour-title').textContent=st.title;+ document.getElementById('tour-text').innerHTML=st.text;+ document.getElementById('tour-progress').textContent='';+ document.getElementById('tour-back').style.visibility=i===0?'hidden':'visible';+ document.getElementById('tour-next').textContent=i===TOUR.length-1?'Done':'Next';+ card.style.display='block';+ // place the card near the ring, clamped on screen, never covering it+ const rr=el2?el2.getBoundingClientRect():{left:innerWidth/2-200,top:innerHeight/2,bottom:innerHeight/2,width:0};+ const cw=Math.min(400,innerWidth*0.9),ch=card.offsetHeight||170;+ let x=Math.max(12,Math.min(innerWidth-cw-12,rr.left));+ let y=rr.bottom+14;+ if(y+ch>innerHeight-12)y=Math.max(12,rr.top-ch-14);+ card.style.left=x+'px';card.style.top=y+'px';+}+document.getElementById('btnDemo').onclick=()=>tourShow(0);+document.getElementById('tour-next').onclick=()=>tourShow(tourI+1);+document.getElementById('tour-back').onclick=()=>tourShow(tourI-1);+document.getElementById('tour-exit').onclick=tourStop;+document.addEventListener('keydown',e=>{if(tourI>=0){if(e.key==='Escape')tourStop();if(e.key==='ArrowRight'||e.key==='Enter')tourShow(tourI+1);if(e.key==='ArrowLeft')tourShow(tourI-1);}});+addEventListener('resize',()=>{if(tourI>=0)tourShow(tourI);});+ document.getElementById('btn-update').onclick=()=>runJob('update'); (function(){const b=document.getElementById('btn-uninstall');let armed=false,t=null; b.onclick=()=>{ if(!armed){armed=true;b.textContent='Click again to uninstall';t=setTimeout(()=>{armed=false;b.textContent='Uninstall';},4000);return;}