← Commit history

v0.13.1: newbie-panel module 2.1.2 (sha256 8d3f5bf, node --check passes, self-reports 2.1.2) and one #dash-top container now owns the page gutter so the module card and the status pill row are flush by construction instead of via two margins that must be kept equal

John Lauer ·929b3c1772 ·1mo ago ·parent a856fb6
6 files changed +53−21
Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "adom-project-manager"-version = "0.13.0"+version = "0.13.1" dependencies = [  "altium-codec",  "anyhow",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-project-manager"-version = "0.13.0"+version = "0.13.1" edition = "2021" description = "Adom Project Manager — the central hub for navigating wiki boards, molecules, and libraries into Hydrogen: browse your wiki boards/projects, view symbol + footprint + 3D and the footprint layer stackup, add components to your KiCad/Altium libraries, and deliver self-contained KiCad projects. Built on adom-lbr." 
bin/adom-project-manager
⋯ 1 unchanged line ⋯
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-project-manager",-  "version": "0.13.0",+  "version": "0.13.1",   "type": "app",   "description": "Adom Project Manager — the central hub for navigating wiki boards, molecules and libraries into Hydrogen: browse your wiki boards/projects (Personal / Organization / Public), view symbol + footprint + 3D and the per-EDA footprint layer stackup, explore the board and schematic in INTERACTIVE cross-probing viewers (click a net, hover a part for stock and price), add components to your KiCad/Altium libraries, and deliver self-contained KiCad projects. Built on adom-lbr. (Formerly Adom Project Manager / adom-project-manager.)",   "dependencies": {
src/manage.html+6−3
@@ -482,7 +482,9 @@      this page never slices prose to make one. Prose appears on tap only, and a red      pill always carries its next step. */   :root{--led-green:var(--green);--led-amber:var(--yellow);--led-red:var(--red)}-  #leds{display:flex;flex-wrap:wrap;gap:8px;margin:0 22px 16px}+  #dash-top{flex:0 0 auto;padding:0 22px}+  #dash-top:empty{display:none}+  #leds{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 16px}   #leds:empty{display:none}   .led-pill{display:inline-flex;align-items:center;gap:7px;background:var(--surface);     border:1px solid var(--line);border-radius:999px;padding:4px 12px;font-size:12.5px;cursor:pointer;color:var(--text)}@@ -492,7 +494,7 @@   .led-pill[data-state=busy] .dot{background:var(--led-amber)}   .led-pill[data-state=bad] .dot{background:var(--led-red)}   .led-pill .lbl{color:var(--muted)}-  .led-detail{margin:0 22px 16px;background:var(--surface);border:1px solid var(--line);+  .led-detail{margin:0 0 16px;background:var(--surface);border:1px solid var(--line);     border-left:3px solid var(--teal);border-radius:10px;padding:12px 14px;font-size:13px;color:var(--text);max-width:70ch}   .led-detail[hidden]{display:none}   .led-detail .nx{display:block;margin-top:7px;color:var(--muted)}@@ -562,10 +564,11 @@     </div>     <div class="tv-body"><div class="tv-load" id="tv-load"><img src="loader.gif" width="48" alt=""><span>Starting tool…</span></div><iframe id="tv-frame" title="tool"></iframe></div>   </div>+  <div id="dash-top">   <div id="newbie"></div>-   <div id="leds" role="status" aria-label="System status"></div>   <div class="led-detail" id="led-detail" hidden></div>+  </div>    <div id="workspace">   <!-- HOME: full-screen gallery of boards/molecules/projects/circuits. The left list
src/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.2',                            TWO_COL_MIN: TWO_COL_MIN, PHRASE_BUDGET: PHRASE_BUDGET }; })(typeof window !== 'undefined' ? window : this);