app
Parts Search
Public Made by Adomby adom
One search box across Mouser, DigiKey and JLCPCB: parallel queries, side-by-side product photos, and a Mouser-preferred recommendation that reasons around stock and lead time.
← Commit history
v2.0.3: Module upgraded to adom-ui-design 2.1.1
6 files changed
+56−107
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-parts-search"-version = "2.0.2"+version = "2.0.3" dependencies = [ "base64", "clap",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-parts-search"-version = "2.0.2"+version = "2.0.3" edition = "2021" description = "Parts Search Electronics component search — CLI, backend HTTP service, and Hydrogen webview app" license = "MIT"
VERSION+1−1@@ -1 +1 @@-2.0.2+2.0.3
package.json+1−40@@ -38,44 +38,5 @@ "SKILL.md", "BUILD-SKILL.md", "CHANGELOG.md"- ],- "changelog": "Module upgraded to adom-ui-design 2.1.1",- "discovery_triggers": [- "parts search",- "find part",- "source component",- "source part",- "compare distributors",- "compare vendors",- "Mouser or DigiKey",- "BOM sourcing",- "price this part",- "where can I get",- "check stock",- "parts availability",- "fastest delivery",- "cheapest part",- "component search",- "part lookup",- "price breaks"- ],- "discovery_pitch": "Search Mouser, DigiKey and JLCPCB in parallel with product photos; Mouser-preferred best-source pick. Say 'with parts search' to your AI.",- "sample_prompts": [- {- "label": "source an STM32F103RBT6",- "prompt": "source an STM32F103RBT6"- },- {- "label": "where can I get an ESP32-S3-WROOM cheape",- "prompt": "where can I get an ESP32-S3-WROOM cheapest and fastest"- },- {- "label": "compare Mouser, DigiKey and JLCPCB for a",- "prompt": "compare Mouser, DigiKey and JLCPCB for an LM555"- },- {- "label": "price this BOM across distributors",- "prompt": "price this BOM across distributors"- } ]-}+}
page.json+8−49@@ -33,25 +33,13 @@ }, "hero": { "type": "image",- "path": "screenshots/hero.png"+ "path": "docs/hero.png" }, "sample_prompts": [- {- "label": "source an STM32F103RBT6",- "prompt": "source an STM32F103RBT6"- },- {- "label": "where can I get an ESP32-S3-WROOM cheape",- "prompt": "where can I get an ESP32-S3-WROOM cheapest and fastest"- },- {- "label": "compare Mouser, DigiKey and JLCPCB for a",- "prompt": "compare Mouser, DigiKey and JLCPCB for an LM555"- },- {- "label": "price this BOM across distributors",- "prompt": "price this BOM across distributors"- }+ "source an STM32F103RBT6",+ "where can I get an ESP32-S3-WROOM cheapest and fastest",+ "compare Mouser, DigiKey and JLCPCB for an LM555",+ "price this BOM across distributors" ], "discovery_triggers": [ "parts search",@@ -66,41 +54,12 @@ "where can I get", "check stock", "parts availability",- "fastest delivery",- "cheapest part",- "component search",- "part lookup",- "price breaks"+ "fastest delivery" ],- "discovery_pitch": "Search Mouser, DigiKey and JLCPCB in parallel with product photos; Mouser-preferred best-source pick. Say 'with parts search' to your AI.",+ "discovery_pitch": "Search Mouser + DigiKey + JLCPCB in parallel from one interface, with product photos and a Mouser-preferred best-source pick.", "metadata": { "platform": "linux" }, "category": "tools",- "org": "adom",- "description": "One search box across Mouser, DigiKey and JLCPCB. Parallel queries, side-by-side product photos, and a Mouser-preferred recommendation that reasons around stock and lead time. Dock Bar ready: serve, health, settings, Say it.",- "dependencies": {- "adom/adom-mouser": "*",- "adom/adom-digikey": "*",- "adom/adom-jlcpcb": "*"- },- "needs_sudo": false,- "scripts": {- "install": "./install.sh",- "uninstall": "./uninstall.sh"- },- "files": [- "bin/**",- "skills/**",- "docs/icon.svg",- "dockbar.json",- "install.sh",- "uninstall.sh",- "package.json",- "README.md",- "SKILL.md",- "BUILD-SKILL.md",- "CHANGELOG.md"- ],- "changelog": "Module upgraded to adom-ui-design 2.1.1"+ "org": "adom" }
src/app/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.1', TWO_COL_MIN: TWO_COL_MIN, PHRASE_BUDGET: PHRASE_BUDGET }; })(typeof window !== 'undefined' ? window : this);