← Commit history

1.4.5: newbie panel is a full-width band above the app body (adom-ui-design 1.11.0), prompts flow three per row without wrapping, Copy and Send to AI are line-icon buttons with tooltip + aria-label + toast, demo button is the panel's only filled button and folds with the panel, true line settings cog, toast spec

John Lauer ·3b5097ce14 ·1mo ago ·parent a59500b
3 files changed +74−45
Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "adom-chip-fetcher"-version = "1.4.4"+version = "1.4.5" dependencies = [  "anyhow",  "clap",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-chip-fetcher"-version = "1.4.4"+version = "1.4.5" edition = "2021" description = "Crawls the web on your behalf to gather everything about the chips you care about (datasheet, symbol, footprint, 3D, stock) into one library, by driving Chrome via pup." 
src/ui/index.html+72−43
@@ -120,8 +120,20 @@ header{flex:0 0 44px;height:44px;display:flex;align-items:center;gap:12px;paddin .say-head h2{font:600 17px var(--brand-heading);color:var(--text);margin:0} .say-sub{font-size:12.5px;color:var(--text-2);max-width:62ch} .say-sub b{color:var(--text);font-weight:600}-.say-list{display:flex;flex-wrap:wrap;gap:8px}-.say-item{display:inline-flex;align-items:stretch;border:1px solid var(--border);border-radius:999px;background:var(--elevated);overflow:hidden}+.say-list{display:flex;flex-wrap:wrap;gap:8px 10px}+.say-item{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--border);border-radius:999px;background:var(--elevated);padding:2px 4px 2px 2px;max-width:100%}+.say-iconbtn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;flex:none;border:none;border-radius:999px;background:none;color:var(--text-2);cursor:pointer;transition:color .15s ease,background .15s ease,transform .06s ease}+.say-iconbtn svg{width:16px;height:16px}+.say-iconbtn:hover{color:var(--accent);background:var(--overlay)}+.say-iconbtn:active{transform:translateY(1px) scale(.94)}+.say-iconbtn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}+.demo-row{margin:10px 0 12px}+.btn-demo-primary{display:inline-flex;align-items:center;gap:8px;background:var(--accent);color:var(--on-accent);border:none;border-radius:8px;padding:9px 16px;font:600 13px var(--brand-heading);cursor:pointer;transition:background .15s ease,transform .06s ease}+.btn-demo-primary svg{width:15px;height:15px}+.btn-demo-primary:hover{background:var(--accent-hover)}+.btn-demo-primary:active{transform:translateY(1px)}+.btn-demo-primary:disabled{opacity:.6;cursor:default}+@media (max-width:720px){.say-item{width:100%}.say-iconbtn{width:44px;height:44px}} .say-item:hover{border-color:var(--accent)} .say-phrase{display:inline-block;line-height:1.3;padding:7px 12px 7px 14px;background:none;border:none;color:var(--text);font:500 14px var(--brand-body);text-align:left} .say-phrase em{font-style:normal;color:var(--accent)}@@ -131,7 +143,7 @@ header{flex:0 0 44px;height:44px;display:flex;align-items:center;gap:12px;paddin .say-send:hover{background:var(--accent-10)} .say-send:active{transform:translateY(1px)} .say-send:focus-visible{outline-offset:-2px}-.say.say-folded .say-sub,.say.say-folded .say-list,.say.say-folded h2{display:none}+.say.say-folded .say-sub,.say.say-folded .say-list,.say.say-folded h2,.say.say-folded .demo-row{display:none} .say.say-folded{padding:6px 12px} .say-fold{display:block;width:100%;text-align:left;background:none;border:none;color:var(--text-2);font:500 12.5px var(--brand-body);cursor:pointer;padding:4px 2px} .say-fold:hover{color:var(--accent)}@@ -165,7 +177,9 @@ header{flex:0 0 44px;height:44px;display:flex;align-items:center;gap:12px;paddin .tour-spotlight{outline:2px solid var(--accent);outline-offset:4px;border-radius:12px;transition:outline-color .2s ease} @media (max-width:720px){.tour-hud{left:12px;right:12px;max-width:none;transform:none;bottom:12px}} /* ---- The newbie panel: say strip + LEDs + getting started, one closable block ---- */-.newbie{position:relative}+.newbie{position:relative;flex:0 0 auto;border-bottom:1px solid var(--border);background:var(--bg);padding:14px 20px 16px}+.newbie-inner{max-width:1100px;margin:0 auto}+.newbie-close{position:absolute;top:10px;right:14px;z-index:2} .newbie-close{position:absolute;top:10px;right:10px;z-index:2} /* ---- Welcome (first run) ---- */ .welcome{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin-bottom:16px;max-width:760px}@@ -474,8 +488,9 @@ dialog.settings{width:min(680px,92vw);overflow:hidden} .restore-btn.armed{color:var(--red);border-color:var(--red-35);background:var(--red-10)}  /* ---- Toasts (toasts skill): bottom center, stack newest on top, three types, pause on hover ---- */-#toasts{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);display:flex;flex-direction:column-reverse;gap:8px;z-index:var(--z-toast);max-width:min(520px,calc(100vw - 24px));pointer-events:none}+#toasts{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);display:flex;flex-direction:column-reverse;gap:8px;z-index:var(--z-toast);max-width:min(520px,calc(100vw - 24px));pointer-events:none} .toast{display:flex;align-items:center;gap:10px;pointer-events:auto;background:var(--overlay);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:10px;padding:10px 12px 10px 12px;color:var(--text);font-size:13px;box-shadow:var(--shadow);animation:toastin .18s ease}+@media (prefers-reduced-motion:reduce){.toast{animation:none}} @keyframes toastin{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}} .toast.ok{border-left-color:var(--green)}.toast.err{border-left-color:var(--red)} .toast .ticon{flex:none;display:inline-flex;color:var(--accent)}@@ -565,7 +580,7 @@ dialog.settings{width:min(680px,92vw);overflow:hidden}     <button class="iconbtn" id="btn-arch" aria-label="Architecture map" data-tooltip="Architecture map: every tool chip fetcher depends on. Local CLIs that run in your container, native Rust libraries, remote service containers called over HTTPS, and the sub-apps you may also need to modify. Open this before editing chip fetcher."><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3L3 6v15l6-3 6 3 6-3V3l-6 3-6-3z"/><path d="M9 3v15M15 6v15"/></svg></button>     <button class="iconbtn" id="btn-reveal-lib" aria-label="Library folder" data-tooltip="Reveal the chip fetcher library folder in the VS Code Explorer sidebar. Opens ~/project/chip-fetcher/library/ so you can see every chip directory."><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7.5a2 2 0 0 1 2-2h4l2 2.5h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7.5z"/></svg></button>     <button class="iconbtn" id="btn-help" aria-pressed="false" aria-label="Getting started" data-tooltip="Show or hide the newbie panel: the say-it phrases, the component lights, and getting started with its demo fetch."><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M9.5 9a2.5 2.5 0 1 1 3.4 2.33c-.7.28-1.4.84-1.4 1.67v.5"/><path d="M12 17h.01"/></svg></button>-    <button class="iconbtn" id="btn-settings" aria-label="Settings" data-tooltip="Settings: components and their install buttons, the taskbar progress mode, dashboard behaviour, and vendor logins."><!-- the shared Adom cog (assets/icons/settings-cog.svg), inlined so it follows currentColor --><svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M12 8a4 4 0 0 1 4 4a4 4 0 0 1-4 4a4 4 0 0 1-4-4a4 4 0 0 1 4-4m0 2a2 2 0 0 0-2 2a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2m-2 12c-.25 0-.46-.18-.5-.42l-.37-2.65c-.63-.25-1.17-.59-1.69-.99l-2.49 1.01c-.22.08-.49 0-.61-.22l-2-3.46c-.13-.22-.07-.49.12-.64l2.11-1.66c-.04-.32-.07-.64-.07-.97s.03-.66.07-1L2.46 9.37c-.19-.15-.25-.42-.12-.64l2-3.46c.12-.22.39-.31.61-.22l2.49 1c.52-.39 1.06-.73 1.69-.98l.37-2.65c.04-.24.25-.42.5-.42h4c.25 0 .46.18.5.42l.37 2.65c.63.25 1.17.59 1.69.98l2.49-1c.22-.09.49 0 .61.22l2 3.46c.12.22.07.49-.12.64l-2.11 1.63c.04.34.07.67.07 1s-.03.65-.07.97l2.11 1.66c.19.15.24.42.12.64l-2 3.46c-.12.22-.39.3-.61.22l-2.49-1.01c-.52.4-1.06.74-1.69.99l-.37 2.65c-.04.24-.25.42-.5.42h-4Z"/></svg></button>+    <button class="iconbtn" id="btn-settings" aria-label="Settings" data-tooltip="Settings: components and their install buttons, the taskbar progress mode, dashboard behaviour, and vendor logins."><!-- the shared Adom cog (adom-ui-design assets/icons/settings-cog-outline.svg), inlined so it follows currentColor; the old filled-path version was a defect in the pack --><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3.2"/><path d="M12 2.6v2.2M12 19.2v2.2M21.4 12h-2.2M4.8 12H2.6M18.6 5.4l-1.6 1.6M7 17l-1.6 1.6M18.6 18.6L17 17M7 7L5.4 5.4"/></svg></button>   </div>   <div class="hover-popover" id="app-info" role="dialog" aria-label="App information">     <div class="popover-title">Chip Fetcher</div>@@ -593,33 +608,34 @@ dialog.settings{width:min(680px,92vw);overflow:hidden}   <div class="project-pills" id="project-tabs" role="tablist" aria-label="Projects" hidden></div> </div> +    <section class="newbie" id="newbie">+  <div class="newbie-inner">+  <button class="iconbtn newbie-close" id="btn-newbie-hide" aria-label="Close the newbie panel" data-tooltip="Tuck the whole panel away (phrases, component lights, getting started). The ? in the header brings it back."><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg></button>+  <section class="say" id="say" aria-labelledby="sayTitle">+    <div class="say-head">+      <h2 id="sayTitle">Say it</h2>+      <span class="say-sub">You never need this page's buttons: just tell your AI what you want, using the word <b>cf</b> (chip fetcher). Copy a prompt, or send it straight to a new AI thread.</span>+    </div>+    <div class="demo-row"><button class="btn-demo-primary" id="btn-tour"><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="M6 4l14 8-14 8z"/></svg><span id="btnTourLabel">Play the 1-minute demo</span></button></div>+    <div class="say-list" id="sayList"></div>+    <button class="say-fold" id="sayFold" hidden>Say it: 6 phrases for your AI. Tap to expand.</button>+  </section>+  <div class="leds" id="leds" aria-label="Component status"></div>+  <section class="welcome" id="welcome">+    <div class="welcome-head">+      <h2>Getting started</h2>+    </div>+    <p>Once cf feels familiar, close this whole panel with the X in its corner; the ? in the header brings it back. The library starts with a few sample parts seeded from the Adom Wiki so the cards below are real; delete them any time.</p>+    <ol>+      <li><span class="num">1</span><div>Check the component lights above and fix anything that is not green. The Components card in Settings has an Install button for each one.<br><button class="btn" id="btn-welcome-settings">Open Settings to Components</button></div></li>+      <li><span class="num">2</span><div>Ask your AI for a chip by part number, using the word <b>cf</b>. Try it right now with a serious one, the STM32H757 (a 480 MHz dual-core H7):<br><button class="btn" id="btn-welcome-example">Copy: "get me all the files for the STM32H757XIH6 with cf"</button></div></li>+      <li><span class="num">3</span><div>Cards appear here as parts are fetched. Each card carries the datasheet, the KiCad symbol, the footprint and the 3D model, with one-click open and send-to-EDA buttons. The demo above runs one fetch for you, live.</div></li>+    </ol>+  </section>+  </div>+</section> <div class="app-body" id="appBody">   <div class="content" id="content">-    <section class="newbie" id="newbie">-    <button class="iconbtn newbie-close" id="btn-newbie-hide" aria-label="Close the newbie panel" data-tooltip="Tuck the whole panel away (phrases, component lights, getting started). The ? in the header brings it back."><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg></button>-    <section class="say" id="say" aria-labelledby="sayTitle">-      <div class="say-head">-        <h2 id="sayTitle">Say it</h2>-        <span class="say-sub">You never need this page's buttons: just tell your AI what you want, using the word <b>cf</b> (chip fetcher). Copy a phrase, or send it straight to a new AI thread.</span>-      </div>-      <div class="say-list" id="sayList"></div>-      <button class="say-fold" id="sayFold" hidden>Say it: 6 phrases for your AI. Tap to expand.</button>-    </section>-    <div class="leds" id="leds" aria-label="Component status"></div>-    <section class="welcome" id="welcome">-      <div class="welcome-head">-        <h2>Getting started</h2>-      </div>-      <p>New here? Take the tour: captions on your screen, optional voiceover, and a real fetch landing in front of you.</p>-      <p><button class="btn btn-primary" id="btn-tour">Play the 1-minute demo</button></p>-      <p>Once cf feels familiar, close this whole panel with the X in its corner; the ? in the header brings it back. The library starts with a few sample parts seeded from the Adom Wiki so the cards below are real; delete them any time.</p>-      <ol>-        <li><span class="num">1</span><div>Check the component lights above and fix anything that is not green. The Components card in Settings has an Install button for each one.<br><button class="btn" id="btn-welcome-settings">Open Settings to Components</button></div></li>-        <li><span class="num">2</span><div>Ask your AI for a chip by part number, using the word <b>cf</b>. Try it right now with a serious one, the STM32H757 (a 480 MHz dual-core H7):<br><button class="btn btn-primary" id="btn-welcome-example">Copy: "get me all the files for the STM32H757XIH6 with cf"</button></div></li>-        <li><span class="num">3</span><div>Cards appear here as parts are fetched. Each card carries the datasheet, the KiCad symbol, the footprint and the 3D model, with one-click open and send-to-EDA buttons. The demo above runs one fetch for you, live.</div></li>-      </ol>-    </section>-    </section>     <div id="grid-view"><div class="cards" id="cards"><div class="empty-state">Loading the library</div></div></div>     <div id="validation-view" class="view" hidden></div>     <div id="sources-view" class="view" hidden></div>@@ -1118,19 +1134,30 @@ async function buildSay() {   for (const p of phrases) {     const item = document.createElement('div'); item.className = 'say-item';     const b = document.createElement('button'); b.className = 'say-phrase'; b.type = 'button';-    b.setAttribute('data-tooltip', 'Copy this phrase to your clipboard.');+    b.setAttribute('data-tooltip', 'Copy this prompt to your clipboard');+    b.setAttribute('aria-label', 'Copy this prompt to your clipboard');     b.innerHTML = '&ldquo;' + escapeHtml(p).replace(/chip ?fetcher|\bcf\b/gi, m => '<em>' + m + '</em>') + '&rdquo;';-    b.onclick = async () => {+    const doCopy = async () => {       if (await copyText(p)) cfToast('Copied to clipboard. Paste it into any AI thread.', { type: 'ok' });-      else cfToast('Could not reach the clipboard. Select the phrase and copy it by hand.', { type: 'err' });+      else cfToast('Could not reach the clipboard. Select the prompt and copy it by hand.', { type: 'err' });     };-    const c = document.createElement('button'); c.className = 'say-try'; c.type = 'button'; c.textContent = 'Copy';-    c.setAttribute('data-tooltip', 'Copy this phrase to your clipboard.');-    c.onclick = b.onclick;-    const t = document.createElement('button'); t.className = 'say-send'; t.type = 'button'; t.textContent = 'Send to AI';-    t.setAttribute('data-tooltip', 'Open a NEW AI thread and send this phrase to it. Existing threads are never touched.');-    t.onclick = async () => {-      t.disabled = true;+    b.onclick = doCopy;+    // Icon + tooltip + toast is one contract (newbie-panel skill): an icon+    // gives none of the confirmation a labelled button does, so both of the+    // others are mandatory on every click.+    const copyBtn = document.createElement('button');+    copyBtn.className = 'say-iconbtn'; copyBtn.type = 'button';+    copyBtn.setAttribute('data-tooltip', 'Copy this prompt to your clipboard');+    copyBtn.setAttribute('aria-label', 'Copy this prompt to your clipboard');+    copyBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1"/></svg>';+    copyBtn.onclick = doCopy;+    const sendBtn = document.createElement('button');+    sendBtn.className = 'say-iconbtn'; sendBtn.type = 'button';+    sendBtn.setAttribute('data-tooltip', 'Send this prompt to a new AI thread');+    sendBtn.setAttribute('aria-label', 'Send this prompt to a new AI thread');+    sendBtn.innerHTML = '<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="M21 3 10.5 13.5"/><path d="M21 3l-6.8 18-3.7-7.5L3 9.8z"/></svg>';+    sendBtn.onclick = async () => {+      sendBtn.disabled = true;       try {         const r = await (await postJson('/api/say-send', { phrase: p })).json();         if (r.ok) { cfToast('Sent to a new AI thread.', { type: 'ok' }); gradSave({ aiDriven: true }); applyGraduation(); }@@ -1140,9 +1167,9 @@ async function buildSay() {         }       } catch (e) {         if (await copyText(p)) cfToast('Could not reach the AI panel; copied instead.', { type: 'err' });-      } finally { t.disabled = false; }+      } finally { sendBtn.disabled = false; }     };-    item.appendChild(b); item.appendChild(c); item.appendChild(t); list.appendChild(item);+    item.appendChild(b); item.appendChild(copyBtn); item.appendChild(sendBtn); list.appendChild(item);   } } @@ -1822,6 +1849,7 @@ function tourSetPaused(p) {   if (p) postJson('/api/demo/quiet', {}).catch(() => {}); } function tourStart() {+  const tb = $id('btn-tour'); if (tb) { tb.disabled = true; $id('btnTourLabel').textContent = 'Demo running'; }   setDemoMuted(demoMuted());   $id('tourHud').hidden = false;   tourSetPausedIcon();@@ -1830,6 +1858,7 @@ function tourStart() { } function tourSetPausedIcon() { $id('tourPlayIco').innerHTML = '<path d="M10 5v14M15 5v14"/>'; } function tourExit(finished) {+  const tb = $id('btn-tour'); if (tb) { tb.disabled = false; $id('btnTourLabel').textContent = 'Play the 1-minute demo'; }   clearTimeout(tourTimer); tourTimer = null; tourIdx = -1; tourPaused = false;   $id('tourHud').hidden = true;   if (tourSpot) { tourSpot.classList.remove('tour-spotlight'); tourSpot = null; }