← Commit history

Publish 2.0.331

John Lauer ·12d811e4e1 ·1mo ago ·parent 8162f90
3 files changed +173−114
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "pup-bridge",-  "version": "2.0.330",+  "version": "2.0.331",   "type": "app",   "description": "The pup SKILL PACK \u2014 the AI skills that teach a cloud/container AI how to drive the Adom Desktop Puppeteer (pup) bridge (browser_* verbs): the everyday `pup` skill plus focused sub-skills for windows/sessions/tabs, screenshots+recording, browsers+Chrome install, and driving the Adom wiki. Installs into ~/.claude/skills + ~/.codex/skills. DOCS ONLY \u2014 this is NOT the bridge runtime. The bridge itself (the Node program that drives the browser) runs on the DESKTOP: Adom Desktop bundles it and auto-installs/updates it, and it's published separately on the Releases tab as adom-bridge-puppeteer-v<ver>.zip. The pkg version is kept in lockstep with the Release version so the page's max version row always carries BOTH a pkg tarball (skills) and a runtime zip (bridge) \u2014 a skills-only or runtime-only bump on the top row breaks constraint installs. TL;DR: pkg install = the skills for your AI; the bridge = the Release zip, which Adom Desktop manages for you.",   "keywords": [
page.json+1−1
@@ -4,7 +4,7 @@   "slug": "pup-bridge",   "title": "Pup - Puppeteer Bridge",   "brief": "pup is the AI's own browser: a real, full Chrome on the user's desktop that the AI fully controls (a sandbox, not the user's signed-in browser). Rides Bridge; pup_* verbs open windows and tabs, navigate, screenshot, and eval JS.",-  "version": "2.0.330",+  "version": "2.0.331",   "tags": [     "skills",     "pup",
tools/pup-dashboard+171−112
@@ -249,54 +249,75 @@ async function buildStatus() { // \\s), or JS consumes them as string escapes before the browser ever sees the regex: \\b becomes a // backspace char, \\d becomes 'd', \\s becomes 's'. That shipped live (2.0.326-2.0.329): the status // pills rendered 'pup 2' / 'in talled and pinned' and the say-chip teal noun highlight never applied.-const PAGE = `<!doctype html><meta charset="utf-8"><title>pup</title>+// NOTE: PAGE is a TEMPLATE LITERAL. Every regex escape inside it must be DOUBLED (\\b, \\d,+// \\s), or JS consumes them as string escapes before the browser sees the regex: \b becomes a+// backspace char, \d becomes 'd', \s becomes 's'. That shipped live (2.0.326-2.0.329): the status+// pills rendered 'pup 2' / 'in talled and pinned' and the say-chip teal noun highlight never applied.+// The app mark below is the SAME artifact as docs/img/dock-pup.svg (webview.favicon), inlined at+// 18x18 per dash-header; if you tune one, tune the file and re-verify BOTH 16px tab and 18px header.+const PAGE = `<!doctype html><meta charset="utf-8"><title>Pup</title> <style>-:root{--surface:#161b22;--surface2:#21262d;--line:#30363d;--teal:#00b8b1;--muted:#8b949e;--text:#e6edf3;--body:#c9d1d9;--led-green:#3fb950;--led-amber:#d29922;--led-red:#f85149;--sans:"Familjen Grotesk",system-ui,sans-serif;color-scheme:dark}+:root{--surface:#161b22;--surface2:#21262d;--line:#30363d;--teal:#00b8b1;--on-accent:#04262a;--muted:#8b949e;--text:#e6edf3;--body:#c9d1d9;--led-green:#3fb950;--led-amber:#d29922;--led-red:#f85149;--sans:"Familjen Grotesk",system-ui,sans-serif;color-scheme:dark} *{box-sizing:border-box} body{margin:0;background:#0d1117;color:var(--text);font:14px/1.5 var(--sans)}-.wrap{max-width:820px;margin:0 auto;padding:0 20px 60px}-/* ── 44px three-zone header (dash-header) ── */-.hdr{height:44px;display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--line);position:sticky;top:0;background:#0d1117;z-index:5}-.hdr .brand{display:flex;align-items:center;gap:9px;position:relative;cursor:default}-.hdr .bicon{width:18px;height:18px;border-radius:5px;background:var(--teal);display:flex;align-items:center;justify-content:center;flex:none}-.hdr .bicon svg{width:12px;height:12px}-.hdr .bname{font-weight:700;font-size:15px;letter-spacing:-.01em}-.hdr .bsub{color:var(--muted);font-size:12.5px}-.hdr .pop{position:absolute;top:34px;left:0;background:var(--surface);border:1px solid var(--line);border-radius:8px;padding:9px 12px;font-size:12px;color:var(--muted);white-space:nowrap;box-shadow:0 8px 24px rgba(0,0,0,.5);opacity:0;pointer-events:none;transition:opacity .12s ease .35s;z-index:9}+/* ── header: 44px, EDGE TO EDGE, padding inside the bar (dash-header) ── */+.hdr{height:44px;display:flex;align-items:center;padding:0 16px;background:var(--surface);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20}+.hdr .brand{display:flex;align-items:center;position:relative;cursor:default}+.hdr .mark{width:18px;height:18px;flex:none;color:var(--text);display:flex}+.hdr .mark svg{width:18px;height:18px;display:block}+.hdr .bname{font-family:var(--sans);font-weight:600;font-size:15px;color:var(--text);letter-spacing:-.01em;margin-left:9px}+.hdr .bsub{font-size:13px;font-weight:400;color:var(--muted);margin-left:8px}+.hdr .pop{position:absolute;top:36px;left:0;background:var(--surface);border:1px solid var(--line);border-radius:8px;padding:9px 12px;font-size:12px;color:var(--muted);white-space:nowrap;box-shadow:0 8px 24px rgba(0,0,0,.5);opacity:0;pointer-events:none;transition:opacity .12s ease .6s;z-index:30} .hdr .brand:hover .pop{opacity:1} .hdr .pop b{color:var(--text);font-weight:600}.hdr .pop a{color:var(--teal);text-decoration:none}-.hdr .sp{flex:1}-.hdr .target{display:inline-flex;align-items:center;gap:7px;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:3px 11px;font:12px ui-monospace,monospace;color:var(--body)}-.hdr .target .dot{width:8px;height:8px;border-radius:50%;background:var(--led-green)}-.hdr .target[data-state=bad] .dot{background:var(--led-red)}.hdr .target[data-state=busy] .dot{background:var(--led-amber)}-.hdr .ibtn{background:none;border:0;color:var(--muted);cursor:pointer;padding:6px;border-radius:6px;display:flex;align-items:center}-.hdr .ibtn:hover{color:var(--teal);background:var(--surface)}-.hdr .ibtn svg{width:18px;height:18px}-/* ── say strip (say-strip) ── */-.say{margin:18px 0 0;background:var(--surface);border:1px solid var(--line);border-left:3px solid var(--teal);border-radius:10px;padding:14px 16px;position:relative}-.say h2{font:600 15px var(--sans);margin:0}-.say .expl{color:var(--muted);font-size:13.5px;margin:4px 0 0}.say .expl b.noun{color:var(--teal);font-weight:700}-.say-x{position:absolute;top:10px;right:12px;background:none;border:0;color:var(--muted);font-size:18px;line-height:1;cursor:pointer}.say-x:hover{color:var(--text)}-.say-chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}-.say-chip{display:inline-flex;align-items:center;gap:8px;background:#0d1117;border:1px solid var(--line);border-radius:999px;padding:5px 6px 5px 12px;font-size:13.5px;cursor:pointer}-.say-chip em{color:var(--teal);font-style:normal}-.say-chip .copy,.say-chip .send{font:500 11px var(--body);background:none;border:1px solid var(--line);border-radius:999px;padding:2px 9px;cursor:pointer}-.say-chip .copy{color:var(--muted)}.say-chip .send{color:var(--teal);border-color:rgba(0,184,177,.45)}-.say-chip:hover .copy{color:var(--teal);border-color:var(--teal)}.say-chip .send:hover{background:rgba(0,184,177,.12)}-/* ── status pill row (status-leds) ── */-.leds{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}+.hdr .right{margin-left:auto;display:flex;align-items:center;gap:6px}+.chip{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--line);border-radius:999px;padding:3px 10px;font:12px ui-monospace,monospace;color:var(--muted)}+.chip b{color:var(--text);font-weight:400}+.chip .dot{width:8px;height:8px;border-radius:50%;background:#6e7681}+.chip[data-state=ok] .dot{background:var(--led-green)}.chip[data-state=busy] .dot{background:var(--led-amber)}.chip[data-state=bad] .dot{background:var(--led-red)}+.ibtn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;background:none;border:1px solid transparent;border-radius:6px;color:var(--muted);cursor:pointer;padding:0}+.ibtn svg{width:18px;height:18px}+.ibtn:hover{color:var(--teal);border-color:var(--line);background:rgba(0,184,177,.08)}+.ibtn:focus-visible{outline:2px solid var(--teal);outline-offset:1px}+/* ── page body ── */+.wrap{max-width:1180px;margin:0 auto;padding:0 20px 60px}+/* ── newbie panel: ONE full-width band, TWO columns ── */+.newbie{background:var(--surface);border:1px solid var(--line);border-left:3px solid var(--teal);border-radius:12px;padding:18px 20px 20px;margin:16px 0 20px;position:relative}+.newbie h2{font:600 15px var(--sans);color:var(--text);margin:0}+.newbie .expl{font-size:13.5px;color:var(--muted);margin:4px 0 0;max-width:70ch}+.newbie .expl b.noun{color:var(--teal);font-weight:700}+.nb-x{position:absolute;top:12px;right:14px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;background:none;border:1px solid transparent;border-radius:6px;color:var(--muted);font-size:17px;line-height:1;cursor:pointer}+.nb-x:hover{color:var(--teal);border-color:var(--line)}+.nb-cols{display:grid;grid-template-columns:220px 1fr;gap:24px;align-items:start;margin-top:14px}+.nb-right{border-left:1px solid var(--line);padding-left:24px}+.play{display:inline-flex;align-items:center;gap:8px;background:var(--teal);color:var(--on-accent);border:0;border-radius:8px;padding:10px 18px;font:600 14px var(--sans);cursor:pointer}+.play svg{width:16px;height:16px}+.play:disabled{opacity:.55;cursor:default}+.play-sub{font-size:12px;color:var(--muted);margin-top:8px}+/* prompts TABLE, not pills */+table.prompts{width:100%;border-collapse:collapse;table-layout:fixed}+table.prompts tr{height:40px;border-bottom:1px solid var(--line);cursor:pointer}+table.prompts tr:last-child{border-bottom:0}+table.prompts tr:hover{background:rgba(255,255,255,.03)}+table.prompts td.phrase{font-size:14px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:12px}+table.prompts td.phrase em{color:var(--teal);font-style:normal}+table.prompts td.acts{width:72px;text-align:right;white-space:nowrap}+table.prompts td.acts .ibtn{display:inline-flex;vertical-align:middle;margin-left:6px}+.nb-collapsed{display:none;font-size:13px;color:var(--muted);cursor:pointer;padding:10px 0}+.nb-collapsed:hover{color:var(--teal)}+.newbie.folded .nb-cols,.newbie.folded h2,.newbie.folded .expl,.newbie.folded .nb-x{display:none}+.newbie.folded .nb-collapsed{display:block}+.newbie.folded{padding:6px 20px}+/* ── status pills ── */+.leds{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px} .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;position:relative} .led-pill .dot{width:8px;height:8px;border-radius:50%;background:#6e7681} .led-pill[data-state=ok] .dot{background:var(--led-green)}.led-pill[data-state=busy] .dot{background:var(--led-amber)}.led-pill[data-state=bad] .dot{background:var(--led-red)} .led-pill .st{color:var(--muted)}-.led-detail{position:absolute;top:28px;left:0;min-width:240px;max-width:360px;background:var(--surface);border:1px solid var(--line);border-radius:8px;padding:10px 12px;font-size:12.5px;color:var(--body);box-shadow:0 8px 24px rgba(0,0,0,.5);z-index:9;white-space:normal}+.led-detail{position:absolute;top:28px;left:0;min-width:240px;max-width:360px;background:var(--surface);border:1px solid var(--line);border-radius:8px;padding:10px 12px;font-size:12.5px;color:var(--body);box-shadow:0 8px 24px rgba(0,0,0,.5);z-index:9;white-space:normal;text-align:left} .led-detail .fix{color:var(--teal);margin-top:6px}-/* ── demo (dash-demo) ── */-.demo{margin-top:20px;padding:15px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}-.demo .lbl{font-weight:600}.demo .det{color:var(--muted);font-size:13px;margin:4px 0 10px}-.demo button.play{background:var(--teal);color:#04262a;border:0;border-radius:7px;padding:9px 15px;font:600 14px var(--sans);cursor:pointer}-.demo button.play:disabled{opacity:.5;cursor:default}-.transport{margin-top:12px;padding:12px;border:1px solid var(--line);border-radius:9px;background:#0d1117}+/* ── demo transport ── */+.transport{margin:0 0 20px;padding:12px;border:1px solid var(--line);border-radius:10px;background:var(--surface)} .tstep{font:13px ui-monospace,monospace;color:var(--body);margin-bottom:8px}.tstep #tstepn{color:var(--teal);font-weight:600}.tstep #tstate{color:var(--muted);margin-left:8px} .tbtns{display:flex;gap:8px;margin-bottom:10px;align-items:center} .tbtn{background:var(--surface2);color:var(--text);border:1px solid var(--line);border-radius:7px;padding:6px 14px;font-size:16px;line-height:1;cursor:pointer}@@ -304,97 +325,134 @@ body{margin:0;background:#0d1117;color:var(--text);font:14px/1.5 var(--sans)} .tbtn.texit{border-color:rgba(248,81,73,.5);color:var(--led-red)}.tbtn.texit:hover{background:rgba(248,81,73,.1)} .tsep{width:1px;height:20px;background:var(--line);margin:0 2px} .tbar{height:6px;background:var(--surface2);border-radius:3px;overflow:hidden}.tfill{height:100%;width:0;background:var(--teal);transition:width .3s ease}-/* ── toast (toasts) ── */+/* ── tooltip + toast ── */+#tip{position:fixed;background:var(--surface2);border:1px solid var(--line);border-radius:6px;padding:5px 9px;font-size:12px;color:var(--body);pointer-events:none;z-index:99;box-shadow:0 6px 18px rgba(0,0,0,.5)} #toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);display:flex;flex-direction:column;gap:8px;align-items:center;z-index:99}-.toast{background:var(--surface2);border:1px solid var(--line);border-left:3px solid var(--teal);color:var(--body);border-radius:8px;padding:9px 15px;font-size:13px;box-shadow:0 8px 24px rgba(0,0,0,.5);opacity:1;transition:opacity .3s}+.toast{background:var(--surface2);border:1px solid var(--line);border-left:3px solid var(--teal);color:var(--body);border-radius:8px;padding:9px 15px;font-size:13px;box-shadow:0 8px 24px rgba(0,0,0,.5);transition:opacity .3s} .toast.success{border-left-color:var(--led-green)}.toast.error{border-left-color:var(--led-red)}.toast.out{opacity:0} .hidden{display:none!important}-@media(max-width:480px){.hdr .bsub{display:none}}+@media(max-width:760px){.nb-cols{grid-template-columns:1fr}.nb-right{border-left:0;border-top:1px solid var(--line);padding-left:0;padding-top:16px}.play{width:100%;justify-content:center}}+@media(max-width:720px){.hdr .bsub{display:none}} </style>-<div class="wrap">-  <header class="hdr">-    <div class="brand" tabindex="0">-      <span class="bicon"><svg viewBox="0 0 24 24" fill="#04262a"><circle cx="8" cy="9" r="3"/><circle cx="15" cy="14" r="4"/></svg></span>-      <span class="bname">Pup</span>-      <span class="bsub">AI browser control</span>-      <div class="pop" id="pop">loading&hellip;</div>-    </div>-    <div class="sp"></div>-    <div class="target" id="target" hidden><span class="dot"></span><span id="targetbox">&hellip;</span></div>-    <button class="ibtn" id="recall" title="Show getting-started help" aria-label="Show help">-      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>+<header class="hdr">+  <div class="brand" tabindex="0">+    <span class="mark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="2.75" y="4.5" width="18.5" height="15" rx="2.5"/><path d="M2.75 8.75h18.5"/><circle cx="12" cy="14.2" r="3.1"/></svg></span>+    <span class="bname">Pup</span>+    <span class="bsub">AI browser control</span>+    <div class="pop" id="pop">loading&hellip;</div>+  </div>+  <div class="right">+    <span class="chip" id="target" hidden><span class="dot"></span><span id="targetbox">&hellip;</span></span>+    <button class="ibtn" id="recall" aria-label="Show the getting-started panel" data-tip="Show the getting-started panel">+      <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="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>     </button>-    <button class="ibtn" id="gear" title="Settings" aria-label="Settings">-      <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="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h0a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51h0a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v0a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>+    <button class="ibtn" id="gear" aria-label="Settings" data-tip="Settings">+      <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="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>     </button>-  </header>--  <section class="say" id="say">-    <button class="say-x" id="sayx" aria-label="Collapse">&times;</button>+  </div>+</header>+<div class="wrap">+  <section class="newbie" id="newbie">+    <button class="nb-x" id="nbx" aria-label="Collapse this panel" data-tip="Collapse this panel">&times;</button>+    <div class="nb-collapsed" id="nbc">Say it: <span id="nbcn">6</span> phrases</div>     <h2>Say it</h2>     <p class="expl">You never need this page&rsquo;s buttons: just tell your AI what you want, using the word <b class="noun">pup</b>. Copy a phrase, or send it straight to a new AI thread.</p>-    <div class="say-chips" id="saychips"></div>+    <div class="nb-cols">+      <div class="nb-left">+        <button class="play" id="demo">+          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M7 4.8v14.4a.6.6 0 0 0 .92.5l11.3-7.2a.6.6 0 0 0 0-1l-11.3-7.2a.6.6 0 0 0-.92.5z"/></svg>+          <span id="demolbl">Play the demo</span>+        </button>+        <div class="play-sub" id="demosub">11 steps, about 2 minutes</div>+      </div>+      <div class="nb-right"><table class="prompts"><tbody id="prompts"></tbody></table></div>+    </div>   </section>-   <div class="leds" id="leds"></div>--  <div class="demo" id="demo-card">-    <div class="lbl">See what pup can do</div>-    <div class="det">A guided, narrated tour on your desktop (spoken and captioned): background window, taskbar tile and progress bar, screenshot, annotation, jump list. It plays on whichever desktop you are actively using.</div>-    <button class="play" id="demo">Play the demo</button>-    <div id="transport" class="transport" hidden>-      <div class="tstep"><span id="tstepn">1/11</span> <span id="tsteptitle">Intro</span> <span id="tstate"></span></div>-      <div class="tbtns">-        <button class="tbtn" id="tprev" title="previous step">&#9198;</button>-        <button class="tbtn" id="tplay" title="pause / resume">&#9208;</button>-        <button class="tbtn" id="tnext" title="next step">&#9197;</button>-        <span class="tsep"></span>-        <button class="tbtn" id="tmute" title="mute / unmute narration">&#128266;</button>-        <span class="tsep"></span>-        <button class="tbtn texit" id="texit" title="exit the demo">&#10005;</button>-      </div>-      <div class="tbar"><div id="tfill" class="tfill"></div></div>+  <div id="transport" class="transport" hidden>+    <div class="tstep"><span id="tstepn">1/11</span> <span id="tsteptitle">Intro</span> <span id="tstate"></span></div>+    <div class="tbtns">+      <button class="tbtn" id="tprev" data-tip="Previous step">&#9198;</button>+      <button class="tbtn" id="tplay" data-tip="Pause or resume">&#9208;</button>+      <button class="tbtn" id="tnext" data-tip="Next step">&#9197;</button>+      <span class="tsep"></span>+      <button class="tbtn" id="tmute" data-tip="Mute or unmute the narration">&#128266;</button>+      <span class="tsep"></span>+      <button class="tbtn texit" id="texit" data-tip="Exit the demo">&#10005;</button>     </div>+    <div class="tbar"><div id="tfill" class="tfill"></div></div>   </div> </div> <div id="toast"></div> <script> var SAY=__SAY__;+var ICON_COPY='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="12" height="12" rx="2.5"/><path d="M6 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1"/></svg>';+var ICON_SEND='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21.5 2.5 10.5 13.5"/><path d="M21.5 2.5 14.8 21.5l-4.3-8-8-4.3z"/></svg>'; function toast(kind,msg){var w=document.getElementById('toast');var t=document.createElement('div');t.className='toast '+(kind||'');t.textContent=msg;w.appendChild(t);setTimeout(function(){t.classList.add('out');setTimeout(function(){t.remove();},300);},4000);}+/* tooltips: 600ms, body-appended, viewport-clamped */+(function(){var tip=null,timer=null;+ function show(el){var t=el.getAttribute('data-tip');if(!t)return;tip=document.createElement('div');tip.id='tip';tip.textContent=t;document.body.appendChild(tip);+   var r=el.getBoundingClientRect(),tr=tip.getBoundingClientRect();+   var x=Math.min(Math.max(6,r.left+r.width/2-tr.width/2),innerWidth-tr.width-6);+   var y=r.bottom+6; if(y+tr.height>innerHeight-6) y=r.top-tr.height-6;+   tip.style.left=x+'px';tip.style.top=y+'px';}+ function hide(){if(timer){clearTimeout(timer);timer=null;}if(tip){tip.remove();tip=null;}}+ document.addEventListener('mouseover',function(e){var el=e.target.closest('[data-tip]');if(!el)return;hide();timer=setTimeout(function(){show(el);},600);});+ document.addEventListener('mouseout',function(e){if(e.target.closest('[data-tip]'))hide();});+ document.addEventListener('click',hide,true);})(); function legacyCopy(text){var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;opacity:0';document.body.appendChild(ta);ta.select();var ok=false;try{ok=document.execCommand('copy');}catch(e){}ta.remove();return ok;}-function copyPhrase(text){var done=function(){toast('success','Copied to clipboard. Paste it into any AI thread.');};if(document.hasFocus&&document.hasFocus()){navigator.clipboard.writeText(text).then(done,function(){if(legacyCopy(text))done();});}else if(legacyCopy(text))done();}-// ── say strip ──+function copyPhrase(text,quiet){var done=function(){if(!quiet)toast('success','Copied to clipboard. Paste it into any AI thread.');};if(document.hasFocus&&document.hasFocus()){navigator.clipboard.writeText(text).then(done,function(){if(legacyCopy(text))done();});}else if(legacyCopy(text))done();}+/* ── prompts TABLE ── */ (function(){-  var chips=document.getElementById('saychips');-  chips.innerHTML=SAY.map(function(p){var q=p.replace(/\\bpup\\b/,'<em>pup</em>');return '<span class="say-chip" data-say="'+p.replace(/"/g,'&quot;')+'">&ldquo;'+q+'&rdquo; <button class="copy">Copy</button><button class="send">Send to AI</button></span>';}).join('');-  chips.querySelectorAll('.say-chip').forEach(function(c){-    c.addEventListener('click',function(ev){ if(ev.target.closest('.send'))return; copyPhrase(c.dataset.say); });-    c.querySelector('.send').addEventListener('click',async function(ev){-      ev.stopPropagation(); var b=ev.target; b.disabled=true; b.textContent='…';-      var r=await fetch('api/say-send',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({phrase:c.dataset.say})}).then(function(x){return x.json();}).catch(function(){return null;});-      b.disabled=false; b.textContent='Send to AI';-      if(r&&r.ok) toast('success','Sent to a new AI thread.');-      else { copyPhrase(c.dataset.say); toast('error',(r&&r.why)||'Could not reach the AI panel; copied instead.'); }-    });+  var tb=document.getElementById('prompts');+  document.getElementById('nbcn').textContent=SAY.length;+  tb.innerHTML=SAY.map(function(p){+    var q=p.replace(/\\bpup\\b/,'<em>pup</em>');+    return '<tr data-say="'+p.replace(/"/g,'&quot;')+'" title="'+p.replace(/"/g,'&quot;')+'">'+      +'<td class="phrase">&ldquo;'+q+'&rdquo;</td>'+      +'<td class="acts">'+      +'<button class="ibtn act-copy" aria-label="Copy this prompt to your clipboard" data-tip="Copy this prompt to your clipboard">'+ICON_COPY+'</button>'+      +'<button class="ibtn act-send" aria-label="Send this prompt to a new AI thread" data-tip="Send this prompt to a new AI thread">'+ICON_SEND+'</button>'+      +'</td></tr>';+  }).join('');+  tb.addEventListener('click',async function(ev){+    var row=ev.target.closest('tr'); if(!row)return;+    var phrase=row.dataset.say;+    if(ev.target.closest('.act-send')){+      var b=ev.target.closest('.act-send'); b.disabled=true;+      var r=await fetch('api/say-send',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({phrase:phrase})}).then(function(x){return x.json();}).catch(function(){return null;});+      b.disabled=false;+      if(r&&r.ok){toast('success','Sent to a new AI thread.');}+      else{copyPhrase(phrase,true);toast('', (r&&r.parked)?'Copied instead. Send to AI is paused while we finish a fix.':((r&&r.why)||'Copied instead. The AI panel was unreachable.'));}+      return;+    }+    copyPhrase(phrase);   /* the row itself, and the copy icon, both copy */   });-  document.getElementById('sayx').onclick=function(){ document.getElementById('say').classList.add('hidden'); try{localStorage.setItem('pupSayHidden','1');}catch(e){} };-  try{ if(localStorage.getItem('pupSayHidden')==='1') document.getElementById('say').classList.add('hidden'); }catch(e){}-  document.getElementById('recall').onclick=function(){ document.getElementById('say').classList.remove('hidden'); try{localStorage.removeItem('pupSayHidden');}catch(e){} };-  document.getElementById('gear').onclick=function(){ fetch('api/open-settings',{method:'POST'}).then(function(){toast('','Opening pup settings on your desktop…');}).catch(function(){}); }; })();-// ── status: header meta + pill row ──+/* ── panel fold / recall (newbie-graduation, slug-namespaced key) ── */+(function(){+  var panel=document.getElementById('newbie'), KEY='pup-bridge:newbieFolded';+  function fold(v){panel.classList.toggle('folded',v);try{v?localStorage.setItem(KEY,'1'):localStorage.removeItem(KEY);}catch(e){}}+  try{ if(localStorage.getItem(KEY)==='1') panel.classList.add('folded'); }catch(e){}+  document.getElementById('nbx').onclick=function(){fold(true);};+  document.getElementById('nbc').onclick=function(){panel.classList.remove('folded');};+  document.getElementById('recall').onclick=function(){fold(false);};+  document.getElementById('gear').onclick=function(){fetch('api/open-settings',{method:'POST'}).then(function(){toast('','Opening pup settings on your desktop\\u2026');}).catch(function(){});};+})();+/* ── status ── */ function ledState(s){return s==='ok'?'ok':(s==='fail'||s==='bad'?'bad':(s==='warn'||s==='busy'?'busy':'unknown'));} async function load(){   try{     var d=await (await fetch('api/status',{cache:'no-store'})).json();-    // header popover (versions live here, never in the bar)     document.getElementById('pop').innerHTML='<b>pup</b> '+(d.version||'?')+' &middot; dashboard '+(d.dashboardVersion||'?')+'<br><a href="https://wiki.adom.inc/adom/pup-bridge" target="_blank">wiki.adom.inc/adom/pup-bridge</a>';     var tgt=document.getElementById('target');-    if(d.desktop){ tgt.hidden=false; document.getElementById('targetbox').textContent='target: '+d.desktop; tgt.setAttribute('data-state',ledState(d.state)); }+    tgt.hidden=false;+    document.getElementById('targetbox').innerHTML='target: <b>'+(d.desktop||'this machine')+'</b>';+    tgt.setAttribute('data-state',ledState(d.state));     var leds=d.leds||[];     document.getElementById('leds').innerHTML=leds.map(function(l,i){       var st=ledState(l.state);-      var short=(l.detail||'').replace(/\\b\\d[\\d.]*\\b/g,'').replace(/ on [^.,;(]+/i,'').replace(/[.—(].*$/,'').replace(/\\s+/g,' ').trim().slice(0,26);+      var short=(l.detail||'').replace(/\\b\\d[\\d.]*\\b/g,'').replace(/ on [^.,;(]+/i,'').replace(/[.\\u2014(].*$/,'').replace(/\\s+/g,' ').trim().slice(0,26);       return '<span class="led-pill" data-state="'+st+'" data-i="'+i+'"><span class="dot"></span><b>'+l.label+'</b> <span class="st">'+short+'</span></span>';     }).join('');     document.querySelectorAll('.led-pill').forEach(function(pill){@@ -410,40 +468,41 @@ async function load(){     });   }catch(e){} }-// ── demo transport (dash-demo) ──+/* ── demo transport ── */ (function(){-  var demoBtn=document.getElementById('demo'), tp=document.getElementById('transport');+  var demoBtn=document.getElementById('demo'), lbl=document.getElementById('demolbl'), sub=document.getElementById('demosub'), tp=document.getElementById('transport');   var ctl=function(action){return fetch('api/demo/control',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({action:action})}).catch(function(){});};   document.getElementById('tprev').onclick=function(){ctl('prev');};   document.getElementById('tnext').onclick=function(){ctl('next');};   document.getElementById('texit').onclick=function(){ctl('stop');};   var paused=false;-  document.getElementById('tplay').onclick=function(){ paused=!paused; ctl(paused?'pause':'resume'); };-  var muted=false; try{muted=localStorage.getItem('pupDemoMuted')==='1';}catch(e){}-  var mb=document.getElementById('tmute'); mb.innerHTML=muted?'🔇':'🔊';-  mb.onclick=function(){ muted=!muted; try{localStorage.setItem('pupDemoMuted',muted?'1':'0');}catch(e){} mb.innerHTML=muted?'🔇':'🔊'; ctl(muted?'mute':'unmute'); };+  document.getElementById('tplay').onclick=function(){paused=!paused;ctl(paused?'pause':'resume');};+  var muted=false; try{muted=localStorage.getItem('pup-bridge:demoMuted')==='1';}catch(e){}+  var mb=document.getElementById('tmute'); mb.innerHTML=muted?'\\uD83D\\uDD07':'\\uD83D\\uDD0A';+  mb.onclick=function(){muted=!muted;try{localStorage.setItem('pup-bridge:demoMuted',muted?'1':'0');}catch(e){}mb.innerHTML=muted?'\\uD83D\\uDD07':'\\uD83D\\uDD0A';ctl(muted?'mute':'unmute');};   var poll=null;-  function stopPoll(){ if(poll){clearInterval(poll);poll=null;} tp.hidden=true; demoBtn.disabled=false; demoBtn.textContent='Play the demo'; }+  function stopPoll(){if(poll){clearInterval(poll);poll=null;}tp.hidden=true;demoBtn.disabled=false;lbl.textContent='Play the demo';sub.textContent='11 steps, about 2 minutes';}   async function tick(){     try{       var d=await (await fetch('api/demo/state',{cache:'no-store'})).json();-      if(!d.running){ stopPoll(); return; }-      tp.hidden=false; demoBtn.disabled=true; demoBtn.textContent='Playing on '+(d.target||'your desktop')+'…';+      if(!d.running){stopPoll();return;}+      tp.hidden=false; demoBtn.disabled=true; lbl.textContent='Demo running';+      sub.textContent='playing on '+(d.target||'your desktop');       document.getElementById('tstepn').textContent=(d.idx+1)+'/'+d.total;       document.getElementById('tsteptitle').textContent=d.title||'';       paused=!!d.paused;-      document.getElementById('tplay').innerHTML=paused?'▶':'⏸';+      document.getElementById('tplay').innerHTML=paused?'\\u25B6':'\\u23F8';       document.getElementById('tstate').textContent=paused?'(paused)':'';       document.getElementById('tfill').style.width=Math.round(100*(d.idx+1)/(d.total||1))+'%';     }catch(e){}   }   demoBtn.onclick=async function(){-    demoBtn.disabled=true; demoBtn.textContent='Starting…';-    var mm=false; try{mm=localStorage.getItem('pupDemoMuted')==='1';}catch(e){}+    demoBtn.disabled=true; lbl.textContent='Starting\\u2026';+    var mm=false; try{mm=localStorage.getItem('pup-bridge:demoMuted')==='1';}catch(e){}     try{ await fetch('api/demo/start',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({muted:mm})}); }catch(e){}     if(poll) clearInterval(poll); poll=setInterval(tick,700); tick();   };-  setInterval(function(){ if(poll) return; fetch('api/demo/state',{cache:'no-store'}).then(function(r){return r.json();}).then(function(d){ if(d.running){ poll=setInterval(tick,700); tick(); } }).catch(function(){}); }, 3000);+  setInterval(function(){ if(poll) return; fetch('api/demo/state',{cache:'no-store'}).then(function(r){return r.json();}).then(function(d){ if(d.running){poll=setInterval(tick,700);tick();} }).catch(function(){}); },3000); })(); load(); setInterval(load,15000); </script>`;