← Commit history

Publish 1.9.203

John Lauer ·b76c66ace9 ·1mo ago ·parent 4a1cd01
2 files changed +51−25
dashboard/ui/vendor/newbie-panel.js+44−15
@@ -154,6 +154,7 @@   function tipFor(node, text) {     node.setAttribute('aria-label', text);     node.removeAttribute('title');          // two tooltips on one control is a bug+    node.__anpSetTip = function (t) { text = t; if (tip && tip.classList.contains('on')) tip.textContent = t; };     node.addEventListener('mouseenter', function (e) {       clearTimeout(tipT);       tipT = setTimeout(function () {@@ -307,21 +308,32 @@     /* the header ? is a real TOGGLE, both ways, with aria-pressed and a label that swaps */     var toggleBtn = typeof cfg.toggleButton === 'string'       ? document.querySelector(cfg.toggleButton) : cfg.toggleButton;-    function setOpen(open, remember) {+    /* setOpen(open, remember, source)+     * source is 'user' when a person clicked the X or the header toggle, and 'api' when the+     * app called open()/close() itself. It is passed to onToggle, because an app with a+     * graduation policy calls open()/close() from inside its own onToggle handler: without a+     * source the callback cannot tell its own action from a person's, so graduating the panel+     * open pins it open forever and graduating it closed marks it user-dismissed. It looks+     * like "the panel sometimes ignores graduation". (lbr, 2026-08-25) */+    function setOpen(open, remember, source) {       panel.hidden = !open;       wrap.style.display = open ? '' : 'none';   /* no dead margin when collapsed */       if (toggleBtn) {+        var label = open ? 'Hide the sample prompts' : 'Show the sample prompts';         toggleBtn.setAttribute('aria-pressed', open ? 'true' : 'false');-        toggleBtn.setAttribute('aria-label', open ? 'Hide the sample prompts' : 'Show the sample prompts');+        toggleBtn.setAttribute('aria-label', label);+        /* the TOOLTIP has to swap with the state too, or a person with the panel already in+         * front of them hovers the control and is told it will show them the panel */+        if (toggleBtn.__anpSetTip) toggleBtn.__anpSetTip(label);       }       if (remember !== false) { try { localStorage.setItem(key, open ? '1' : '0'); } catch (e) {} }-      if (typeof cfg.onToggle === 'function') cfg.onToggle(open);+      if (typeof cfg.onToggle === 'function') cfg.onToggle(open, source || 'api');     }-    x.addEventListener('click', function () { setOpen(false); });+    x.addEventListener('click', function () { setOpen(false, true, 'user'); });     if (toggleBtn) {       if (!toggleBtn.innerHTML.trim()) toggleBtn.innerHTML = ICON.help;       tipFor(toggleBtn, 'Show the sample prompts');-      toggleBtn.addEventListener('click', function () { setOpen(panel.hidden); });+      toggleBtn.addEventListener('click', function () { setOpen(panel.hidden, true, 'user'); });     }     var saved = null;     try { saved = localStorage.getItem(key); } catch (e) {}@@ -345,8 +357,11 @@     }      return {-      panel: panel, open: function () { setOpen(true); }, close: function () { setOpen(false); },-      toggle: function () { setOpen(panel.hidden); }, isOpen: function () { return !panel.hidden; },+      panel: panel,+      open:   function (o) { setOpen(true,  !(o && o.remember === false), 'api'); },+      close:  function (o) { setOpen(false, !(o && o.remember === false), 'api'); },+      toggle: function () { setOpen(panel.hidden, true, 'api'); },+      isOpen: function () { return !panel.hidden; },       overBudget: over, toast: toast, selfCheck: function () { return selfCheck(panel); }     };   }@@ -361,6 +376,10 @@     'card padding':         ['padding',         '18px 20px 20px'],     'column gap':           ['columnGap',       '24px']   };+  /* selfCheck(panel) - pass the panel explicitly when the page mounts more than one, or+   * mounts into a shadow root. The no-arg form takes the FIRST .anp in the light DOM, which+   * is right for the single-panel case and silently wrong for the others. Returns+   * {ok:false, error} rather than throwing when there is no panel. (chip-fetcher, 2026-08-25) */   function selfCheck(panel) {     panel = panel || document.querySelector('.anp');     if (!panel) return { ok: false, error: 'no .anp panel on the page' };@@ -374,13 +393,23 @@     }     Object.keys(CANON).forEach(function (k) {       var want = CANON[k][1];-      if (want.indexOf('snap:') === 0) want = snap(parseFloat(want.slice(5)));-      var got = cs[CANON[k][0]];-      /* compare numerically with a sub-pixel tolerance: Chrome prints 0.666667px where the-       * arithmetic gives 0.6666666666, and a string compare fails a panel that is correct */-      var pass = (parseFloat(want) && parseFloat(got))-        ? Math.abs(parseFloat(got) - parseFloat(want)) < 0.01-        : got === want;+      var got = cs[CANON[k][0]], declared = CANON[k][1];+      /* Border widths need BOTH candidates accepted. Chrome reports the USED width, snapped+       * to whole device pixels, on some surfaces (ah's webview and pup on a dpr-1.5 laptop+       * both return 0.666667px for a declared 1px) and reports the DECLARED width on others+       * (an emulated dpr returns 1px at every ratio). Accepting only one of the two fails a+       * correct panel in the other environment, and at dpr 1.5 specifically, which is the+       * machine this fleet is reviewed on. Found by nb, 2026-08-25. */+      var pass;+      if (declared.indexOf('snap:') === 0) {+        var d = parseFloat(declared.slice(5)), g = parseFloat(got);+        pass = Math.abs(g - d) < 0.01 || Math.abs(g - parseFloat(snap(d))) < 0.01;+        want = d + 'px or ' + snap(d);+      } else {+        pass = (parseFloat(want) && parseFloat(got))+          ? Math.abs(parseFloat(got) - parseFloat(want)) < 0.01+          : got === want;+      }       if (!pass) ok = false;       rows.push({ what: k, want: want, got: got, pass: pass });     });@@ -399,6 +428,6 @@     return { ok: ok, rows: rows };   } -  root.AdomNewbiePanel = { mount: mount, toast: toast, selfCheck: selfCheck, version: '1.0.0',+  root.AdomNewbiePanel = { mount: mount, toast: toast, selfCheck: selfCheck, version: '2.1.0',                            TWO_COL_MIN: TWO_COL_MIN, PHRASE_BUDGET: PHRASE_BUDGET }; })(typeof window !== 'undefined' ? window : this);
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