← Commit history

Publish 1.9.248

John Lauer ·6010770acd ·1mo ago ·parent 3ba3cb5
3 files changed +262−11
.release-page-head+1−1
@@ -1 +1 @@-c928c128f99ea4bdfe323c78f376e0f64948e53a\ No newline at end of file+3ba3cb5c112857e34f6bd6ebbd3a78ee78f63dd3\ No newline at end of file
dashboard/ui/quick-actions.htmladded+254
@@ -0,0 +1,254 @@+<!doctype html>+<html lang="en">+<head>+<meta charset="utf-8">+<meta name="viewport" content="width=device-width, initial-scale=1">+<title>Fusion Quick Actions</title>+<link rel="icon" type="image/svg+xml" href="favicon.svg">+<style>+:root{+  --bg:#0d1117;--surface:#161b22;--elevated:#1c2128;--overlay:#21262d;--border:#30363d;+  --text:#e6edf3;--text-body:#c9d1d9;--text-2:#8b949e;+  --accent:#00b8b1;--accent-hover:#00d4cb;--green:#3fb950;--red:#f85149;--yellow:#d29922;--blue:#64abff;+  --sans:'Familjen Grotesk',system-ui,sans-serif;--body:'Satoshi',system-ui,sans-serif;+  --mono:'JetBrains Mono',ui-monospace,Consolas,monospace;+}+*{box-sizing:border-box;margin:0;padding:0}+*{scrollbar-width:thin;scrollbar-color:rgba(139,148,158,.38) transparent}+::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:transparent}+::-webkit-scrollbar-thumb{background:rgba(139,148,158,.32);border-radius:999px;border:2px solid transparent;background-clip:padding-box}+::-webkit-scrollbar-thumb:hover{background:rgba(0,184,177,.55);background-clip:padding-box}+@font-face{font-family:'Familjen Grotesk';src:url('https://adom.inc/fonts/FamiljenGrotesk-SemiBold.woff2') format('woff2');font-weight:600}+@font-face{font-family:'Satoshi';src:url('https://adom.inc/fonts/Satoshi-Regular.woff2') format('woff2');font-weight:400}+@font-face{font-family:'Satoshi';src:url('https://adom.inc/fonts/Satoshi-Medium.woff2') format('woff2');font-weight:500}+body{background:var(--bg);color:var(--text-body);font:15px/1.5 var(--body);min-height:100vh}+header{height:44px;display:flex;align-items:center;gap:12px;padding:0 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:50}+.brand{display:flex;align-items:center;gap:8px;font-family:var(--sans);font-weight:600;color:var(--accent);white-space:nowrap;position:relative}+.brand svg{width:16px;height:16px;stroke:var(--text)}+.brand-pop{display:none;position:absolute;top:34px;left:0;z-index:9000;background:var(--overlay);border:1px solid var(--border);border-radius:10px;padding:10px 12px;min-width:260px;font:12px var(--mono);color:var(--text-2)}+.brand:hover .brand-pop{display:block}+.brand-pop div{display:flex;justify-content:space-between;gap:16px;padding:2px 0}.brand-pop b{color:var(--text);font-weight:500}.brand-pop a{color:var(--accent);text-decoration:none}+.vsep{width:1px;height:20px;background:var(--border)}+.subject{font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}+.meta{font:12px var(--mono);color:var(--text-2)}+@media(max-width:720px){.meta{display:none}}+.actions{margin-left:auto;display:flex;gap:8px}+.tchip{display:inline-flex;align-items:center;gap:7px;background:var(--elevated);border:1px solid var(--border);border-radius:999px;padding:3px 11px;font:12px var(--mono);color:var(--text-2)}+.led{width:8px;height:8px;border-radius:50%;background:var(--text-2);flex-shrink:0}.led.on{background:var(--green)}.led.warn{background:var(--yellow)}.led.bad{background:var(--red)}+.iconbtn,.btn{font:inherit;cursor:pointer;color:var(--text);background:var(--elevated);border:1px solid var(--border);border-radius:8px;transition:background .15s ease,border-color .15s ease,transform .06s ease}+.iconbtn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:none;border-color:transparent}+.iconbtn:hover,.btn:hover{background:var(--overlay);border-color:var(--accent)}+.iconbtn:active,.btn:active{transform:translateY(1px) scale(.98)}+.btn{padding:8px 14px;font-weight:500;display:inline-flex;align-items:center;gap:8px;min-height:40px}+.btn.primary{background:var(--accent);border-color:var(--accent);color:#04201f}.btn.primary:hover{background:var(--accent-hover)}+.btn.danger{color:var(--red);border-color:rgba(248,81,73,.5)}+.btn:disabled{opacity:.5;cursor:not-allowed;transform:none}+.btn:focus-visible,.iconbtn:focus-visible,input:focus-visible{outline:2px solid var(--accent);outline-offset:2px}+main{max-width:1080px;margin:0 auto;padding:16px;display:grid;grid-template-columns:1fr 1fr;gap:16px}+@media(max-width:860px){main{grid-template-columns:1fr}}+.card{background:var(--elevated);border:1px solid var(--border);border-left:3px solid color-mix(in srgb,var(--accent) 45%,transparent);border-radius:10px;padding:14px 16px 16px;min-width:0}+.card.wide{grid-column:1/-1}+.card-title{font-size:13px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:10px;flex-wrap:wrap}+.card-status{font-size:11px;color:var(--text-2);font-weight:400}.card-status.ok{color:var(--green)}.card-status.bad{color:var(--red)}.card-status.warn{color:var(--yellow)}+.card-desc{font-size:12px;line-height:1.55;color:var(--text-2);margin:6px 0 10px}+.card-desc code,.kv code{font:11px var(--mono);background:rgba(110,118,129,.2);padding:1px 4px;border-radius:4px}+.row{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:8px}+input[type=text]{font:inherit;color:var(--text);background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:8px 10px;min-height:40px;flex:1;min-width:160px}+.kv{display:grid;grid-template-columns:max-content 1fr;gap:3px 14px;font:12px var(--mono);color:var(--text-2);margin-top:8px}+.kv b{color:var(--text);font-weight:500;word-break:break-all}+.prog{height:8px;background:var(--overlay);border:1px solid var(--border);border-radius:999px;overflow:hidden;margin-top:10px}+.prog>div{height:100%;width:0;background:var(--accent);transition:width .4s ease}+.prog.fail>div{background:var(--red)}.prog.done>div{background:var(--green)}+.stage{font:12px var(--mono);color:var(--text-2);margin-top:6px;min-height:18px}+.stage b{color:var(--text);font-weight:500}+.plan{list-style:none;margin-top:8px;font:12px var(--mono)}.plan li{display:flex;justify-content:space-between;gap:12px;padding:4px 0;border-bottom:1px solid var(--border)}+.plan li:last-child{border-bottom:0}.plan .c{color:var(--text-2)}.plan .c.measured{color:var(--green)}+.gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px;margin-top:10px}+.shot{background:var(--surface);border:1px solid var(--border);border-radius:8px;overflow:hidden}+.shot img{display:block;width:100%;height:auto;background:#000}+.shot .cap{font:11px var(--mono);color:var(--text-2);padding:6px 8px;display:flex;justify-content:space-between;gap:6px}+.tag{display:inline-block;font:10px var(--mono);letter-spacing:.04em;text-transform:uppercase;padding:1px 6px;border-radius:999px;border:1px solid var(--border);color:var(--text-2)}+.tag.proof{color:var(--accent);border-color:rgba(0,184,177,.5)}.tag.feed{color:var(--blue);border-color:rgba(100,171,255,.5)}+.lint{margin-top:10px;padding:10px 12px;border-radius:8px;border:1px solid var(--border);background:var(--surface);font-size:12px;line-height:1.5}+.lint.warn{border-color:rgba(210,153,34,.5)}.lint.ok{border-color:rgba(63,185,80,.5)}+.lint ul{margin:6px 0 0 18px}+.ask{margin-top:10px;padding:12px;border-radius:8px;border:1px solid rgba(210,153,34,.6);background:rgba(210,153,34,.08)}+.ask h4{font-size:13px;color:var(--text);margin-bottom:4px}.ask p{font-size:12px;color:var(--text-2)}+.ask ul{font:12px var(--mono);margin:6px 0 8px 18px;color:var(--text-2)}+.docs{list-style:none;font:12px var(--mono);margin-top:8px}.docs li{padding:3px 0;border-bottom:1px solid var(--border);display:flex;justify-content:space-between}.docs li:last-child{border:0}+.docs .mod{color:var(--yellow)}+.log{font:11px/1.5 var(--mono);background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:8px 10px;max-height:320px;overflow:auto;margin-top:8px;white-space:pre-wrap;word-break:break-word}+.log .ok{color:var(--green)}.log .err{color:var(--red)}.log .t{color:var(--text-2)}.log .v{color:var(--accent)}+.toasts{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);display:flex;flex-direction:column-reverse;gap:8px;z-index:9999;pointer-events:none;width:min(520px,calc(100vw - 24px))}+.toast{pointer-events:auto;background:var(--overlay);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:8px;padding:10px 12px;font-size:13px;color:var(--text);box-shadow:0 8px 24px rgba(0,0,0,.4);animation:tin .18s ease}+.toast.ok{border-left-color:var(--green)}.toast.err{border-left-color:var(--red)}+@keyframes tin{from{opacity:0;transform:translateY(6px)}to{opacity:1}}+.grid3{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:8px;margin-top:8px}+.grid3 .btn{width:100%;justify-content:center}+.hint{font-size:12px;color:var(--text-2);margin-top:8px}+.mini{font:11px var(--mono);color:var(--text-2)}+.catalog{max-height:260px;overflow:auto;margin-top:8px;font:12px var(--mono)}+.catalog div{padding:4px 0;border-bottom:1px solid var(--border)}.catalog b{color:var(--accent);font-weight:500}.catalog span{color:var(--text-2)}+</style>+</head>+<body>+<header>+  <div class="brand">+    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3 3 8l9 5 9-5-9-5z"/><path d="M3 16l9 5 9-5"/><path d="M3 12l9 5 9-5"/></svg>+    Fusion Bridge <span style="color:var(--text-2)">&middot; Quick Actions</span>+    <div class="brand-pop">+      <div><span>page</span><b>web-control quick actions demo</b></div>+      <div><span>lane</span><b>browser &rarr; 127.0.0.1:2468x &rarr; ab &rarr; fusion bridge</b></div>+      <div><span>ab</span><b id="bpAb">-</b></div>+      <div><a href="https://wiki.adom.inc/adom/fusion-bridge" target="_blank" rel="noopener">wiki.adom.inc/adom/fusion-bridge</a></div>+    </div>+  </div>+  <div class="vsep"></div>+  <div class="subject"><span class="tchip"><span class="led" id="led"></span><span id="conn">not connected</span></span></div>+  <div class="meta" id="meta">press Connect</div>+  <div class="actions">+    <a class="iconbtn" href="./" title="Back to the Fusion Dashboard" aria-label="Fusion Dashboard"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12h18M3 6h18M3 18h18"/></svg></a>+    <button class="iconbtn" id="btnClear" title="Clear the verb log" aria-label="Clear log"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4h8v2M6 6l1 14h10l1-14"/></svg></button>+  </div>+</header>++<main>+  <section class="card wide" data-card="connection">+    <div class="card-title">Adom Bridge on this machine <span class="card-status" id="connStatus">not probed yet</span></div>+    <div class="card-desc">This page talks to Adom Bridge the same way a wiki part page does: it probes <code>http://127.0.0.1:24680-24689/status</code> for the local web-control listener, then sends every action as <code>POST /command {command, args}</code>. Each command that changes something asks you once in Adom Bridge (Allow once / Allow 1 hr / Deny). Your browser may ask for local network access first; allow it. Nothing leaves this machine except the part files it fetches from the wiki.</div>+    <div class="row">+      <button class="btn primary" id="btnConnect" data-action="connect">Connect</button>+      <span class="mini" id="connNote"></span>+    </div>+    <div class="kv" id="connKv"></div>+  </section>++  <section class="card" data-card="part">+    <div class="card-title">Part to import <span class="card-status" id="partStatus"></span></div>+    <div class="card-desc">A wiki component page publishes its Fusion files (<code>.lbr</code> plus a <code>.step</code>). Resolve them here; the import below zips them in the browser and stages the zip on this machine in one call.</div>+    <div class="row"><input type="text" id="partRef" value="ray/dac80504" aria-label="Wiki page reference" placeholder="owner/slug"><button class="btn" id="btnResolve" data-action="resolve">Resolve files</button></div>+    <div class="kv" id="partKv"></div>+  </section>++  <section class="card" data-card="fusion">+    <div class="card-title">Fusion on this machine <span class="card-status" id="fusionStatus"></span></div>+    <div class="card-desc">Read before you act: is Fusion installed, running, and is the Adom add-in answering? The open documents are listed with their unsaved marker, so a restart is never a surprise.</div>+    <div class="row"><button class="btn" id="btnFusion" data-action="fusion_state">Read Fusion state</button><button class="btn" id="btnPlan" data-action="plan">Pre-flight the import</button></div>+    <div class="kv" id="fusionKv"></div>+    <ul class="docs" id="docs"></ul>+    <ul class="plan" id="plan"></ul>+    <div id="ask"></div>+  </section>++  <section class="card wide" data-card="import">+    <div class="card-title">Import into Fusion, one call <span class="card-status" id="importStatus"></span></div>+    <div class="card-desc"><code>fusion_install_library_bundle {zipPath, libraryName}</code> returns immediately with a receipt (what was placed, which devicesets paired with a STEP, the part-number lint) and a <code>runId</code>. The page then polls <code>fusion_prove_result</code>: the bar is the bridge's measured percent, the line is its stage text, and the frames are the proof it captured, four per part, labelled so a proof shot is never mistaken for a live-feed frame.</div>+    <div class="row">+      <button class="btn primary" id="btnImport" data-action="import_bundle" disabled>Create Fusion library</button>+      <button class="btn" id="btnAttach" data-action="attach_run" title="Attach to a run already in flight">Attach to running import</button>+      <span class="mini" id="importNote">resolve a part first</span>+    </div>+    <div class="prog" id="prog"><div></div></div>+    <div class="stage" id="stage"></div>+    <div class="kv" id="receipt"></div>+    <div id="lint"></div>+    <div class="gallery" id="gallery"></div>+  </section>++  <section class="card" data-card="feed">+    <div class="card-title">Live feed health <span class="card-status" id="feedStatus"></span></div>+    <div class="card-desc">The 1 fps feed captures the Fusion window on this machine into a ring buffer. <code>running</code> alone proves nothing; a feed is live when <code>captured</code> climbs. The bridge says so itself in <code>healthy</code> and <code>_honesty</code>.</div>+    <div class="row"><button class="btn" data-action="feed_status" id="btnFeed">Read feed status</button><button class="btn" data-action="feed_start" id="btnFeedStart">Start feed</button><button class="btn" data-action="feed_stop" id="btnFeedStop">Stop feed</button></div>+    <div class="kv" id="feedKv"></div>+  </section>++  <section class="card" data-card="banner">+    <div class="card-title">In-Fusion banner <span class="card-status" id="bannerStatus"></span></div>+    <div class="card-desc">The Adom-branded status palette inside Fusion's own window. The import drives it automatically; these buttons drive it by hand so you can see what the user at Fusion sees.</div>+    <div class="row"><button class="btn" data-action="banner_show" id="btnBannerShow">Show banner</button><button class="btn" data-action="banner_hide" id="btnBannerHide">Hide banner</button></div>+    <div class="kv" id="bannerKv"></div>+  </section>++  <section class="card wide" data-card="catalog">+    <div class="card-title">Every verb, one surface <span class="card-status" id="catStatus"></span></div>+    <div class="card-desc">Web control and an AI thread hit the same dispatcher. <code>fusion_describe</code> is the catalog; anything listed here can be sent from this page or from <code>adom-bridge fusion_*</code>.</div>+    <div class="row"><button class="btn" data-action="describe" id="btnDescribe">Load the catalog</button><input type="text" id="catFilter" placeholder="filter" aria-label="Filter verbs" style="max-width:220px"></div>+    <div class="catalog" id="catalog"></div>+  </section>++  <section class="card wide" data-card="log">+    <div class="card-title">Verb log <span class="card-status" id="logStatus">0 calls</span></div>+    <div class="card-desc">Every <code>POST /command</code> this page sent, with its arguments, the round-trip time and the response summary. This is the same trace an agent reads when it ralph-tests the page.</div>+    <div class="log" id="log" data-log></div>+  </section>+</main>+<div class="toasts" id="toasts" aria-live="polite"></div>++<script>+(function(){+'use strict';+var PORTS=[];for(var p=24680;p<=24689;p++)PORTS.push(p);+var bridge=null, part=null, staged=null, runId=null, pollTimer=null, calls=0;+var $=function(id){return document.getElementById(id)};+function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]})}+function toast(msg,kind){var t=document.createElement('div');t.className='toast '+(kind||'');t.textContent=msg;$('toasts').appendChild(t);setTimeout(function(){t.remove()},kind==='err'?7000:4000)}+function kv(el,obj){el.innerHTML=Object.keys(obj).map(function(k){return '<span>'+esc(k)+'</span><b>'+esc(obj[k])+'</b>'}).join('')}+function status(el,text,cls){el.textContent=text;el.className='card-status '+(cls||'')}+function logLine(html){var l=$('log');l.insertAdjacentHTML('afterbegin','<div>'+html+'</div>');calls++;$('logStatus').textContent=calls+' call'+(calls===1?'':'s')}+function summarize(d){if(!d||typeof d!=='object')return String(d);var keys=['success','ok','errorCode','error','stage','percent','runId','installed','partsToBind','healthy','captured','tick','running','status','verdict','evidenceCount'];var out=[];keys.forEach(function(k){if(k in d&&d[k]!==null&&d[k]!==undefined&&d[k]!=='')out.push(k+'='+JSON.stringify(d[k]).slice(0,60))});return out.join(' ')||Object.keys(d).slice(0,6).join(',')}+function toB64(buf){var u=new Uint8Array(buf),s='';for(var i=0;i<u.length;i+=32768)s+=String.fromCharCode.apply(null,u.subarray(i,i+32768));return btoa(s)}+function timeout(ms){try{return AbortSignal.timeout(ms)}catch(e){return undefined}}++// ---- transport: probe + command -------------------------------------------------+function probe(){return Promise.all(PORTS.map(function(p){return fetch('http://127.0.0.1:'+p+'/status',{signal:timeout(1500)}).then(function(r){return r.ok?r.json():null}).then(function(j){return j&&j.service==='adom-bridge'?{port:p,raw:j}:null}).catch(function(){return null})})).then(function(rs){for(var i=0;i<rs.length;i++)if(rs[i])return rs[i];return null})}+function cmd(command,args,opts){opts=opts||{};if(!bridge){return Promise.reject(new Error('not connected'))}var t0=performance.now();var body=JSON.stringify({command:command,args:args||{}});var short=body.length>300?body.slice(0,300)+'...':body;return fetch('http://127.0.0.1:'+bridge.port+'/command',{method:'POST',headers:{'Content-Type':'application/json'},body:body,signal:timeout(opts.timeoutMs||135000)}).then(function(r){return r.json().catch(function(){return{}}).then(function(j){return{status:r.status,j:j}})}).then(function(x){var ms=Math.round(performance.now()-t0);var d=x.j||{};var data=d.data&&typeof d.data==='object'?d.data:d;var ok=x.status===200&&d.success!==false&&data.success!==false&&!d.errorCode;logLine('<span class="t">'+new Date().toLocaleTimeString()+'</span> <span class="v">'+esc(command)+'</span> '+esc(short.slice(0,220))+' <span class="'+(ok?'ok':'err')+'">'+(ok?'ok':'FAIL')+'</span> '+ms+'ms '+esc(summarize(data.result||data))+(d._hint?'\n   hint: '+esc(String(d._hint).slice(0,200)):''));if(!ok){var e=new Error(data.error||d.error||d.errorCode||data.errorCode||('HTTP '+x.status));e.code=d.errorCode||data.errorCode||(x.status===403?'web_denied':null);e.payload=data;throw e}return data}).catch(function(e){if(!e.payload)logLine('<span class="t">'+new Date().toLocaleTimeString()+'</span> <span class="v">'+esc(command)+'</span> <span class="err">unreachable: '+esc(e.message)+'</span>');throw e})}++// ---- connection --------------------------------------------------------------------+function connect(){status($('connStatus'),'probing 127.0.0.1:24680-24689...');$('connNote').textContent='allow local network access if your browser asks';$('led').className='led';return probe().then(function(b){bridge=b;if(!b){status($('connStatus'),'Adom Bridge not detected','bad');$('led').className='led bad';$('conn').textContent='not connected';$('meta').textContent='is Adom Bridge running?';toast('Adom Bridge not found on 24680-24689. Is ab running on this machine?','err');return null}var wc=b.raw.webControl||{};var m=b.raw.machine||{};var name=m.clientName||m.hostname||'this machine';$('led').className='led '+(wc.enabled?'on':'warn');$('conn').textContent=name+' :'+b.port;$('meta').textContent='ab '+(b.raw.version||'?')+' · web control '+(wc.enabled?'on':'OFF')+' · gate '+(wc.commandGate||'?');$('bpAb').textContent=(b.raw.version||'?')+' on '+name;status($('connStatus'),wc.enabled?'connected, web control enabled':'connected, web control is OFF in ab Settings',wc.enabled?'ok':'warn');$('connNote').textContent='';kv($('connKv'),{machine:name,'ab version':b.raw.version||'?',endpoint:'http://127.0.0.1:'+b.port,'web control':wc.enabled?'enabled':'disabled','command gate':wc.commandGate||'?','handshake required':wc.requireHandshake?'yes':'no','grants':(wc.grants||[]).map(function(g){return g.origin+' ('+g.mode+')'}).join(', ')||'none'});toast('Connected to Adom Bridge on '+name,'ok');$('btnConnect').textContent='Reconnect';return b})}++// ---- part files ----------------------------------------------------------------------+function resolvePart(){var ref=$('partRef').value.trim().replace(/^\/+|\/+$/g,'');if(!ref)return;status($('partStatus'),'resolving...');part=null;$('btnImport').disabled=true;return fetch('https://wiki.adom.inc/api/v1/pages/'+ref+'/files',{signal:timeout(15000)}).then(function(r){if(!r.ok)throw new Error('wiki answered '+r.status);return r.json()}).then(function(j){var files=j.files||[];var lbr=files.filter(function(f){return /\.lbr$/i.test(f.name)});var steps=files.filter(function(f){return /\.(step|stp)$/i.test(f.name)});if(!lbr.length)throw new Error('this page publishes no .lbr');var base='https://wiki.adom.inc/api/v1/pages/'+ref+'/files/';part={ref:ref,name:ref.split('/').pop(),lbr:{name:lbr[0].name,url:base+lbr[0].name,size:lbr[0].size},steps:steps.map(function(f){return{name:f.name,url:base+f.name,size:f.size}})};kv($('partKv'),{page:'https://wiki.adom.inc/'+ref,library:part.lbr.name+' ('+part.lbr.size+' B)',models:part.steps.map(function(s){return s.name+' ('+s.size+' B)'}).join(', ')||'none (the import will be an unbound library)'});status($('partStatus'),'ready','ok');$('btnImport').disabled=false;$('importNote').textContent=part.steps.length?'one call, then the page polls':'no STEP on this page: nothing to bind';toast('Resolved '+part.lbr.name+(part.steps.length?' + '+part.steps.length+' model(s)':''),'ok')}).catch(function(e){status($('partStatus'),e.message,'bad');toast('Could not resolve the part: '+e.message,'err')})}++// ---- zip (STORE only) built in the browser ------------------------------------------+var CRC=(function(){var t=[];for(var n=0;n<256;n++){var c=n;for(var k=0;k<8;k++)c=c&1?0xEDB88320^(c>>>1):c>>>1;t[n]=c>>>0}return t})();+function crc32(u8){var c=0xFFFFFFFF;for(var i=0;i<u8.length;i++)c=CRC[(c^u8[i])&0xFF]^(c>>>8);return (c^0xFFFFFFFF)>>>0}+function makeZip(entries){var parts=[],central=[],offset=0,enc=new TextEncoder();entries.forEach(function(en){var name=enc.encode(en.name),data=new Uint8Array(en.data),crc=crc32(data);var lh=new DataView(new ArrayBuffer(30));lh.setUint32(0,0x04034b50,true);lh.setUint16(4,20,true);lh.setUint16(6,0,true);lh.setUint16(8,0,true);lh.setUint16(10,0,true);lh.setUint16(12,0x21,true);lh.setUint32(14,crc,true);lh.setUint32(18,data.length,true);lh.setUint32(22,data.length,true);lh.setUint16(26,name.length,true);lh.setUint16(28,0,true);parts.push(new Uint8Array(lh.buffer),name,data);var ch=new DataView(new ArrayBuffer(46));ch.setUint32(0,0x02014b50,true);ch.setUint16(4,20,true);ch.setUint16(6,20,true);ch.setUint16(8,0,true);ch.setUint16(10,0,true);ch.setUint16(12,0,true);ch.setUint16(14,0x21,true);ch.setUint32(16,crc,true);ch.setUint32(20,data.length,true);ch.setUint32(24,data.length,true);ch.setUint16(28,name.length,true);ch.setUint16(30,0,true);ch.setUint16(32,0,true);ch.setUint16(34,0,true);ch.setUint16(36,0,true);ch.setUint32(38,0,true);ch.setUint32(42,offset,true);central.push(new Uint8Array(ch.buffer),name);offset+=30+name.length+data.length});var cdSize=central.reduce(function(a,b){return a+b.length},0);var eocd=new DataView(new ArrayBuffer(22));eocd.setUint32(0,0x06054b50,true);eocd.setUint16(8,entries.length,true);eocd.setUint16(10,entries.length,true);eocd.setUint32(12,cdSize,true);eocd.setUint32(16,offset,true);var all=parts.concat(central,[new Uint8Array(eocd.buffer)]);var total=all.reduce(function(a,b){return a+b.length},0),out=new Uint8Array(total),pos=0;all.forEach(function(b){out.set(b,pos);pos+=b.length});return out.buffer}+function stage(path,buf,note){var u=new Uint8Array(buf),CH=1024*1024,n=Math.max(1,Math.ceil(u.length/CH));function one(off){var end=Math.min(off+CH,u.length),fin=end>=u.length;setStage(note+' (chunk '+(Math.floor(off/CH)+1)+'/'+n+')');return cmd('write_file_chunk',{path:path,dataB64:toB64(u.subarray(off,end)),offset:off,final:fin,createDirs:true}).then(function(d){if(fin)return d.path||path;return one(typeof d.nextOffset==='number'?d.nextOffset:end)})}return one(0)}++// ---- import: one call, then poll --------------------------------------------------------+function setProg(p,cls){var el=$('prog');el.className='prog '+(cls||'');el.firstElementChild.style.width=Math.max(0,Math.min(100,p||0))+'%'}+function setStage(html){$('stage').innerHTML=html}+function renderLint(pm){if(!pm){$('lint').innerHTML='';return}var ok=pm.ok!==false;var h='<div class="lint '+(ok?'ok':'warn')+'"><b>Part numbers</b> '+esc(pm.summary||(ok?'every component carries a manufacturer part number and a distributor number.':''))+'';if(pm.warnings&&pm.warnings.length){h+='<ul>'+pm.warnings.slice(0,6).map(function(w){return '<li>'+esc(typeof w==='string'?w:(w.message||JSON.stringify(w)))+'</li>'}).join('')+'</ul>'}if(pm.userAction)h+='<div class="mini" style="margin-top:6px">'+esc(pm.userAction)+'</div>';h+='</div>';$('lint').innerHTML=h}+function renderReceipt(d){var unb=(d.unbound||[]).map(function(u){return u.package+' ('+u.why+')'}).join('; ');kv($('receipt'),{installed:d.installed?'yes, '+d.libraryPath:'no',library:d.libraryName||'',parts:(d.partsToBind||0)+' of '+(d.partsTotal||0)+' paired with a STEP','named skips':unb||'none','fusion':(d.fusion&&d.fusion.message)||'',runId:d.runId||'','estimated':d.estimatedSec?Math.round(d.estimatedSec)+'s':''});renderLint(d.partMetadata)}+function renderFrames(ev){var g=$('gallery');g.innerHTML='';(ev||[]).forEach(function(f){var src=f.thumbnailDataUrl||f.dataUrl;var h='<div class="shot">'+(src?'<img src="'+src+'" alt="'+esc(f.step||f.view||'frame')+'">':'<div class="cap" style="padding:18px 8px">frame on the box: '+esc((f.screenshot||'').split('/').pop())+'</div>')+'<div class="cap"><span><span class="tag '+(f.kind==='proof'?'proof':'feed')+'">'+esc(f.kind||'frame')+'</span> '+esc(f.step||f.view||'')+'</span><span>'+esc(f.part||'')+'</span></div></div>';g.insertAdjacentHTML('beforeend',h)})}+function poll(id){runId=id;clearTimeout(pollTimer);var tick=0;(function loop(){tick++;cmd('fusion_prove_result',{runId:id}).then(function(d){var r=d.result||d;var pct=typeof r.percent==='number'?r.percent:null;if(r.stage==='failed'){setProg(100,'fail');setStage('<b>failed</b> '+esc(r.error||(r.bind&&r.bind.error)||r.errorCode||''));status($('importStatus'),'failed','bad');renderFrames(r.evidence);toast('Import failed: '+(r.error||(r.bind&&r.bind.error)||'see the log'),'err');return}if(r.stage==='done'){setProg(100,'done');var pv=r.prove||{};setStage('<b>done</b> verdict: '+esc(r.verdict||(pv.proven?'proven':'not proven'))+' · '+(pv.partsProven||0)+'/'+(pv.partsTotal||r.partsTotal||0)+' parts proven · '+(r.evidenceCount||0)+' frames');status($('importStatus'),r.verdict==='proven'||pv.proven?'proven':'finished, not proven',r.verdict==='proven'||pv.proven?'ok':'warn');if(r.partMetadata)renderLint(r.partMetadata);toast('Library created in Fusion: '+(pv.partsProven||0)+'/'+(pv.partsTotal||0)+' parts proven','ok');return cmd('fusion_prove_result',{runId:id,inlineFrames:true},{timeoutMs:120000}).then(function(d2){var r2=d2.result||d2;renderFrames(r2.evidence||r.evidence)}).catch(function(){renderFrames(r.evidence)})}if(pct!==null)setProg(pct);setStage('<b>'+esc(r.stage||'working')+'</b>'+(r.stageLabel?' · '+esc(r.stageLabel):'')+(pct!==null?' · '+Math.round(pct)+'%':'')+(typeof r.etaSec==='number'?' · ~'+Math.round(r.etaSec)+'s left':''));if(r.evidence&&r.evidence.length)renderFrames(r.evidence);if(r.errorCode==='addin_restart_required')showAsk(r);pollTimer=setTimeout(loop,tick<3?5000:10000)}).catch(function(e){if(e.code==='run_not_found'&&tick<6){setStage('starting...');pollTimer=setTimeout(loop,6000);return}setStage('<b>poll failed</b> '+esc(e.message));status($('importStatus'),'poll failed','bad')})})()}+function importBundle(){if(!part)return;var b=$('btnImport');b.disabled=true;status($('importStatus'),'working');$('gallery').innerHTML='';$('receipt').innerHTML='';$('lint').innerHTML='';setProg(0);setStage('fetching '+esc(part.lbr.name)+' from the wiki...');var fetches=[fetch(part.lbr.url).then(function(r){return r.arrayBuffer()})].concat(part.steps.map(function(s){return fetch(s.url).then(function(r){return r.arrayBuffer()})}));Promise.all(fetches).then(function(bufs){setStage('zipping in the browser...');var entries=[{name:part.lbr.name,data:bufs[0]}].concat(part.steps.map(function(s,i){return{name:s.name,data:bufs[i+1]}}));var zip=makeZip(entries);var name=part.name.replace(/[^A-Za-z0-9._-]+/g,'_');return stage('~/adom-wiki-libs/'+name+'.zip',zip,'staging the bundle on this machine (approve in Adom Bridge)').then(function(p){staged=p;setStage('installing the bundle (one call)...');return cmd('fusion_install_library_bundle',{zipPath:p,libraryName:name})})}).then(function(d){renderReceipt(d);if(d.errorCode==='nothing_to_bind'){setStage('<b>installed</b>, nothing to bind: '+esc(d._hint||''));status($('importStatus'),'installed, unbound','warn');b.disabled=false;return}if(d.attached)toast('An import is already running; attached to '+d.runId);else toast('Installed and started run '+d.runId,'ok');poll(d.runId)}).catch(function(e){setProg(100,'fail');setStage('<b>'+esc(e.code||'failed')+'</b> '+esc(e.message));status($('importStatus'),'failed','bad');if(e.payload&&e.payload.errorCode==='addin_restart_required')showAsk(e.payload);toast((e.code==='web_denied'?'Denied in Adom Bridge: ':'')+e.message,'err')}).finally(function(){b.disabled=false})}+function attachRun(){cmd('fusion_prove_result',{}).then(function(d){var r=d.result||d;if(!r.runId){toast('No run to attach to','err');return}toast('Attached to run '+r.runId);poll(r.runId)}).catch(function(e){toast(e.message,'err')})}++// ---- the restart ask: always a choice --------------------------------------------------+function showAsk(p){var docs=(p.openDocuments||p.documentNames||[]);var unsaved=p.unsavedDocuments||[];$('ask').innerHTML='<div class="ask" data-ask="addin_restart_required"><h4>Fusion needs a restart to load the Adom add-in</h4><p>'+esc(p.userMessage||'The add-in is not answering. Restarting Fusion loads it. Your documents will be saved first.')+'</p>'+(docs.length?'<ul>'+docs.map(function(d){return '<li>'+esc(typeof d==='string'?d:(d.name||''))+(unsaved.indexOf(typeof d==='string'?d:d.name)>=0?' (unsaved)':'')+'</li>'}).join('')+'</ul>':'<p class="mini">'+(p.documentsKnown===false?'The bridge could not list open documents.':'No open documents.')+'</p>')+'<div class="row"><button class="btn primary" data-action="restart_save">Save and restart Fusion</button><button class="btn" data-action="restart_hold">I\'ll handle it</button></div></div>';$('ask').querySelector('[data-action=restart_save]').onclick=function(){cmd(p.remedyVerb||'fusion_restart_for_addin',{save:true},{timeoutMs:135000}).then(function(){toast('Fusion is restarting with your documents saved','ok');$('ask').innerHTML=''}).catch(function(e){toast(e.message,'err')})};$('ask').querySelector('[data-action=restart_hold]').onclick=function(){$('ask').innerHTML='<div class="mini">You own the restart. Nothing on this page will restart Fusion.</div>';toast('Okay, the restart is yours')}}++// ---- fusion state, plan ------------------------------------------------------------------+function fusionState(){status($('fusionStatus'),'reading...');return cmd('fusion_readiness',{}).then(function(r){var x=r.data||r;var d={installed:x.installed?'yes':'no',running:x.running?'yes':'no',ready:x.ready?'yes':'no','bridge':x.bridgeVersion||'?'};if(x.needsSignin)d['needs sign-in']='yes';kv($('fusionKv'),d);status($('fusionStatus'),x.ready?'ready':(x.running?'running, add-in not ready':(x.installed?'installed, not running':'not installed')),x.ready?'ok':'warn');return cmd('fusion_get_app_state',{})}).then(function(r){var x=r.data||r;var docs=x.documents||[];$('docs').innerHTML=docs.length?docs.map(function(d){return '<li><span>'+esc(d.name||'')+(d.isModified||d.modified?' <span class="mod">unsaved</span>':'')+'</span><span>'+esc(d.isActive?'active':'')+'</span></li>'}).join(''):'<li><span>no documents open</span><span></span></li>'}).catch(function(e){status($('fusionStatus'),e.message,'bad');toast(e.message,'err')})}+function plan(){var parts=part&&part.steps.length?part.steps.map(function(s){return{package:part.name,modelPath:s.name}}):[];status($('fusionStatus'),'planning...');return cmd('fusion_plan_library_import',{parts:parts}).then(function(d){var f=d.fusion||{};status($('fusionStatus'),f.status||'?',f.status==='ready'?'ok':'warn');kv($('fusionKv'),{status:f.status||'?',message:f.message||'','total':Math.round(d.estimatedSec||0)+'s'+(d.estimatedSecIfAlreadyOpen&&d.estimatedSecIfAlreadyOpen!==d.estimatedSec?' ('+Math.round(d.estimatedSecIfAlreadyOpen)+'s if already open)':''),confidence:d.confidence||''});$('plan').innerHTML=(d.plan||[]).map(function(s){return '<li><span>'+esc(s.label||s.stage)+(s.needsUserConsent?' (asks you first)':'')+'</span><span class="c '+esc(s.confidence||'')+'">'+Math.round(s.estimatedSec||0)+'s · '+esc(s.confidence||'')+'</span></li>'}).join('');if(f.status==='addin_restart_required')showAsk({userMessage:f.message,documentsKnown:false});toast('Plan: '+Math.round(d.estimatedSec||0)+'s, '+(d.plan||[]).length+' stages')}).catch(function(e){status($('fusionStatus'),e.message,'bad');toast(e.message,'err')})}++// ---- feed, banner, catalog ----------------------------------------------------------------+function feed(action){status($('feedStatus'),action+'...');return cmd('fusion_live_feed',action==='status'?{action:'status'}:{action:action,intervalSec:1,maxSec:300}).then(function(d){var x=d.data||d;kv($('feedKv'),{running:x.running?'yes':'no',healthy:x.healthy===undefined?'(bridge < 1.9.245)':(x.healthy?'yes':'NO'),tick:x.tick,captured:x.captured,buffered:x.buffered,'last error':x.lastError||'none','honesty':x._honesty||''});status($('feedStatus'),x.healthy?'live':(x.running?'running but not live':'stopped'),x.healthy?'ok':(x.running?'warn':''));if(x._honesty)toast(x._honesty,'err')}).catch(function(e){status($('feedStatus'),e.message,'bad');toast(e.message,'err')})}+function banner(show){var args=show?{action:'show',title:'Quick Actions demo',detail:'driven from the browser through Adom Bridge',percent:42,tone:'working'}:{action:'hide'};return cmd('fusion_activity_banner',args).then(function(d){kv($('bannerKv'),{result:summarize(d.data||d)});status($('bannerStatus'),show?'shown in Fusion':'hidden','ok')}).catch(function(e){status($('bannerStatus'),e.message,'bad');toast(e.message,'err')})}+var catalog=[];+function describe(){status($('catStatus'),'loading...');return cmd('fusion_describe',{},{timeoutMs:60000}).then(function(d){var x=d.data||d;var list=x.verbs||x.commands||x.catalog||(Array.isArray(x)?x:[]);if(!Array.isArray(list)){list=Object.keys(x).filter(function(k){return /^fusion_/.test(k)}).map(function(k){return Object.assign({name:k},typeof x[k]==='object'?x[k]:{description:String(x[k])})})}catalog=list.map(function(v){return{name:v.name||v.verb||'',desc:v.description||v.desc||v.summary||''}});status($('catStatus'),catalog.length+' verbs','ok');renderCatalog()}).catch(function(e){status($('catStatus'),e.message,'bad')})}+function renderCatalog(){var f=$('catFilter').value.toLowerCase();$('catalog').innerHTML=catalog.filter(function(v){return !f||(v.name+' '+v.desc).toLowerCase().indexOf(f)>=0}).slice(0,200).map(function(v){return '<div><b>'+esc(v.name)+'</b> <span>'+esc(String(v.desc).slice(0,160))+'</span></div>'}).join('')||'<div><span>nothing matches</span></div>'}++// ---- wire up ---------------------------------------------------------------------------------+$('btnConnect').onclick=connect;$('btnResolve').onclick=resolvePart;$('partRef').addEventListener('keydown',function(e){if(e.key==='Enter')resolvePart()});+$('btnFusion').onclick=fusionState;$('btnPlan').onclick=plan;$('btnImport').onclick=importBundle;$('btnAttach').onclick=attachRun;+$('btnFeed').onclick=function(){feed('status')};$('btnFeedStart').onclick=function(){feed('start')};$('btnFeedStop').onclick=function(){feed('stop')};+$('btnBannerShow').onclick=function(){banner(true)};$('btnBannerHide').onclick=function(){banner(false)};+$('btnDescribe').onclick=describe;$('catFilter').addEventListener('input',renderCatalog);+$('btnClear').onclick=function(){$('log').innerHTML='';calls=0;$('logStatus').textContent='0 calls'};+window.QuickActions={connect:connect,resolvePart:resolvePart,importBundle:importBundle,poll:poll,cmd:cmd,state:function(){return{bridge:bridge,part:part,staged:staged,runId:runId,calls:calls}}};+resolvePart();+})();+</script>+</body>+</html>
page.json+7−10
@@ -11,10 +11,6 @@     "uninstall": "./uninstall.sh"   },   "tags": [-    "ab",-    "bridge",-    "fusion",-    "desktop-app",     "fusion360",     "autodesk",     "cad",@@ -79,10 +75,7 @@     "build a basic parts library",     "add a 3d model to a component",     "make a fusion 360 library with 3d",-    "set up the fusion bridge",-    "fusion dashboard",-    "fusion status page",-    "open the fusion launch page"+    "set up the fusion bridge"   ],   "discovery_pitch": "Use the Adom Bridge Fusion 360 bridge when the user wants to DRIVE Autodesk Fusion 360 from the cloud: open/search their team's Fusion cloud designs, lay out a PCB or do electronics, export STEP/IGES/STL/Gerbers/BOM, or build a component library with real rendering 3D. It runs Fusion on the user's own machine via Adom Bridge. NOT for web/browser/login/form-filling (that's the native-browser extension) and NOT for KiCad (that's the kicad bridge).",   "brief": "Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.",@@ -111,5 +104,9 @@       "label": "Part volume",       "prompt": "How much material does each printed part in this assembly use?"     }-  ]-}+  ],+  "author": {+    "name": "John Lauer",+    "email": "[email protected]"+  }+}\ No newline at end of file