← Commit history

Brand compliance plus the zero-click toolbar install. The annotate overlay, capture toast, popup, and caption bar now follow the Adom UI design system exactly: token colors (the real 00b8b1 accent family, 161b22 surfaces, 30363d borders, c9d1d9 body text), monochrome SVG line icons replacing every glyph character per the icon law, brand fonts bundled in the extension popup (Familjen Grotesk headers, Satoshi body, JetBrains Mono data), toast moved to bottom-center per the toast standard, drawing palette mapped to brand status colors. New pup_install_toolbar verb answers John's can-not-you-just-do-that-automatically: pup stages the extension, drives chrome://extensions itself through the shadow DOM (dev mode toggle, Load unpacked), and ab drives the one native folder dialog, so the one-time install is fully automated. /health now carries owner and view per session plus the toolbar prefs for the extension's badge feed, and annotation start clears any stale result flag

John Lauer ·7f96f68a1d ·2mo ago ·parent 784d73f
9 files changed +191−68
adom-bridge-puppeteer-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "2.0.50",-  "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.50/adom-bridge-puppeteer-v2.0.50.zip",-  "sha256": "4404efaf1bb53ed3fd81d139f3b2201cc43dc6efc97657e37aa95adcb0ef46a6",-  "size": 659091,+  "version": "2.0.51",+  "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.51/adom-bridge-puppeteer-v2.0.51.zip",+  "sha256": "ad76e57a1c9295defd3db771f80bb7ed60abdf5b1a1203f1e78d4c42121d5485",+  "size": 742113,   "verbPrefixes": [     "browser_",     "desktop_recorder_",
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-2.0.50+2.0.51
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "puppeteer",   "displayName": "Puppeteer (Chrome for Testing + Native)",-  "version": "2.0.50",+  "version": "2.0.51",   "description": "Drives a browser ALREADY on the machine \u2014 installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) \u2014 launched with a FRESH isolated profile, so the common case has NO ~150 MB Chrome-for-Testing download and it never touches the user's real tabs/logins. Binds its local server to LOOPBACK (127.0.0.1) only, so no Windows Firewall 'allow Node through the firewall' prompt ever pops. Chrome for Testing is fetched only as a LAST RESORT if the machine has no Chromium browser at all. SPAWN-VERIFIED fallback: pup tries each detected browser (Chrome \u2192 Edge \u2192 CfT) and actually launches it, falling through to the next if one won't start (and handling browsers like Edge that relaunch onto another PID), so a corrupt/partial install never hard-fails an open. The browser that launches is cached as the default (persisted across restarts) so it isn't re-probed each time. On spawn the bridge SELF-WARMS: detects browsers + disk, and only if the box has no Chrome/Edge and no cached CfT does it background-fetch CfT so the first open is instant. pup_use pins a specific browser (chrome | edge | cft | auto); {browser:'chrome',install:true} downloads & installs REAL Google Chrome (user-scoped, no admin) on an Edge-only box. pup_readiness (READ-ONLY) reports which browser will be used + the full candidate list + the default + free disk. Every failure returns a specific errorCode + _hint (chrome_install_no_disk, chrome_download_network, chrome_install_permission, chrome_stable_not_detected, \u2026). Launches with FULL browser permissions by default for full debug (strictPermissions:true opts out). Headful automation: open/close windows+tabs, navigate, screenshot, eval JS, record. pup is a GENERIC, non-profile browser only \u2014 every session is a fresh isolated Chrome/Edge process, never the user's real signed-in browser; profile defaults to sessionId. To drive the user's REAL Chrome/Edge (their profile, cookies, SSO), use the Adom browser EXTENSION's nbrowser_* verbs \u2014 a separate bridge, not pup's job. macOS (v1.9.74+): full parity with mac-native mechanics \u2014 background-by-default is the app-level hide (windows stay drivable/screenshottable while hidden), raise/lower map to unhide+activate/hide, pup_alert_window posts a native toast whose click focuses the window, pup_use {browser:'chrome',install:true} installs Chrome from the official dmg, and taskbar identity/badging remains Windows-only.",   "homepage": "https://wiki.adom.inc/adom/adom-bridge",   "author": "Adom Inc.",
src/extension/fg-500.woff2added
⋯ 1 unchanged line ⋯
src/extension/fg-700.woff2added
⋯ 1 unchanged line ⋯
src/extension/popup.html+55−32
@@ -3,58 +3,81 @@ <head> <meta charset="utf-8"> <style>-  :root { color-scheme: dark; }+  @font-face { font-family: 'Familjen Grotesk'; font-weight: 500; src: url(fg-500.woff2) format('woff2'); }+  @font-face { font-family: 'Familjen Grotesk'; font-weight: 700; src: url(fg-700.woff2) format('woff2'); }+  @font-face { font-family: 'Satoshi'; font-weight: 400; src: url(satoshi-400.woff2) format('woff2'); }+  @font-face { font-family: 'Satoshi'; font-weight: 500; src: url(satoshi-500.woff2) format('woff2'); }+  :root {+    color-scheme: dark;+    --bg: #0d1117; --surface: #161b22; --elevated: #1c2128; --overlay: #21262d;+    --border: #30363d; --text: #e6edf3; --text-body: #c9d1d9; --text-2: #8b949e;+    --accent: #00b8b1; --accent-hover: #00d4cb; --red: #f85149; --yellow: #d29922;+    --green: #3fb950;+  }   body {-    margin: 0; width: 248px; background: #0d1117; color: #e5e7eb;-    font: 13px "Segoe UI", system-ui, sans-serif; border: 1px solid #2dd4bf44;+    margin: 0; width: 252px; background: var(--bg); color: var(--text-body);+    font: 400 13px 'Satoshi', sans-serif; border: 1px solid var(--border);   }   .hdr {-    display: flex; align-items: center; gap: 8px; padding: 10px 12px 8px;-    border-bottom: 1px solid #ffffff12;+    display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 12px;+    border-bottom: 1px solid var(--border); background: var(--surface);   }-  .hdr .logo { width: 18px; height: 18px; border-radius: 5px; background: #0f766e;-    display: grid; place-items: center; }-  .hdr .logo span { color: #5eead4; font-size: 11px; letter-spacing: 1px; }-  .hdr .t { font-weight: 600; color: #7fe9e2; }-  .hdr .owner { margin-left: auto; font-size: 11px; color: #9ca3af; max-width: 110px;+  .hdr svg.logo { width: 16px; height: 16px; color: var(--text); }+  .hdr .t { font: 600 13px 'Familjen Grotesk', sans-serif; color: var(--accent); }+  .hdr .owner { margin-left: auto; font-size: 11px; color: var(--text-2); max-width: 116px;     overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }-  .owner b { color: #fbbf24; font-weight: 600; }+  .owner b { color: var(--yellow); font-weight: 500; }   .menu { padding: 6px; }   button.row {     display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;-    background: none; border: none; border-radius: 8px; color: #e5e7eb;-    padding: 8px 10px; font: inherit; cursor: pointer;+    background: none; border: none; border-radius: 8px; color: var(--text-body);+    padding: 9px 10px; font: inherit; cursor: pointer; transition: background 0.15s ease;   }-  button.row:hover { background: #ffffff10; }-  button.row .ic { width: 18px; text-align: center; color: #5eead4; }-  button.row .kbd { margin-left: auto; font-size: 10px; color: #6b7280;-    border: 1px solid #ffffff1c; border-radius: 4px; padding: 1px 5px; }-  button.row.danger .ic { color: #f87171; }-  button.row.danger:hover { background: #f8717118; }-  .sep { height: 1px; background: #ffffff12; margin: 4px 8px; }-  .foot { padding: 6px 12px 9px; font-size: 10.5px; color: #6b7280; }-  .foot .dot { color: #34d399; }-  .off { padding: 14px 12px; color: #9ca3af; font-size: 12px; display: none; }+  button.row:hover { background: var(--elevated); }+  button.row svg { width: 16px; height: 16px; flex: none; color: var(--text); }+  button.row .kbd { margin-left: auto; font: 400 10px 'JetBrains Mono', monospace;+    color: var(--text-2); border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; }+  button.row.danger { color: var(--red); }+  button.row.danger svg { color: var(--red); }+  .sep { height: 1px; background: var(--border); margin: 4px 8px; }+  .foot { padding: 7px 12px 9px; font: 400 10.5px 'JetBrains Mono', monospace; color: var(--text-2); }+  .foot .dot { color: var(--green); }+  .off { padding: 14px 12px; color: var(--text-2); font-size: 12px; display: none; } </style> </head> <body>   <div class="hdr">-    <div class="logo"><span>⠿</span></div>+    <svg class="logo" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">+      <circle cx="7" cy="6" r="2.4"/><circle cx="15" cy="9.5" r="2.4"/>+      <circle cx="7" cy="13" r="2.4"/><circle cx="15" cy="16.5" r="2.4"/>+    </svg>     <div class="t">Adom Pup</div>-    <div class="owner" id="owner">…</div>+    <div class="owner" id="owner">&hellip;</div>   </div>   <div class="menu" id="menu">-    <button class="row" data-act="annotate"><span class="ic">✎</span>Annotate this page</button>-    <button class="row" data-act="snip"><span class="ic">▣</span>Snip a region<span class="kbd">Ctrl·Shift·A</span></button>+    <button class="row" data-act="annotate">+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20l1.5-4.5L16 5a2.1 2.1 0 0 1 3 3L8.5 18.5 4 20z"/><path d="M13.5 7.5l3 3"/></svg>+      Annotate this page</button>+    <button class="row" data-act="snip">+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M4 8V5a1 1 0 0 1 1-1h3M16 4h3a1 1 0 0 1 1 1v3M20 16v3a1 1 0 0 1-1 1h-3M8 20H5a1 1 0 0 1-1-1v-3"/><rect x="8.5" y="8.5" width="7" height="7" rx="1" stroke-dasharray="2.5 2"/></svg>+      Snip a region<span class="kbd">Ctrl Shift A</span></button>     <div class="sep"></div>-    <button class="row" data-act="view"><span class="ic">◐</span><span id="viewLabel">Switch to public view</span></button>-    <button class="row" data-act="dashboard"><span class="ic">▦</span>Open pup dashboard</button>-    <button class="row" data-act="info"><span class="ic">ⓘ</span>Window info card</button>+    <button class="row" data-act="view">+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8"/><path d="M12 4a8 8 0 0 1 0 16z" fill="currentColor" stroke="none"/></svg>+      <span id="viewLabel">Switch to public view</span></button>+    <button class="row" data-act="dashboard">+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/></svg>+      Open pup dashboard</button>+    <button class="row" data-act="info">+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="8"/><path d="M12 11v5M12 8v.5"/></svg>+      Window info card</button>     <div class="sep"></div>-    <button class="row danger" data-act="close"><span class="ic">✕</span>Close this window</button>+    <button class="row danger" data-act="close">+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg>+      Close this window</button>   </div>   <div class="off" id="off">The Adom toolbar is turned off in pup's Settings, or the pup bridge isn't running.</div>-  <div class="foot" id="foot"><span class="dot">●</span> pup connected</div>+  <div class="foot" id="foot"><span class="dot">&#9679;</span> pup connected</div>   <script src="popup.js"></script> </body> </html>
src/extension/satoshi-400.woff2added
⋯ 1 unchanged line ⋯
src/extension/satoshi-500.woff2added
⋯ 1 unchanged line ⋯
src/server.js+130−30
@@ -5273,8 +5273,8 @@ function appIconReprimand(url, hadFavicon) { const ANNOTATE_OVERLAY_JS = `(() => {   if (window.__pupAnnotate) { window.__pupAnnotate.show(); return 'shown'; }   var initTool = window.__pupAnnotateTool || 'pen'; window.__pupAnnotateTool = null;-  var S = { tool: 'pen', color: '#ff4d5e', strokes: [], cur: null, quick: initTool === 'snip', seq: 0 };-  var COLORS = ['#ff4d5e', '#ffd23f', '#39d98a', '#38bdf8', '#c084fc', '#ffffff'];+  var S = { tool: 'pen', color: '#f85149', strokes: [], cur: null, quick: initTool === 'snip', seq: 0 };+  var COLORS = ['#f85149', '#d29922', '#3fb950', '#64abff', '#8c6bf7', '#e6edf3'];   var wrap = document.createElement('div');   wrap.id = '__pup_annotate';   wrap.style.cssText = 'position:fixed;inset:0;z-index:2147483646;pointer-events:none;';@@ -5282,7 +5282,7 @@ const ANNOTATE_OVERLAY_JS = `(() => {   cv.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;pointer-events:auto;touch-action:none;';   wrap.appendChild(cv);   var bar = document.createElement('div');-  bar.style.cssText = 'position:fixed;top:14px;left:50%;transform:translateX(-50%);display:flex;gap:6px;align-items:center;background:#111827ee;border:1px solid #2dd4bf55;border-radius:999px;padding:7px 12px;pointer-events:auto;font:13px system-ui,sans-serif;color:#e5e7eb;box-shadow:0 6px 24px #000a;';+  bar.style.cssText = 'position:fixed;top:14px;left:50%;transform:translateX(-50%);display:flex;gap:6px;align-items:center;background:#161b22f2;border:1px solid #30363d;border-radius:999px;padding:7px 12px;pointer-events:auto;font:13px Satoshi,system-ui,sans-serif;color:#c9d1d9;box-shadow:0 6px 24px #000a;';   wrap.appendChild(bar);   var ctx = cv.getContext('2d');   // ── Photoshop-style tool cursors: SVG data-URI, hotspot at the working tip, pen carries a@@ -5315,7 +5315,7 @@ const ANNOTATE_OVERLAY_JS = `(() => {       ctx.lineTo(b2[0]-14*Math.cos(ang+0.42), b2[1]-14*Math.sin(ang+0.42));       ctx.closePath(); ctx.fill(); }     else if (st.tool === 'text') { ctx.font = '600 17px system-ui,sans-serif'; ctx.fillText(st.text || '', st.pts[0][0], st.pts[0][1]); }-    else if (st.tool === 'snip') { var s1 = st.pts[0], s2 = st.pts[st.pts.length - 1]; ctx.save(); ctx.setLineDash([7, 5]); ctx.lineWidth = 2; ctx.strokeStyle = '#5eead4'; ctx.strokeRect(Math.min(s1[0],s2[0]), Math.min(s1[1],s2[1]), Math.abs(s2[0]-s1[0]), Math.abs(s2[1]-s1[1])); ctx.restore(); }+    else if (st.tool === 'snip') { var s1 = st.pts[0], s2 = st.pts[st.pts.length - 1]; ctx.save(); ctx.setLineDash([7, 5]); ctx.lineWidth = 2; ctx.strokeStyle = '#00d4cb'; ctx.strokeRect(Math.min(s1[0],s2[0]), Math.min(s1[1],s2[1]), Math.abs(s2[0]-s1[0]), Math.abs(s2[1]-s1[1])); ctx.restore(); }   }   function redraw() { ctx.clearRect(0, 0, innerWidth, innerHeight); S.strokes.forEach(drawStroke); if (S.cur) drawStroke(S.cur); }   // Tight-crop bbox over ALL strokes, padded and clamped to the viewport (1a2).@@ -5360,34 +5360,55 @@ const ANNOTATE_OVERLAY_JS = `(() => {     // supersedes the last capture with the union bbox.     if (S.quick) emit(null, false);   });-  function btn(label, title, fn, id) {+  function icon(paths, dash) {+    var n = document.createElementNS('http://www.w3.org/2000/svg', 'svg');+    n.setAttribute('viewBox', '0 0 24 24'); n.setAttribute('width', '14'); n.setAttribute('height', '14');+    n.setAttribute('fill', 'none'); n.setAttribute('stroke', 'currentColor');+    n.setAttribute('stroke-width', '1.9'); n.setAttribute('stroke-linecap', 'round'); n.setAttribute('stroke-linejoin', 'round');+    paths.forEach(function (dd, i) { var p2 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); p2.setAttribute('d', dd); if (dash && i === dash - 1) p2.setAttribute('stroke-dasharray', '2.5 2'); n.appendChild(p2); });+    return n;+  }+  var ICONS = {+    pen: [['M4 20l1.5-4.5L16 5a2.1 2.1 0 0 1 3 3L8.5 18.5 4 20z', 'M13.5 7.5l3 3']],+    arrow: [['M5 19L17 7', 'M9 7h8v8']],+    box: [['M4.5 5.5h15v13h-15z']],+    text: [['M6 6h12', 'M12 6v12']],+    snip: [['M4 8V5a1 1 0 0 1 1-1h3M16 4h3a1 1 0 0 1 1 1v3M20 16v3a1 1 0 0 1-1 1h-3M8 20H5a1 1 0 0 1-1-1v-3', 'M8.5 8.5h7v7h-7z'], 2],+    auto: [['M13 3L5 13h5l-1 8 8-10h-5l1-8']],+    undo: [['M8 5L4 9l4 4', 'M4 9h9a6 6 0 0 1 0 12h-2']],+    clear: [['M5 7h14', 'M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2', 'M7 7l1 13h8l1-13']],+    done: [['M4.5 12.5l5 5 10-11']],+    cancel: [['M6 6l12 12', 'M18 6L6 18']],+  };+  function btn(label, title, fn, id, iconKey) {     var b = document.createElement('button');-    b.textContent = label; b.title = title;-    b.style.cssText = 'background:none;border:1px solid transparent;border-radius:8px;color:#e5e7eb;padding:3px 8px;cursor:pointer;font:inherit;';-    b.onmouseenter = function () { b.style.background = '#ffffff14'; };-    b.onmouseleave = function () { b.style.background = S.tool === b.dataset.tool ? '#2dd4bf22' : 'none'; };+    if (iconKey && ICONS[iconKey]) b.appendChild(icon(ICONS[iconKey][0], ICONS[iconKey][1]));+    b.appendChild(document.createTextNode(label)); b.title = title;+    b.style.cssText = 'background:none;border:1px solid transparent;border-radius:8px;color:#c9d1d9;padding:3px 8px;cursor:pointer;font:inherit;display:flex;align-items:center;gap:5px;';+    b.onmouseenter = function () { b.style.background = '#1c2128'; };+    b.onmouseleave = function () { b.style.background = S.tool === b.dataset.tool ? 'rgba(0,184,177,.13)' : 'none'; };     b.onclick = fn; if (id) b.dataset.tool = id; bar.appendChild(b); return b;   }-  function selTool(id) { S.tool = id; setCursor(); [].forEach.call(bar.querySelectorAll('button[data-tool]'), function (x) { x.style.background = x.dataset.tool === id ? '#2dd4bf22' : 'none'; x.style.borderColor = x.dataset.tool === id ? '#2dd4bf66' : 'transparent'; }); }-  btn('✎ Pen', 'freehand', function () { selTool('pen'); }, 'pen');-  btn('↗ Arrow', 'arrow', function () { selTool('arrow'); }, 'arrow');-  btn('▭ Box', 'rectangle', function () { selTool('box'); }, 'box');-  btn('T Text', 'text note', function () { selTool('text'); }, 'text');-  btn('▣ Snip', 'marquee a region — captures it immediately (like Win+Shift+S)', function () { selTool('snip'); }, 'snip');+  function selTool(id) { S.tool = id; setCursor(); [].forEach.call(bar.querySelectorAll('button[data-tool]'), function (x) { x.style.background = x.dataset.tool === id ? 'rgba(0,184,177,.13)' : 'none'; x.style.borderColor = x.dataset.tool === id ? 'rgba(0,184,177,.45)' : 'transparent'; }); }+  btn('Pen', 'freehand', function () { selTool('pen'); }, 'pen', 'pen');+  btn('Arrow', 'arrow', function () { selTool('arrow'); }, 'arrow', 'arrow');+  btn('Box', 'rectangle', function () { selTool('box'); }, 'box', 'box');+  btn('Text', 'text note', function () { selTool('text'); }, 'text', 'text');+  btn('Snip', 'marquee a region: captures it the moment you release', function () { selTool('snip'); }, 'snip', 'snip');   var dot = document.createElement('button');-  dot.title = 'color'; dot.style.cssText = 'width:20px;height:20px;border-radius:50%;border:2px solid #fff8;cursor:pointer;background:' + S.color + ';';+  dot.title = 'drawing color'; dot.style.cssText = 'width:20px;height:20px;border-radius:50%;border:2px solid #8b949e;cursor:pointer;background:' + S.color + ';';   dot.onclick = function () { var i = (COLORS.indexOf(S.color) + 1) % COLORS.length; S.color = COLORS[i]; dot.style.background = S.color; setCursor(); };   bar.appendChild(dot);   var qk = document.createElement('button');-  function qkPaint() { qk.style.cssText = 'background:' + (S.quick ? '#2dd4bf22' : 'none') + ';border:1px solid ' + (S.quick ? '#2dd4bf88' : 'transparent') + ';border-radius:8px;color:' + (S.quick ? '#5eead4' : '#e5e7eb') + ';padding:3px 8px;cursor:pointer;font:inherit;'; }-  qk.textContent = '⚡ Auto'; qk.title = 'Quick capture: copy to clipboard the moment you lift the mouse';+  function qkPaint() { qk.style.cssText = 'background:' + (S.quick ? 'rgba(0,184,177,.13)' : 'none') + ';border:1px solid ' + (S.quick ? 'rgba(0,184,177,.55)' : 'transparent') + ';border-radius:8px;color:' + (S.quick ? '#00d4cb' : '#c9d1d9') + ';padding:3px 8px;cursor:pointer;font:inherit;display:flex;align-items:center;gap:5px;'; }+  qk.appendChild(icon(ICONS.auto[0])); qk.appendChild(document.createTextNode('Auto')); qk.title = 'Quick capture: copy to clipboard the moment you lift the mouse';   qk.onclick = function () { S.quick = !S.quick; qkPaint(); };   qkPaint(); bar.appendChild(qk);-  btn('↶ Undo', 'undo last', function () { S.strokes.pop(); redraw(); });-  btn('✕ Clear', 'clear all', function () { S.strokes = []; redraw(); });-  var done = btn('✓ Done', 'capture for the owning AI thread', function () { emit(null, true); });-  done.style.cssText += 'background:#2dd4bf22;border:1px solid #2dd4bf88;color:#5eead4;font-weight:600;';-  btn('✖', 'cancel (remove drawing)', function () { wrap.remove(); window.__pupAnnotate = null; window.__pupAnnotateResult = { done: false, ts: Date.now() }; });+  btn('Undo', 'undo last', function () { S.strokes.pop(); redraw(); }, null, 'undo');+  btn('Clear', 'clear all', function () { S.strokes = []; redraw(); }, null, 'clear');+  var done = btn('Done', 'capture for the owning AI thread', function () { emit(null, true); }, null, 'done');+  done.style.cssText += 'background:rgba(0,184,177,.13);border:1px solid rgba(0,184,177,.55);color:#00d4cb;font-weight:600;';+  btn('', 'cancel (remove drawing)', function () { wrap.remove(); window.__pupAnnotate = null; window.__pupAnnotateResult = { done: false, ts: Date.now() }; }, null, 'cancel');   window.__pupAnnotate = { show: function () { wrap.style.display = ''; bar.style.display = 'flex'; cv.style.pointerEvents = 'auto'; } };   addEventListener('resize', fit);   document.documentElement.appendChild(wrap);@@ -5435,7 +5456,7 @@ async function addCaptionBar(buf, caption) {     const esc = String(caption).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');     const svg = Buffer.from('<svg xmlns="http://www.w3.org/2000/svg" width="' + W + '" height="' + BAR + '">'       + '<rect width="100%" height="100%" fill="#0d1117"/>'-      + '<text x="8" y="17" font-family="Segoe UI, system-ui, sans-serif" font-size="12" fill="#7fe9e2">' + esc + '</text></svg>');+      + '<text x="8" y="17" font-family="Segoe UI, system-ui, sans-serif" font-size="12" fill="#00d4cb">' + esc + '</text></svg>');     return await sharp(buf)       .extend({ bottom: BAR, background: '#0d1117' })       .composite([{ input: svg, top: meta.height, left: 0 }])@@ -5486,10 +5507,10 @@ async function showCaptureToast(pg, previewDataUrl, line1) {     await pg.evaluate(function (dataUrl, txt) {       var old = document.getElementById('__pup_ann_toast'); if (old) old.remove();       var t = document.createElement('div'); t.id = '__pup_ann_toast';-      t.style.cssText = 'position:fixed;right:18px;bottom:18px;z-index:2147483647;background:#111827f2;border:1px solid #2dd4bf66;border-radius:12px;padding:10px;box-shadow:0 8px 30px #000c;font:12px system-ui,sans-serif;color:#e5e7eb;max-width:340px;cursor:pointer;';-      var cap = document.createElement('div'); cap.textContent = txt; cap.style.cssText = 'margin-bottom:7px;color:#5eead4;font-weight:600;';+      t.style.cssText = 'position:fixed;left:50%;transform:translateX(-50%);bottom:18px;z-index:2147483647;background:#21262df2;border:1px solid #30363d;border-radius:12px;padding:10px;box-shadow:0 8px 30px #000c;font:12px Satoshi,system-ui,sans-serif;color:#c9d1d9;max-width:340px;cursor:pointer;';+      var cap = document.createElement('div'); cap.textContent = txt; cap.style.cssText = 'margin-bottom:7px;color:#00d4cb;font-weight:500;';       t.appendChild(cap);-      if (dataUrl) { var im = document.createElement('img'); im.src = dataUrl; im.style.cssText = 'display:block;max-width:320px;max-height:200px;border-radius:8px;border:1px solid #ffffff22;'; t.appendChild(im); }+      if (dataUrl) { var im = document.createElement('img'); im.src = dataUrl; im.style.cssText = 'display:block;max-width:320px;max-height:200px;border-radius:8px;border:1px solid #30363d;'; t.appendChild(im); }       t.onclick = function () { t.remove(); };       document.documentElement.appendChild(t);       setTimeout(function () { try { t.remove(); } catch (e) {} }, 4500);@@ -5502,7 +5523,7 @@ async function startAnnotation(session, sessionId, callerThread, opts) {   const pg = tab && tab.page;   if (!pg) throw new Error('no page to annotate');   const tool = (opts && opts.tool) || 'pen';-  await pg.evaluate((t2) => { window.__pupAnnotateTool = t2; }, tool);+  await pg.evaluate((t2) => { window.__pupAnnotateTool = t2; window.__pupAnnotateResult = null; }, tool);   await pg.evaluate((code) => eval(code), ANNOTATE_OVERLAY_JS);   console.log(`[annotate] "${sessionId}" overlay started (by ${callerThread || 'unknown'}, tool=${tool})`);   // Capture loop: quick-mode mouse-ups keep it alive (each lift supersedes the last capture);@@ -5541,7 +5562,7 @@ async function startAnnotation(session, sessionId, callerThread, opts) {         // preview thumbnail for the toast (resized so the data URL stays small)         let preview = null;         try { const sharp = require('sharp'); const small = await sharp(buf).resize({ width: 320, withoutEnlargement: true }).png().toBuffer(); preview = 'data:image/png;base64,' + small.toString('base64'); } catch (e) {}-        await showCaptureToast(pg, preview, `${isSnip ? '▣ Snippet' : '✎ Annotation'} ${clipped ? 'copied to your clipboard' : 'captured'}`);+        await showCaptureToast(pg, preview, `${isSnip ? 'Snippet' : 'Annotation'} ${clipped ? 'copied to your clipboard' : 'captured'}`);         console.log(`[annotate] "${sessionId}" CAPTURED ${isSnip ? `SNIP ${res.snip.w}x${res.snip.h}` : (clipRect ? `tight-crop ${clipRect.w}x${clipRect.h}` : 'full-view')} -> ${fp} | clipboard=${clipped} | quick=${!!res.quick}`);       } catch (e) { console.log(`[annotate] "${sessionId}" capture failed: ${e.message}`); }       if (!res.quick) return;   // Done/snip end the loop; quick mode re-arms@@ -10575,13 +10596,16 @@ const server = http.createServer(async (req, res) => {       for (const [id, s] of sessions) {         let pageUrl = null;         try { pageUrl = s.page.url(); } catch (e) {}-        sessionList.push({ sessionId: id, url: pageUrl, active: id === activeSessionId });+        // v2.0.51: owner + view ride /health so the toolbar extension can badge tabs per owning+        // thread and label its view toggle without a heavier verb round-trip.+        sessionList.push({ sessionId: id, url: pageUrl, active: id === activeSessionId, owner: s.owner || s._ownerThread || null, view: s._wikiView || null });       }       sendJSON(res, {         status: 'ok',         bridge: 'puppeteer',         version: BRIDGE_VERSION,         port: PORT,+        toolbar: { enabled: pupSettings().toolbarExtension !== 'off', ownerBadge: pupSettings().tbOwnerBadge !== 'off', snipHotkey: pupSettings().tbSnipHotkey !== 'off' },         browserCount: browsers.size,         sessionCount: sessions.size,         activeSession: activeSessionId,@@ -10816,6 +10840,7 @@ const server = http.createServer(async (req, res) => {             V('pup_press_key', 'Press a key, optionally with modifier chords held (Ctrl+A, Ctrl+V, Shift+Tab).', { sessionId: 'required str', key: 'required str', modifiers: 'optional array — any of ["Control","Shift","Alt","Meta"] (aliases ctrl/cmd/command/option accepted); held down around the key press' }, 15, { sessionId: 'work', key: 'a', modifiers: ['Control'] }),             V('pup_NATIVE_INPUT_WARNING', '⛔ READ BEFORE USING AD\'s desktop_click / desktop_type / desktop_press_key ON A PUP WINDOW. Native OS input does NOT target a window — it goes to WHATEVER WINDOW HAS OS FOCUS at that instant. This user runs many AI threads and alt-tabs away from a foregrounded window almost immediately. If you fire native input without warning them, your keystrokes land in THEIR document (this happened: a username was typed into the user\'s own window). REQUIRED: notify_user toast + desktop_caption FIRST, tell them explicitly "don\'t type or click until I say done", wait until the window is genuinely foreground, re-verify immediately before EACH event, and caption when done. pup_assisted_click already does all of this — use it. And ALWAYS prefer CDP input (pup_click/pup_type/pup_hover): it needs no foreground and disturbs nobody. A native verb returning ok:true means the event was dispatched to the OS, NOT that it reached your window.', {}, 0, {}),             V('pup_set_viewport', 'Pin the page viewport (CDP device-metrics) to an exact size for deterministic screenshots — OR clear that pin. ⚠ A pin PERSISTS: it survives navigation and hard reload, and while set the window stops tracking OS resizing (a human resizing it sees no reflow, which looks exactly like an app layout bug). ALWAYS clear it when done. Reported as emulatedViewport in pup_status / pup_list_windows.', { sessionId: 'required str', width: 'int — required unless reset', height: 'int — required unless reset', deviceScaleFactor: 'optional number (default 1)', reset: 'optional bool — CLEAR the override and track the OS window again (width/height not needed)' }, 15, { sessionId: 'work', reset: true }),+            V('pup_install_toolbar', 'ONE-TIME automated install of the Adom Pup toolbar extension into the pup Chrome profile (pup drives chrome://extensions + ab drives the native folder dialog; branded Chrome killed --load-extension so this is the zero-click path). Idempotent: returns alreadyInstalled:true when present.', {}, 60, {}),             V('pup_annotate', 'Start the HUMAN annotation overlay on a window\'s active tab (pen/arrow/box/text toolbar injected in-page; also on every window\'s taskbar jump list as "Annotate this page"). Done captures a PNG AND leaves the drawing in the DOM, so the owning thread\'s next pup_screenshot shows exactly what the user marked — check session.lastAnnotation via pup_list_windows. action:"cancel" removes the overlay.', { sessionId: 'required window', action: 'optional "cancel"' }, 15, {}),             V('pup_dashboard', 'Open the LIVE pup management dashboard in its OWN window (its own profile, so its own taskbar icon). Shows every window pup is driving as taskbar button -> window(s) -> tabs with each tab favicon, owning AI thread, foreground/background + the reason given to foreground it, highFps lease, uptime, and WHY each overlay icon was chosen. Live over SSE. Reuses the one window on repeat calls; backgrounded by default like any pup window.', {}, 30, {}),             V('pup_deps', 'Report pup dependency health (wiki issue #15): depsPresent, selfInstalledThisBoot, lastError. Use this to answer "is pup broken?" without reading spawn logs.', { }, 10, {}),@@ -13837,6 +13862,81 @@ const server = http.createServer(async (req, res) => {           sendJSON(res, { success: true, output: JSON.stringify({ ok: true, url: args.url, sessionId: resolvedId, tabId, ...(authSteerHint(args.url) ? { _hint: authSteerHint(args.url).trim() } : {}) }) });           return;         }+        case 'pup_install_toolbar': {+          // v2.0.51 (John: "can't you just do that automatically behind the scenes since you own+          // pup?"): YES. Branded Chrome >=137 killed --load-extension, but pup OWNS this Chrome:+          // it stages the extension dir, drives chrome://extensions itself (dev-mode toggle ++          // Load unpacked through the shadow DOM), and ab drives the one NATIVE folder dialog+          // (desktop_ui_set/click). One-time per profile; the durable profile keeps it forever.+          if (process.platform !== 'win32') { sendJSON(res, { success: false, error: 'windows only' }); return; }+          try {+            // 1) stage the extension shipped inside the bridge onto disk+            const srcDir = path.join(__dirname, 'extension');+            const dstDir = path.join(os.homedir(), '.adom', 'pup-extension');+            fs.mkdirSync(dstDir, { recursive: true });+            for (const f of fs.readdirSync(srcDir)) fs.copyFileSync(path.join(srcDir, f), path.join(dstDir, f));+            // 2) open chrome://extensions in the shared profile+            const be = browsers.get('adom-you') || [...browsers.values()][0];+            if (!be || !be.browser) { sendJSON(res, { success: false, error: 'no pup browser running - open any pup window first', errorCode: 'no_browser' }); return; }+            const epg = await be.browser.newPage();+            await epg.goto('chrome://extensions', { waitUntil: 'domcontentloaded', timeout: 15000 });+            const already = await epg.evaluate(() => {+              try {+                const items = document.querySelector('extensions-manager').shadowRoot.querySelector('extensions-item-list').shadowRoot.querySelectorAll('extensions-item');+                return [...items].some(i => /Adom Pup/i.test(i.shadowRoot.querySelector('#name').textContent));+              } catch (e) { return false; }+            });+            if (already) { await epg.close().catch(() => {}); sendJSON(res, { success: true, output: JSON.stringify({ ok: true, alreadyInstalled: true }) }); return; }+            // 3) dev mode + Load unpacked (opens the native folder dialog)+            await epg.evaluate(() => {+              const tb = document.querySelector('extensions-manager').shadowRoot.querySelector('extensions-toolbar').shadowRoot;+              const dev = tb.querySelector('#devMode');+              if (dev && dev.getAttribute('aria-pressed') !== 'true' && !dev.checked) dev.click();+            });+            await new Promise(r => setTimeout(r, 700));+            await epg.evaluate(() => {+              const tb = document.querySelector('extensions-manager').shadowRoot.querySelector('extensions-toolbar').shadowRoot;+              tb.querySelector('#loadUnpacked').click();+            });+            // 4) drive the native dialog via ab: type the path, confirm+            let dlg = null;+            for (let i = 0; i < 12 && !dlg; i++) {+              await new Promise(r => setTimeout(r, 500));+              try {+                const fw = abPayload(await chrome.abCommand('desktop_find_window', { titleContains: 'Select the extension' }, { timeoutMs: 4000 }));+                const b2 = fw && (fw.best || (fw.data && fw.data.best));+                if (b2 && b2.hwnd) dlg = b2.hwnd;+              } catch (e) {}+            }+            if (!dlg) { await epg.close().catch(() => {}); sendJSON(res, { success: false, error: 'folder dialog never appeared', errorCode: 'dialog_missing' }); return; }+            let setOk = false;+            try {+              const r1 = abPayload(await chrome.abCommand('desktop_ui_set', { hwnd: dlg, contains: 'Folder', text: dstDir }, { timeoutMs: 6000 }));+              setOk = !!(r1 && (r1.ok || r1.set));+            } catch (e) {}+            if (!setOk) {+              try { await chrome.abCommand('desktop_ui_set', { hwnd: dlg, contains: 'File name', text: dstDir }, { timeoutMs: 6000 }); setOk = true; } catch (e) {}+            }+            let clicked = false;+            try { const r2 = abPayload(await chrome.abCommand('desktop_ui_click', { hwnd: dlg, contains: 'Select Folder' }, { timeoutMs: 6000 })); clicked = !!(r2 && (r2.ok || r2.clicked)); } catch (e) {}+            if (!clicked) { try { await chrome.abCommand('desktop_type', { hwnd: dlg, text: dstDir + '\n' }, { timeoutMs: 6000 }); } catch (e) {} }+            // 5) verify it landed+            let installed = false;+            for (let i = 0; i < 10 && !installed; i++) {+              await new Promise(r => setTimeout(r, 800));+              installed = await epg.evaluate(() => {+                try {+                  const items = document.querySelector('extensions-manager').shadowRoot.querySelector('extensions-item-list').shadowRoot.querySelectorAll('extensions-item');+                  return [...items].some(i2 => /Adom Pup/i.test(i2.shadowRoot.querySelector('#name').textContent));+                } catch (e) { return false; }+              }).catch(() => false);+            }+            await epg.close().catch(() => {});+            console.log(`[toolbar] extension install ${installed ? 'SUCCEEDED' : 'FAILED'} (staged at ${dstDir})`);+            sendJSON(res, { success: installed, output: JSON.stringify({ ok: installed, stagedAt: dstDir }), ...(installed ? {} : { error: 'extension did not appear after Load unpacked', errorCode: 'install_unverified' }) });+          } catch (e) { sendJSON(res, { success: false, error: `install failed: ${e.message}` }); }+          return;+        }         case 'pup_annotate': {           // start/cancel the human annotation overlay on a window's active tab. Jump-list clicks           // arrive as user-taskbar-menu (ownership-gate exempt); threads annotate their OWN windows.