app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
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.
← Commit history
dashboard/ui/index.html+99−25@@ -83,7 +83,9 @@ input:focus-visible{outline:none;border-color:var(--accent) !important} .toast{background:var(--overlay);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:8px;padding:10px 14px;color:var(--text);font-size:14px;box-shadow:0 8px 24px rgba(0,0,0,.4)} .toast.ok{border-left-color:var(--green)}.toast.err{border-left-color:var(--red)} dialog{margin:auto;background:var(--overlay);color:var(--text-body);border:1px solid var(--border);border-radius:12px;padding:20px;max-width:520px;width:92vw}-dialog::backdrop{background:rgba(0,0,0,.55)}+/* Backdrop law (settings-layout): dim 20-30%, NEVER blur - settings often change the very+ thing behind the modal (theme, density, the live LEDs), so the app must stay legible. */+dialog::backdrop{background:rgba(0,0,0,.25)} /* The dialog is focused programmatically on open (so it scrolls to the top instead of jumping to the first input). Chrome then paints its DEFAULT focus ring in currentColor - a light 2px halo that reads as a white border around the modal (John spotted it, 2026-08-18). The@@ -91,13 +93,39 @@ dialog::backdrop{background:rgba(0,0,0,.55)} rings for keyboard users. */ dialog:focus,dialog:focus-visible{outline:none} .setrow{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:12px 0;border-bottom:1px solid var(--border)}-.toggle{width:42px;height:24px;border-radius:999px;background:var(--elevated);border:1px solid var(--border);position:relative;cursor:pointer;flex:none;transition:border-color .15s ease}+/* House toggle (settings-layout): 32x18 track, WHITE thumb in BOTH states, teal-on/neutral-off,+ knob stretches under the press. Never a native checkbox (its blue is not an Adom colour). */+.toggle{width:32px;height:18px;border-radius:999px;background:var(--overlay);border:1px solid var(--border);position:relative;cursor:pointer;flex:none;transition:background .15s ease,border-color .15s ease} .toggle:hover{border-color:var(--accent)}-.toggle:active::after{width:22px}-.toggle.is-on:active::after{left:17px}-.toggle::after{content:'';position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:999px;background:var(--text-2);transition:all .15s ease}+.toggle:active::after{width:16px}+.toggle.is-on:active::after{left:10px}+.toggle::after{content:'';position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:999px;background:#fff;transition:transform .18s ease,width .12s ease,left .18s ease} .toggle.is-on{background:var(--accent);border-color:var(--accent)}-.toggle.is-on::after{left:21px;background:#05221f}+.toggle.is-on::after{left:16px;background:#fff}+/* Row default hint + breadcrumb-teal last part of a row name (settings-layout) */+.default-hint{font-size:11px;color:var(--text-2);margin-top:2px}+.ctlrow .lbl b,.ctlrow .lbl .last{color:#8fd3d4;font-weight:600}+/* Choice cards: mutually-exclusive VISUAL alternatives, one real screenshot each */+.choice-set{border:none;margin:.2rem 0 0;padding:0;display:flex;flex-wrap:wrap;gap:.6rem}+.choice{flex:1 1 150px;display:flex;flex-direction:column;gap:.3rem;padding:.55rem;cursor:pointer;background:var(--surface);border:1px solid var(--border);border-radius:8px;transition:border-color .15s ease,background .15s ease,transform .06s ease}+.choice input{position:absolute;opacity:0;pointer-events:none}+.choice .shot{display:block;width:100%;height:auto;border-radius:4px;border:1px solid var(--border);background:var(--elevated)}+.choice .shot-pending{display:flex;align-items:center;justify-content:center;height:52px;border-radius:4px;border:1px dashed var(--border);background:var(--elevated);color:var(--text-2);font-size:10.5px;text-align:center;padding:0 6px}+.choice .cname{font-family:'Familjen Grotesk',sans-serif;font-size:12.5px;font-weight:600;color:var(--text)}+.choice .cdesc{font-size:11px;color:var(--text-2);line-height:1.45}+.choice:hover{border-color:var(--accent);background:var(--elevated)}+.choice:active{transform:translateY(1px) scale(.995)}+.choice.sel{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}+.choice:focus-within{outline:2px solid var(--accent);outline-offset:2px}+/* House scrollbar for the settings body (never the fat OS bar) */+.dlgbody::-webkit-scrollbar{width:9px}+.dlgbody::-webkit-scrollbar-thumb{background:rgba(139,148,158,.35);border-radius:999px;border:2px solid transparent;background-clip:content-box}+.dlgbody::-webkit-scrollbar-thumb:hover{background:var(--accent);background-clip:content-box}+/* Two-click Restore defaults footer */+.settings-footer{margin-top:1.2rem;padding:1rem 18px 16px;border-top:1px solid var(--border);display:flex;flex-direction:column;gap:.5rem}+.restore-btn{width:fit-content;display:inline-flex;align-items:center;gap:.4rem;padding:.4rem .85rem;background:transparent;border:1px solid var(--border);border-radius:.375rem;color:var(--text-body);font-family:'Familjen Grotesk',sans-serif;font-size:12.5px;font-weight:600;cursor:pointer;transition:background .15s ease,border-color .15s ease,color .15s ease}+.restore-btn:hover{background:var(--elevated)}+.restore-btn.armed{color:var(--red);border-color:rgba(248,81,73,.5);background:rgba(248,81,73,.08)} @media (max-width:720px){.meta{display:none}.grid{grid-template-columns:1fr 1fr}} /* Touch targets: the responsive-ui contract is 44x44 minimum on touch. Applied for coarse pointers AND at phone width - pointer:coarse cannot be emulated in the ralph harness, so a@@ -233,6 +261,7 @@ dialog.settings{max-width:560px;overflow:hidden;padding:0} <div class="divider"></div> <div class="ctlrow"><span class="lbl">Get everything ready when I open the Dashboard</span> <div class="toggle" id="tglAutoGreen" role="switch" aria-checked="true"></div></div>+ <div class="default-hint">Default: On</div> </div> <div class="settings-card">@@ -241,9 +270,11 @@ dialog.settings{max-width:560px;overflow:hidden;padding:0} <div class="divider"></div> <div class="ctlrow"><span class="lbl">Silently install Fusion updates</span> <div class="toggle" id="tglAutoUpdate" role="switch" aria-checked="true"></div></div>+ <div class="default-hint">Default: On</div> <div class="divider"></div> <div class="ctlrow"><span class="lbl">Take over the Autodesk seat when it is busy elsewhere</span> <div class="toggle" id="tglSeatTakeover" role="switch" aria-checked="true"></div></div>+ <div class="default-hint">Default: On</div> </div> <div class="settings-card">@@ -252,19 +283,32 @@ dialog.settings{max-width:560px;overflow:hidden;padding:0} <div class="divider"></div> <div class="ctlrow"><span class="lbl">Show the "Adom is driving" badge on Fusion's taskbar button</span> <div class="toggle" id="tglBadge" role="switch" aria-checked="true"></div></div>+ <div class="default-hint">Default: On</div> </div> <div class="settings-card"> <div class="card-title">Activity indicator <span class="card-status" id="stActivity"></span></div> <div class="card-desc">How Fusion's taskbar button signals that Adom is running a verb. <b>Progress bar</b> paints a real bar paced by how long <i>this machine</i> has actually taken for that verb, and clears the moment it finishes or fails - so you can tell "nearly done" from "just started" at a glance. <b>Flash</b> is the older attention flash: it says something is happening, and nothing more. A verb with no measured history yet runs the bar indeterminate rather than inventing a percentage. Run times are recorded per verb to <code>verb-times.jsonl</code>; <code>fusion_verb_times</code> reports p50/p90 per verb.</div> <div class="divider"></div>- <div class="ctlrow"><span class="lbl">Indicator</span>- <div class="segmented" id="segActivity" role="radiogroup" aria-label="Activity indicator">- <button class="seg" data-mode="progress" role="radio">Progress bar</button>- <button class="seg" data-mode="flash" role="radio">Flash</button>- <button class="seg" data-mode="both" role="radio">Both</button>- <button class="seg" data-mode="none" role="radio">None</button>- </div></div>+ <fieldset class="choice-set" id="segActivity" role="radiogroup" aria-label="Activity indicator">+ <label class="choice" data-mode="progress"><input type="radio" name="activity" value="progress">+ <span class="shot-pending">screenshot pending<br>real capture of the taskbar bar</span>+ <span class="cname">Progress bar</span>+ <span class="cdesc">Fills over the verb's usual duration, so you can tell "nearly done" from "just started".</span></label>+ <label class="choice" data-mode="flash"><input type="radio" name="activity" value="flash">+ <span class="shot-pending">screenshot pending<br>real capture of the orange flash</span>+ <span class="cname">Orange flash</span>+ <span class="cdesc">Highlights the button until you look. Says something happened, not how long is left.</span></label>+ <label class="choice" data-mode="both"><input type="radio" name="activity" value="both">+ <span class="shot-pending">screenshot pending</span>+ <span class="cname">Both</span>+ <span class="cdesc">The bar plus the flash - the attention grab and the progress at once.</span></label>+ <label class="choice" data-mode="none"><input type="radio" name="activity" value="none">+ <span class="shot-pending">screenshot pending<br>the plain button, no highlight</span>+ <span class="cname">Off</span>+ <span class="cdesc">The taskbar button never changes while a verb runs.</span></label>+ </fieldset>+ <div class="default-hint">Default: Progress bar</div> </div> <div class="settings-card">@@ -273,11 +317,15 @@ dialog.settings{max-width:560px;overflow:hidden;padding:0} <div class="divider"></div> <div class="ctlrow"><span class="lbl">Wiki part page</span> <input id="inpPart" style="flex:0 1 260px;background:var(--surface);border:1px solid var(--border);border-radius:8px;color:var(--text);padding:7px 10px;font:12.5px 'JetBrains Mono',monospace"></div>+ <div class="default-hint">Default: aravk/in-s63btg</div> </div> </div><!-- /dlgbody -->- <div class="setfoot" style="padding:0 18px 14px">Settings are stored on this machine: the Dashboard's own choices in <code>~/.adom/fusion-dashboard/settings.json</code>, the Fusion upkeep and badge choices as sticky bridge settings on the target box.</div>- <div class="btnrow" style="justify-content:flex-end;padding:0 18px 16px;margin-top:0"><button class="btn" id="btnCloseSettings" data-tip="Close settings - Escape and clicking outside do the same">Close</button></div>+ <div class="settings-footer">+ <button class="restore-btn" id="btnRestoreDefaults" type="button" data-tip="Reset every setting on this page to its default">Restore defaults</button>+ <div class="setfoot">Settings are stored on this machine: the Dashboard's own choices in <code>~/.adom/fusion-dashboard/settings.json</code>, the Fusion upkeep and badge choices as sticky bridge settings on the target box.</div>+ <div class="btnrow" style="justify-content:flex-end;margin-top:.2rem"><button class="btn" id="btnCloseSettings" data-tip="Close settings - Escape and clicking outside do the same">Close</button></div>+ </div> </dialog> <dialog id="dlgSetup">@@ -405,20 +453,24 @@ $('#btnSettings').onclick=async()=>{ $('#stBadge').textContent=bd?'badge on while driving':'badge off'; $('#stBadge').className='card-status'+(bd?' ok':''); const am=(s.activityIndicator||'progress');- document.querySelectorAll('#segActivity .seg').forEach(b=>{- b.classList.toggle('on', b.getAttribute('data-mode')===am);- b.setAttribute('aria-checked', b.getAttribute('data-mode')===am ? 'true':'false');- b.onclick=async()=>{- const mode=b.getAttribute('data-mode');- document.querySelectorAll('#segActivity .seg').forEach(x=>{- x.classList.toggle('on', x===b); x.setAttribute('aria-checked', x===b?'true':'false');});- $('#stActivity').textContent=mode==='none'?'no taskbar signal':(mode+' while a verb runs');+ const paintActivity=mode=>{+ document.querySelectorAll('#segActivity .choice').forEach(c=>{+ const on=c.getAttribute('data-mode')===mode;+ c.classList.toggle('sel',on);+ const r=c.querySelector('input');if(r)r.checked=on;+ });+ $('#stActivity').textContent=mode==='none'?'no taskbar signal':(mode+' while a verb runs');+ $('#stActivity').className='card-status'+(mode!=='none'?' ok':'');+ };+ document.querySelectorAll('#segActivity .choice').forEach(c=>{+ c.onclick=async()=>{+ const mode=c.getAttribute('data-mode');+ paintActivity(mode); try{ await act('set_activity_indicator',{mode}); toast('activity indicator: '+mode); } catch(e){ toast('could not set indicator: '+e,'err'); } }; });- $('#stActivity').textContent=am==='none'?'no taskbar signal':(am+' while a verb runs');- $('#stActivity').className='card-status'+(am!=='none'?' ok':'');+ paintActivity(am); $('#inpPart').value=dashSettings.demoPart||'aravk/in-s63btg'; $('#stPart').textContent=(dashSettings.demoPart||'').split('/').pop().toUpperCase(); const dlg=$('#dlgSettings');@@ -431,6 +483,28 @@ $('#btnSettings').onclick=async()=>{ }; $('#btnCloseSettings').onclick=()=>$('#dlgSettings').close(); $('#btnXSettings').onclick=()=>$('#dlgSettings').close();+// Two-click, auto-disarming Restore defaults (settings-layout): a destructive reset never fires on+// a single click. First click arms (red, "Confirm reset"), second click restores, and it disarms+// itself after 4s so a stray earlier click can't lie in wait.+(()=>{+ const btn=$('#btnRestoreDefaults');let armed=false,t=null;+ const disarm=()=>{armed=false;btn.classList.remove('armed');btn.textContent='Restore defaults';if(t){clearTimeout(t);t=null;}};+ btn.onclick=async()=>{+ if(!armed){armed=true;btn.classList.add('armed');btn.textContent='Confirm reset';t=setTimeout(disarm,4000);return;}+ disarm();+ const DEF={autoGreenOnLaunch:true,autoUpdateFusion:true,autoTakeOverSeat:true,showDrivingBadge:true,activityIndicator:'progress',demoPart:'aravk/in-s63btg'};+ try{+ await saveDashSettings({autoGreenOnLaunch:DEF.autoGreenOnLaunch,demoPart:DEF.demoPart});+ await act('set_auto_update',{enabled:DEF.autoUpdateFusion});+ await act('set_seat_takeover',{enabled:DEF.autoTakeOverSeat});+ await act('set_driving_badge',{enabled:DEF.showDrivingBadge});+ await act('set_activity_indicator',{mode:DEF.activityIndicator});+ toast('Settings restored to defaults','ok');+ renderSurfaces();+ $('#btnSettings').onclick(); // repaint the panel from the restored state+ }catch(e){ toast('could not restore defaults: '+e,'err'); }+ };+})(); // Click-OUTSIDE closes. A <dialog>'s backdrop is part of the dialog element, so a click that // lands on the dialog itself (not on its children) is a backdrop click - but only trust it when // the pointer is actually outside the dialog's box, or a click on the dialog's own padding
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]"+ }+}