← Commit history

The annotate lag John felt is gone by architecture: the whole overlay module is PRECACHED into every page at load, so the pencil click and Alt+Shift+A open the toolbar instantly client-side with zero bridge round-trip, and a new 700ms fast-harvest loop replaces the 10-second identity sweep for capture pickup. The capture pipeline is reordered for perceived speed: screenshot, then immediately the client-side camera flash (a deliberate white 20 percent pulse over exactly the captured region plus margin, replacing the unexplained blip) and the toast with the preview, while the caption bar, iTXt, clipboard, and disk writes run in the background. After the shot the drawings fade out so the user is back to normal surfing. And the pencil tooltip is restyled to the canonical tooltips-skill surface: teal-tinted border, blur, structured title, body, and a monospace shortcut chip

John Lauer ·fd2d201585 ·2mo ago ·parent 64a8e63
4 files changed +120−80
adom-bridge-puppeteer-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "2.0.71",-  "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.71/adom-bridge-puppeteer-v2.0.71.zip",-  "sha256": "b73393399be6bc1cf4273dc4973b257904c3d19990920f43a22dff8626c3bd26",-  "size": 833315,+  "version": "2.0.73",+  "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.73/adom-bridge-puppeteer-v2.0.73.zip",+  "sha256": "6cda3be7368efa1413bb51dbd81b359500d360b11284c361757b0531b80339a6",+  "size": 834115,   "verbPrefixes": [     "browser_",     "desktop_recorder_",
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-2.0.71+2.0.73
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "puppeteer",   "displayName": "Puppeteer (Chrome for Testing + Native)",-  "version": "2.0.71",+  "version": "2.0.73",   "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/server.js+114−74
@@ -694,8 +694,13 @@ function attachTab(session, sessionId, page, { makeActive = true, opener = 'user   } catch (e) {}   // v1.8.21: install the shadow-DOM + scroll helpers on every document so pup_eval   // can use $deep / $$deep / deepText without hand-rolling a shadow-root walk.-  if (process.platform === 'win32' && pupSettings().tbFloatyPencil !== 'off') {-    try { page.evaluateOnNewDocument(FLOATY_JS).catch(() => {}); page.evaluate(FLOATY_JS).catch(() => {}); } catch (e) {}+  if (process.platform === 'win32') {+    // v2.0.72: PRECACHE the annotate module in every page so the pencil and hotkey open the+    // toolbar instantly, client-side, with zero bridge round-trip (John: it was laggy).+    try { page.evaluateOnNewDocument(ANNOTATE_OVERLAY_JS).catch(() => {}); page.evaluate(ANNOTATE_OVERLAY_JS).catch(() => {}); } catch (e) {}+    if (pupSettings().tbFloatyPencil !== 'off') {+      try { page.evaluateOnNewDocument(FLOATY_JS).catch(() => {}); page.evaluate(FLOATY_JS).catch(() => {}); } catch (e) {}+    }   }   try { page.evaluateOnNewDocument(PUP_DEEP_HELPERS).catch(() => {}); } catch (e) {}   // v1.9.48: re-evaluate the window CATEGORY when this tab finishes navigating.@@ -5293,9 +5298,9 @@ function appIconReprimand(url, hadFavicon) { // chat carries both. A preview toast shows the exact crop so the user can verify the grab. // Injected via CDP (CSP-immune), zero external resources; no backticks/interpolation inside the // overlay string (it lives in a template literal).-const ANNOTATE_OVERLAY_JS = `(() => {+const ANNOTATE_OVERLAY_JS = `window.__pupStartAnnotate = function (initTool) {   if (window.__pupAnnotate) { window.__pupAnnotate.show(); return 'shown'; }-  var initTool = window.__pupAnnotateTool || 'pen'; window.__pupAnnotateTool = null;+  initTool = initTool || 'pen';   var S = { tool: 'pen', color: '#f85149', strokes: [], cur: null, quick: true, seq: 0 };   var COLORS = ['#f85149', '#d29922', '#3fb950', '#64abff', '#8c6bf7', '#e6edf3'];   var wrap = document.createElement('div');@@ -5489,12 +5494,43 @@ const ANNOTATE_OVERLAY_JS = `(() => {   function exitAll() { removeEventListener('keydown', onKey, true); wrap.remove(); window.__pupAnnotate = null; window.__pupAnnotateResult = { done: false, ts: Date.now() }; }   function onKey(e) { if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); exitAll(); } }   addEventListener('keydown', onKey, true);+  // v2.0.72: called by the bridge the moment the screenshot is TAKEN (never before, so the+  // flash is not in the shot): camera-flash feedback over the captured region, instant toast+  // with the preview, then the drawings fade out so the user is back to normal surfing.+  window.__pupCaptureDone = function (region, previewUrl, line1) {+    try {+      var fl = document.createElement('div');+      var rg = region && region.w ? region : { x: 0, y: 0, w: innerWidth, h: innerHeight };+      fl.style.cssText = 'position:fixed;left:' + rg.x + 'px;top:' + rg.y + 'px;width:' + rg.w + 'px;height:' + rg.h + 'px;z-index:2147483647;background:#ffffff;opacity:.2;pointer-events:none;transition:opacity .25s ease;border-radius:4px;';+      document.documentElement.appendChild(fl);+      requestAnimationFrame(function () { fl.style.opacity = '0'; });+      setTimeout(function () { try { fl.remove(); } catch (e) {} }, 350);+    } catch (e) {}+    try {+      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;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 = line1 || 'Captured'; cap.style.cssText = 'margin-bottom:7px;color:#00d4cb;font-weight:500;';+      t.appendChild(cap);+      if (previewUrl) { var im = document.createElement('img'); im.src = previewUrl; 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);+    } catch (e) {}+    // fade the drawings out (John: after the snapshot, back to normal surfing)+    setTimeout(function () {+      try {+        cv.style.transition = 'opacity .8s ease'; cv.style.opacity = '0';+        setTimeout(function () { try { wrap.remove(); window.__pupAnnotate = null; window.__pupCaptureDone = null; removeEventListener('keydown', onKey, true); } catch (e) {} }, 900);+      } catch (e) {}+    }, 2200);+  };   window.__pupAnnotate = { show: function () { wrap.style.display = ''; bar.style.display = 'flex'; cv.style.pointerEvents = 'auto'; addEventListener('keydown', onKey, true); } };   addEventListener('resize', fit);   document.documentElement.appendChild(wrap);   fit(); selTool(initTool);   return 'started';-})()`;+};`;  // crc32 (fallback when zlib.crc32 is unavailable, node <20.15) let _crcTable = null;@@ -5634,8 +5670,10 @@ const FLOATY_JS = `(() => {     btn.style.cssText = 'width:30px;height:30px;border-radius:8px;display:grid;place-items:center;background:rgba(13,17,23,.72);border:1px solid rgba(0,184,177,.45);cursor:pointer;opacity:.55;transition:opacity .15s ease;padding:0;';     btn.innerHTML = '<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="#e6edf3" 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>';     var tip = document.createElement('div');-    tip.textContent = 'Draw on this page to show your AI exactly what you mean; a red circle says it best. Click to start (Alt+Shift+A works too). The moment you lift the mouse, the marked-up area is copied to your clipboard, ready to paste into any AI chat.';-    tip.style.cssText = 'position:fixed;z-index:2147483647;max-width:280px;background:#21262d;border:1px solid #30363d;border-radius:8px;padding:7px 10px;font:12px Satoshi,system-ui,sans-serif;color:#c9d1d9;box-shadow:0 6px 20px #000b;pointer-events:none;opacity:0;transform:translateY(4px);transition:opacity .18s ease,transform .18s ease;';+    tip.innerHTML = '<div style=\"font-weight:700;color:#e6edf3;margin-bottom:4px;font-family:Familjen Grotesk,Satoshi,sans-serif;\">Annotate this page</div>'+      + '<div style=\"color:#c9d1d9;\">Draw on the page to show your AI exactly what you mean; a red circle says it best. The moment you lift the mouse, the marked-up area is copied to your clipboard, ready to paste into any AI chat.</div>'+      + '<div style=\"margin-top:7px;color:#8b949e;font-size:11.5px;\">Shortcut&nbsp;&nbsp;<span style=\"font-family:JetBrains Mono,monospace;font-size:10.5px;border:1px solid #30363d;border-radius:4px;padding:1px 6px;color:#c9d1d9;\">Alt&nbsp;Shift&nbsp;A</span></div>';+    tip.style.cssText = 'position:fixed;z-index:2147483647;max-width:min(320px,calc(100vw - 40px));background:rgba(22,27,34,.96);color:#e6edf3;border:1px solid rgba(0,184,177,.28);border-radius:10px;padding:9px 13px;font:400 13px/1.4 Satoshi,system-ui,sans-serif;box-shadow:0 12px 40px rgba(0,0,0,.55);backdrop-filter:blur(16px);pointer-events:none;opacity:0;transform:translateY(4px);transition:opacity .18s ease,transform .18s ease;';     root.appendChild(btn); root.appendChild(tip);     var timer = null;     host.onmouseenter = function () { btn.style.opacity = '1'; timer = setTimeout(function () {@@ -5646,7 +5684,7 @@ const FLOATY_JS = `(() => {       tip.style.left = x + 'px'; tip.style.top = y + 'px'; tip.style.opacity = '1'; tip.style.transform = 'translateY(0)';     }, 600); };     host.onmouseleave = function () { btn.style.opacity = '.55'; clearTimeout(timer); tip.style.opacity = '0'; tip.style.transform = 'translateY(4px)'; };-    host.onclick = function () { clearTimeout(timer); tip.style.opacity = '0'; btn.style.borderColor = '#00d4cb'; window.__pupFloatyClick = Date.now(); setTimeout(function () { btn.style.borderColor = 'rgba(0,184,177,.45)'; }, 1500); };+    host.onclick = function () { clearTimeout(timer); tip.style.opacity = '0'; btn.style.borderColor = '#00d4cb'; if (window.__pupStartAnnotate) { window.__pupStartAnnotate('pen'); } else { window.__pupFloatyClick = Date.now(); } setTimeout(function () { btn.style.borderColor = 'rgba(0,184,177,.45)'; }, 1500); };     document.body.appendChild(host);   }   add();@@ -5655,7 +5693,7 @@ const FLOATY_JS = `(() => {   addEventListener('keydown', function (e) {     if (e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey && (e.key === 'A' || e.key === 'a')) {       e.preventDefault(); e.stopPropagation();-      window.__pupFloatyClick = Date.now();+      if (window.__pupStartAnnotate) { window.__pupStartAnnotate('pen'); } else { window.__pupFloatyClick = Date.now(); }     }   }, true); })()`;@@ -5694,61 +5732,52 @@ function unpackedExtensionId(dir) {   return [...h].map(c => String.fromCharCode('a'.charCodeAt(0) + parseInt(c, 16))).join(''); } async function startAnnotation(session, sessionId, callerThread, opts) {-  // v2.0.61 (menu ralph: annotate/snip injected into pup's belief of the active tab while the-  // user was LOOKING at another — invisible overlay, "snip never worked"): the resolved tab (the-  // one the user clicked from) wins; then Chrome's actually-visible tab; then the old chain.-  let tab = (opts && opts.tabId) ? (session.tabs || []).find(t => t.tabId === opts.tabId) : null;-  if (!tab) { for (const t5 of (session.tabs || [])) { try { const vis = await Promise.race([t5.page.evaluate(() => document.visibilityState), new Promise(r => setTimeout(() => r(''), 900))]); if (vis === 'visible') { tab = t5; break; } } catch (e) {} } }-  if (!tab) tab = (session.activeTabId && (session.tabs || []).find(t => t.tabId === session.activeTabId)) || (session.tabs || [])[0];-  const pg = tab && tab.page;+  const tab = ((opts && opts.tabId) && (session.tabs || []).find(t => t.tabId === opts.tabId)) || null;+  let pg = tab && tab.page;+  if (!pg) {+    for (const t5 of (session.tabs || [])) { try { const vis = await Promise.race([t5.page.evaluate(() => document.visibilityState), new Promise(r => setTimeout(() => r(''), 900))]); if (vis === 'visible') { pg = t5.page; break; } } catch (e) {} }+  }+  if (!pg) { const t0 = (session.activeTabId && (session.tabs || []).find(t => t.tabId === session.activeTabId)) || (session.tabs || [])[0]; pg = t0 && t0.page; }   if (!pg) throw new Error('no page to annotate');   const tool = (opts && opts.tool) || 'pen';-  await pg.evaluate((t2) => { window.__pupAnnotateTool = t2; window.__pupAnnotateResult = null; }, tool);-  await pg.evaluate((code) => eval(code), ANNOTATE_OVERLAY_JS);+  await pg.evaluate((code) => { if (!window.__pupStartAnnotate) eval(code); }, ANNOTATE_OVERLAY_JS);+  await pg.evaluate((t2) => { window.__pupAnnotateResult = null; window.__pupStartAnnotate(t2); }, tool);   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);-  // Done/snip/cancel end it. Drawing STAYS in the DOM after Done so the owning thread's next-  // screenshot shows it.+}++// v2.0.72 CAPTURE PIPELINE, reordered for perceived speed (John: the blip, the slow toast):+// screenshot first, then IMMEDIATELY the client-side camera flash + toast with the preview, and+// only afterwards the slow legs (caption bar, iTXt, clipboard, disk) run in the background.+async function processAnnotateCapture(session, sessionId, pg, res) {+  const ts = new Date().toISOString().replace(/[:.]/g, '-');+  const isSnip = res.snip && res.snip.w > 8 && res.snip.h > 8;+  const clipRect = isSnip ? res.snip : res.bbox;+  const fp = path.join(SHOTS_DIR, `annotate-${isSnip ? 'snip-' : ''}${sessionId}-${ts}.png`);+  let buf = await pg.screenshot(clipRect ? { clip: { x: clipRect.x, y: clipRect.y, width: clipRect.w, height: clipRect.h } } : {});+  // instant feedback: flash + toast (preview resize is fast, everything else waits)+  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) {}+  const line1 = `${isSnip ? 'Snippet' : 'Annotation'} copied to your clipboard`;+  pg.evaluate((rg, pv, l1) => { if (window.__pupCaptureDone) window.__pupCaptureDone(rg, pv, l1); }, clipRect || null, preview, line1).catch(() => {});+  // slow legs, off the perceived path   (async () => {-    const t0 = Date.now();-    let lastSeq = 0;-    while (Date.now() - t0 < 15 * 60000) {-      try {-        await pg.waitForFunction('window.__pupAnnotateResult', { timeout: 15 * 60000 - (Date.now() - t0), polling: 800 });-      } catch (e) { console.log(`[annotate] "${sessionId}" wait ended: ${e.message}`); return; }-      let res = null;-      try { res = await pg.evaluate(() => { const r = window.__pupAnnotateResult; window.__pupAnnotateResult = null; return r; }); } catch (e) { return; }-      if (!res) continue;-      if (!res.done) { console.log(`[annotate] "${sessionId}" cancelled`); return; }-      if (res.seq && res.seq <= lastSeq) { if (!res.quick) return; continue; }-      lastSeq = res.seq || (lastSeq + 1);-      try {-        const ts = new Date().toISOString().replace(/[:.]/g, '-');-        const isSnip = res.snip && res.snip.w > 8 && res.snip.h > 8;-        const clipRect = isSnip ? res.snip : res.bbox;   // tight-crop (1a2) unless nothing drawn-        const fp = path.join(SHOTS_DIR, `annotate-${isSnip ? 'snip-' : ''}${sessionId}-${ts}.png`);-        let buf = await pg.screenshot(clipRect ? { clip: { x: clipRect.x, y: clipRect.y, width: clipRect.w, height: clipRect.h } } : {});-        const owner = session.owner || session._ownerThread || 'unknown-thread';-        let url = ''; try { url = pg.url() || ''; } catch (e) {}-        const shortUrl = url.replace(/^https?:\/\//, '').slice(0, 80);-        const when = new Date().toTimeString().slice(0, 5);-        if (pupSettings().annCaptionBar !== 'off') buf = await addCaptionBar(buf, `pup · ${owner}'s window · ${shortUrl} · ${when}`);-        const sidecar = { kind: 'pup-annotation', thread: owner, container: session._callerContainer || null, sessionId, url, ts: new Date().toISOString(), shapes: res.shapes || 0, crop: clipRect || 'full', viewport: null };-        buf = pngAddITXt(buf, 'pup-annotation', JSON.stringify(sidecar));-        fs.writeFileSync(fp, buf);-        session._lastAnnotation = { path: fp, ts: Date.now(), shapes: res.shapes, snip: isSnip ? res.snip : null };-        const sidecarText = `pup snippet — ${owner}'s window — ${url} — ${sidecar.ts}\n` + JSON.stringify(sidecar, null, 1);-        const clipped = await copyCaptureToClipboard(fp, sidecarText);-        // 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'}`);-        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}`); }-      return;   // v2.0.59: every capture is terminal — auto mode is one-shot (circle, captured, toolbar gone)-    }+    try {+      const owner = session.owner || session._ownerThread || 'unknown-thread';+      let url = ''; try { url = pg.url() || ''; } catch (e) {}+      const shortUrl = url.replace(/^https?:\/\//, '').slice(0, 80);+      const when = new Date().toTimeString().slice(0, 5);+      if (pupSettings().annCaptionBar !== 'off') buf = await addCaptionBar(buf, `pup · ${owner}'s window · ${shortUrl} · ${when}`);+      const sidecar = { kind: 'pup-annotation', thread: owner, container: session._callerContainer || session._ownerContainer || null, sessionId, url, ts: new Date().toISOString(), shapes: res.shapes || 0, crop: clipRect || 'full', viewport: null };+      buf = pngAddITXt(buf, 'pup-annotation', JSON.stringify(sidecar));+      fs.writeFileSync(fp, buf);+      session._lastAnnotation = { path: fp, ts: Date.now(), shapes: res.shapes, snip: isSnip ? res.snip : null };+      const sidecarText = `pup snippet — ${owner}'s window — ${url} — ${sidecar.ts}\n` + JSON.stringify(sidecar, null, 1);+      const clipped = await copyCaptureToClipboard(fp, sidecarText);+      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 post-processing failed: ${e.message}`); }   })(); }+ async function wikiCaption(text, expiresInMs) {   try {     // v1.9.11: AD ≥1.9.144 exposes desktop_caption on the LOOPBACK direct API (the path bridges@@ -15186,28 +15215,39 @@ const _onPupBound = async () => {   // return;` — and minimal-touch is the normal mode, so it never ran once. Overlays are explicitly   // ALLOWED in minimal-touch (v1.9.191, gentle branding), so enforcement gets its OWN interval with no   // minimal-touch gate. 10s cadence: a window is never knowingly bare for more than ~10s.+  // v2.0.72: the FAST loop — 700ms harvest of floaty clicks (fallback path only now) and+  // annotate RESULTS, so mouse-up-to-toast is ~1s instead of riding the 10s identity sweep.+  setInterval(async () => {+    if (process.platform !== 'win32') return;+    for (const [sidQ, sQ] of [...sessions]) {+      if (sQ._lostBrowser || !(sQ.tabs || []).length) continue;+      for (const tQ of (sQ.tabs || [])) {+        if (!tQ || !tQ.page) continue;+        let got = null;+        try {+          got = await Promise.race([+            tQ.page.evaluate(() => {+              const c = window.__pupFloatyClick; const r = window.__pupAnnotateResult;+              window.__pupFloatyClick = null; window.__pupAnnotateResult = null;+              return (c || r) ? { c: c || null, r: r || null } : null;+            }),+            new Promise(r2 => setTimeout(() => r2(null), 600)),+          ]);+        } catch (e) {}+        if (!got) continue;+        if (got.c) { console.log(`[floaty] "${sidQ}" pencil clicked (fallback path) — starting annotate on ${tQ.tabId}`); startAnnotation(sQ, sidQ, 'user-floaty', { tool: 'pen', tabId: tQ.tabId }).catch(() => {}); }+        if (got.r && got.r.done) processAnnotateCapture(sQ, sidQ, tQ.page, got.r).catch(e2 => console.log(`[annotate] "${sidQ}" capture failed: ${e2.message}`));+        else if (got.r && !got.r.done) console.log(`[annotate] "${sidQ}" cancelled`);+      }+    }+  }, 700);+   setInterval(async () => {     if (process.platform !== 'win32') return;     // v2.0.67: floaty-click harvest runs BEFORE the identity gate (the pencil must work even when     // identity stamping is unsupported) and heartbeats so a dead sweep is visible in the log.     global.__floatyHb = (global.__floatyHb || 0) + 1;     if (global.__floatyHb % 15 === 1) console.log(`[floaty] sweep heartbeat #${global.__floatyHb} (${sessions.size} session(s), identitySupported=${_identitySupported})`);-    try {-      for (const [sidF, sF] of [...sessions]) {-        if (sF._lostBrowser || !(sF.tabs || []).length) continue;-        for (const tF of (sF.tabs || [])) {-          if (!tF || !tF.page) continue;-          const clickedF = await Promise.race([-            tF.page.evaluate(() => { const c = window.__pupFloatyClick; window.__pupFloatyClick = null; return c || null; }),-            new Promise(r => setTimeout(() => r(null), 800)),-          ]).catch(() => null);-          if (clickedF) {-            console.log(`[floaty] "${sidF}" pencil clicked — starting annotate (pen) on ${tF.tabId}`);-            startAnnotation(sF, sidF, 'user-floaty', { tool: 'pen', tabId: tF.tabId }).catch(() => {});-          }-        }-      }-    } catch (e) { console.log(`[floaty] harvest error: ${e.message}`); }     if (_identitySupported !== true) return;     try {       for (const [sid2, s2] of [...sessions]) {