← Commit history

v2.0.255: Fix adom/pup-bridge#65: pup_evaluate ran the HOVER handler. Its alias label was written as a switch fallthrough above case 'pup_hover' instead of above case 'pup_eval', so every pup_evaluate call returned 'pup_hover needs selector, or x + y' / hover_target_missing while the _hint stayed pup_evaluate's own — right docs plus wrong error, the tell for a misdispatch. That made the only verb that can READ the DOM unusable, so a page could be driven but never asserted on. Also accepts expr/expression/script/js/code (John probed all four, none of which ever reached the handler) and documents them in the catalog. Second bug from the same report: PUP_ISSUES_REF, pasted into every response's _reportIssues hint, still named the pre-rename slug adom/adom-desktop-puppeteer-bridge, which 404s — so the one string whose job is routing bug reports sent them nowhere. Now adom/pup-bridge, in the hint and in the dashboard link.

John Lauer ·42e866b9e1 ·2mo ago ·parent 942321a
4 files changed +28−15
adom-bridge-puppeteer-manifest.json+5−5
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "2.0.254",-  "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.254/adom-bridge-puppeteer-v2.0.254.zip",-  "sha256": "c8a45e166b20d017ef4de185961f5a9e2d84f76279d710d7d2f6bbcdb91630db",-  "size": 952082,+  "version": "2.0.255",+  "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.255/adom-bridge-puppeteer-v2.0.255.zip",+  "sha256": "95eb9f61a1cb565d7b7ff8d3ac2cb7dbc7c7d1e8a86c0bd6989b5eaf82287fff",+  "size": 952730,   "verbPrefixes": [     "browser_",     "desktop_recorder_",@@ -12,5 +12,5 @@   ],   "healthEndpoint": "/status",   "statusVerb": "browser_readiness",-  "released_at": "2026-08-20T10:51:28.907780Z"+  "released_at": "2026-08-20T11:00:14.163938Z" }
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-2.0.254\ No newline at end of file+2.0.255\ No newline at end of file
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "puppeteer",   "displayName": "Puppeteer (Chrome for Testing + Native)",-  "version": "2.0.254",+  "version": "2.0.255",   "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+21−8
@@ -10988,7 +10988,11 @@ const VERB_META = { //   • ONCE per bridge process on the first verb — orientation, so it's known before anything breaks. //   • Occasionally after that (every Nth call) — a quiet reminder on a long-running session. // Opt out with pup settings {reportIssues:"off"} for a user who doesn't want it.-const PUP_ISSUES_REF = 'adom/adom-desktop-puppeteer-bridge';+// v2.0.255 (John, adom/pup-bridge#65, second bug in the same report): this ref is pasted into+// EVERY pup response's _reportIssues hint, and the old slug has 404'd since the rename. The+// hint's whole job is to route bug reports back here, so a stale slug means reports land+// nowhere or get abandoned — the worst possible thing for the one string that asks for help.+const PUP_ISSUES_REF = 'adom/pup-bridge'; // ─── "You just put something on screen — LOOK at it" (v1.9.99, John's ask) ────────────────────── // The common failure: an AI generates or edits an app, opens it in pup, sees ok:true, and reports // success to the user WITHOUT ever looking at the render. ok:true means the NAVIGATION worked. It@@ -11919,7 +11923,7 @@ footer{color:#5c6c7a;font-size:11px;padding:8px 18px 22px}   <div class="pop" id="pop">     <div>version <b id="cV">?</b></div>     <div id="popMeta">connecting</div>-    <div><a href="https://wiki.adom.inc/adom/adom-desktop-puppeteer-bridge" target="_blank" rel="noopener">wiki: adom-desktop-puppeteer-bridge</a></div>+    <div><a href="https://wiki.adom.inc/adom/pup-bridge" target="_blank" rel="noopener">wiki: pup-bridge</a></div>   </div>   <div class="vsep"></div>   <div class="subject">@@ -13357,7 +13361,7 @@ const server = http.createServer(async (req, res) => {             V('pup_screenshot_element', 'Screenshot ONE element, resolved by CSS selector OR visible text (pierces shadow DOM), scrolled into view first. The clean way to grab a single card/widget on a component-heavy page. Text matches the TIGHTEST text node — to capture the enclosing CARD, add closest (e.g. {text:"Download…", closest:".download-card"}); with closest, an ambiguous phrase still lands on the real widget (skips prose that has no such ancestor). Returns base64 + filePath like pup_screenshot.', { sessionId: 'required str', selector: 'CSS selector (or use text)', text: 'visible text to match (or use selector)', closest: 'optional selector — climb to the nearest ancestor matching it (the card container)', padding: 'optional int px around the element', deep: 'optional bool — pierce shadow DOM (default true)' }, 30, { sessionId: 'work', selector: '.download-card' }),             V('pup_scroll', 'Scroll the ACTUAL scrolling element (nearest ancestor whose scrollHeight>clientHeight, NOT just window — window.scrollTo no-ops on nested-scroll SPAs). Scroll by delta {dy,dx} OR to an element {toSelector|toText} (pierces shadow DOM). Returns {scrollElement, scrollTop, scrollHeight, clientHeight} so you can confirm it landed.', { sessionId: 'required str', dy: 'optional int px (down+/up-)', dx: 'optional int px', toSelector: 'optional CSS selector to scroll to', toText: 'optional visible text to scroll to', block: 'optional start|center|end (for scroll-to; default center)', behavior: 'optional auto|smooth' }, 30, { sessionId: 'work', toText: 'Download' }),             V('pup_set_viewport', 'Set the page viewport (CDP device-metrics) deterministically, independent of the OS window size — fixes a too-small innerWidth so fullPage/element shots render at a known width. width+height required.', { sessionId: 'required str', width: 'required int px', height: 'required int px', deviceScaleFactor: 'optional number (default 1)' }, 15, { sessionId: 'work', width: 1280, height: 900 }),-            V('pup_evaluate', 'Evaluate JS in the page; returns the result.', { sessionId: 'required str', expression: 'required str' }, 30, { sessionId: 'work', expression: 'document.title' }),+            V('pup_evaluate', 'Evaluate JS in the page and return the result — THE verb for reading DOM state / asserting on a page (same handler as pup_eval). Add frame:"<name|url-substring|index>" to run inside an iframe.', { sessionId: 'required str', expression: 'required str — aliases accepted: expr, script, js, code', frame: 'optional str — run inside this iframe instead of the top document' }, 30, { sessionId: 'work', expression: 'document.title' }),             V('pup_fetch_url', 'Fetch a URL\'s content via the browser (cookies/session intact).', { sessionId: 'required str', url: 'required str' }, 45, { sessionId: 'work', url: 'https://api.example.com' }),             V('pup_download', 'Download a file THROUGH THE BROWSER, exactly as the user clicking it would — real session, real cookies, real anti-bot cookies — and Chrome saves it to the Downloads folder. This is the right verb for vendor files (datasheets, KiCad/Altium libraries, STEP 3D models) that a server-side fetch/curl can never get past the site\'s bot wall. Pass selector to click the actual download button (authentic), or url to force-download via an in-page a[download]. Confirms completion via Chrome\'s download events and returns the ACTUAL saved path + byte count + file magic. For a gated file, log in on the window first (pup_login).', { sessionId: 'optional str', selector: 'optional str — the real download control to click', url: 'optional str — download this URL via an in-page a[download] click', saveAs: 'optional str — filename override', saveDir: 'optional str — default the user\'s Downloads folder' }, 100, { sessionId: 'work', selector: 'button.download' }),             V('pup_input_dispatch', 'Low-level input (mouse/keyboard) at coords — beats some overlays.', { sessionId: 'required str', type: 'required str', x: 'optional int', y: 'optional int' }, 20, { sessionId: 'work', type: 'mousePressed', x: 100, y: 200 }),@@ -15959,9 +15963,6 @@ const server = http.createServer(async (req, res) => {           });           return;         }-        // pup_evaluate is the catalog/manifest name; pup_eval the historical one.-        // Accept both, with either `expr` or `expression`.-        case 'pup_evaluate':         case 'pup_hover': {           // v1.9.198 (Bills thread): pup_hover did not exist — the only way to move the pointer           // without clicking was pup_input_dispatch {type:'move'}, which is unobvious next to@@ -16048,10 +16049,22 @@ const server = http.createServer(async (req, res) => {           }           return;         }+        // pup_evaluate is the catalog/manifest name; pup_eval the historical one. Accept both.+        // v2.0.255 (John, adom/pup-bridge#65): this alias was written as a fallthrough label above+        // `case 'pup_hover'` instead of here, so EVERY pup_evaluate call ran the HOVER handler and+        // came back "pup_hover needs selector, or x + y" / hover_target_missing — an error naming a+        // verb the caller never called, while the _hint stayed pup_evaluate's own (that split is the+        // tell for a misdispatch, and it is worth remembering: right docs + wrong error = routing).+        // It made the only verb that can READ the DOM unusable, so a page could be driven but never+        // asserted on, which is exactly where it left John mid-ralph.+        case 'pup_evaluate':         case 'pup_eval': {-          if (!args.expr && args.expression) args.expr = args.expression;+          // He probed expr/expression/script/js/code, all four failing identically because none of+          // them ever reached this handler. Since the catalog did not state the arg name either,+          // accept the obvious synonyms rather than making anyone guess again.+          for (const _a of ['expression', 'script', 'js', 'code']) { if (!args.expr && args[_a]) args.expr = args[_a]; }           if (!args.expr) {-            sendJSON(res, { success: false, error: 'Missing required parameter "expr" (alias: "expression"). Usage: pup_eval { "sessionId": "...", "expr": "document.title" }' });+            sendJSON(res, { success: false, errorCode: 'eval_expr_missing', error: 'Missing required parameter "expr" (aliases: "expression", "script", "js", "code"). Usage: pup_eval { "sessionId": "...", "expr": "document.title" }' });             return;           }           let session, resolvedId;