← Commit history

1.8.49: remove input-timing user-click heuristic that false-fired while the user typed; launch passes always park full-size; late re-parks use foreground-only check

John Lauer ·84da815052 · ·parent 845f2b4
5 files changed +39−47
CHANGELOG.md+15
@@ -1,5 +1,20 @@ # Changelog — Adom Desktop Puppeteer Bridge +## 1.8.49 — 2026-07-14 — Kill the input-timing heuristic (it false-fired whenever the user was typing)++Opening windows while John was actively using the laptop: 2 of 3 came up `user-foreground`, small+(centered 80%) instead of parked full-size. Root cause: the "did the user click it?" check used+`GetLastInputInfo`, which measures input ANYWHERE on the system — so Chrome's startup self-raise ++the user typing in another app read as "user grabbed the pup window."++- **Launch passes (300/700/1200ms) now ALWAYS park — no user detection.** Within ~1.2s of launch the+  taskbar button barely exists; a foreground pup window is Chrome's self-raise. After the launch+  window, pup NEVER auto-re-parks, so a user click is never fought — no heuristics needed.+- **Late re-parks (post-resize) keep a simple foreground check only:** after startup Chrome doesn't+  self-raise, so pup-is-foreground = the user took it → leave it exactly as they have it.+- **Removed the centered-80% "show" placement** — windows are always full-monitor size; the user's+  own click is what brings them up (restoring exactly what Windows restores).+ ## 1.8.48 — 2026-07-14 — Background = on-screen z-bottom (minimize reverted: it broke screenshots)  1.8.47's minimize broke screenshots: a minimized window reports `visibilityState: hidden`, so
adom-bridge-puppeteer-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "1.8.48",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.48/adom-bridge-puppeteer-v1.8.48.zip",-  "sha256": "adf022bff63313fa7705d66a13b72613c01e75aaedddef1d77be5b5744c2b90d",-  "size": 165316,+  "version": "1.8.49",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.49/adom-bridge-puppeteer-v1.8.49.zip",+  "sha256": "b9e59ce594a9098585299570a362608e15a4e0ce8e6424b186566ce462c3c1e1",+  "size": 164981,   "verbPrefixes": [     "browser_",     "desktop_recorder_",
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.8.48\ No newline at end of file+1.8.49\ 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": "1.8.48",+  "version": "1.8.49",   "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. browser_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. browser_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.",   "homepage": "https://wiki.adom.inc/adom/adom-desktop",   "author": "Adom Inc.",
src/server.js+18−41
@@ -1642,32 +1642,16 @@ function osBackgroundWindowByPid(pid, cdpPort, restoreHwnd, respectUser) {       // Resolve the ONE real browser window (largest visible Chrome_WidgetWin_1), not a helper.       '$pup = [WinBgP]::BestWindow([int[]]@($pidList.ToArray()))',       'if ($pup -eq [IntPtr]::Zero) { Write-Output "no-window"; exit }',-      // v1.8.44: if the USER deliberately raised this window (clicked its taskbar button to-      // WATCH a page load), do NOT fight them. A physical input in the last ~1.5s WITH the pup-      // window already foreground means the user took it — not Chrome self-raising (which is-      // programmatic, no input). Skip the re-park and signal the caller to stop retrying.-      // respectUser is 0 for the initial off-screen park (user can't have clicked it yet).+      // respectUser (v1.8.49): used ONLY by LATE re-parks (post-resize etc.), never the launch+      // passes. Rationale: after startup, Chrome does NOT self-raise — so if the pup window is+      // foreground later, the USER took it (clicked its taskbar button). Leave it exactly as they+      // have it. NO input-timing heuristics: GetLastInputInfo measures input ANYWHERE on the+      // system, so "recent input" false-fires whenever the user is typing while a window opens —+      // that bug shipped in 1.8.44-48 and mis-shoved fresh windows into a small centered state.       `$respectUser = ${respectUser ? 1 : 0}`,       'if ($respectUser -eq 1) {',       '  $fg0 = [WinBgP]::GetForegroundWindow()',-      '  if ($fg0 -eq $pup) {',-      '    $lii = New-Object LASTINPUTINFO',-      '    $lii.cbSize = [uint32][System.Runtime.InteropServices.Marshal]::SizeOf($lii)',-      '    [WinBgP]::GetLastInputInfo([ref]$lii) | Out-Null',-      '    $idle = [WinBgP]::GetTickCount() - $lii.dwTime',-      '    if ($idle -lt 1500) {',-      // The USER deliberately raised this window (clicked its taskbar button) to SEE it. It may-      // still be sitting at its off-screen LAUNCH coords, so just "leaving it alone" would show-      // them NOTHING. Transition it into a normal, visible, on-screen window (centered ~80% of-      // the monitor), SHOW it, and bring it to the TOP — and DON'T hand focus back, because the-      // user wants to use it. This is the background↔foreground handoff: AI runs it in the bg,-      // the moment the human takes it, it becomes a real usable window.-      '      $vw = [int]($scrW * 0.8); $vh = [int]($scrH * 0.8)',-      '      $vx = [int](($scrW - $vw) / 2); $vy = [int](($scrH - $vh) / 2)',-      '      [WinBgP]::SetWindowPos($pup, [IntPtr]0, $vx, $vy, $vw, $vh, 0x40) | Out-Null', // HWND_TOP, SWP_SHOWWINDOW-      '      Write-Output "user-foreground"; exit',-      '    }',-      '  }',+      '  if ($fg0 -eq $pup) { Write-Output "user-foreground"; exit }',       '}',       // ORDER MATTERS (v1.8.46): the browser grabbed the foreground on launch while it's still       // OFF-SCREEN (invisible). Hand focus back to the user FIRST — deactivating the pup window@@ -2282,31 +2266,24 @@ async function getOrLaunchBrowser(profileName, { freshProfile = false, strictPer       // can re-raise itself during startup (first paint, restore prompts). Resolve the       // window via the CDP-port owner (reliable across launcher→browser pid relaunches).       if (!foreground && process.platform === 'win32') {-        // v1.8.47b: background = ON-SCREEN at z-bottom (occluded, still renders → screenshots-        // work). Minimize was tried and REJECTED — a minimized window reports visibilityState-        // 'hidden' and Page.captureScreenshot hangs (~27s) even with occlusion disabled. The-        // park deactivates FIRST then moves on-screen behind everything (no pop), using the-        // EnumWindows handle (the real window, not a phantom). The re-park window is SHORT-        // (only ~1.2s, just long enough to counter Chrome's startup foreground-grab) so a user-        // click after that is NEVER fought; within it, GetLastInputInfo distinguishes a real-        // click (→ bring on-screen + latch, stop) from Chrome self-raising (→ re-park).+        // v1.8.49: background = ON-SCREEN at z-bottom (occluded, still renders → screenshots+        // work; minimize was tried and REJECTED — visibilityState 'hidden' hangs+        // Page.captureScreenshot ~27s even with occlusion disabled). The park deactivates FIRST+        // then moves on-screen behind everything (no pop), using the EnumWindows handle (the+        // real window, not a phantom). The launch passes ALWAYS park, no user detection: within+        // ~1.2s of launch the taskbar button barely exists, so a foreground pup window is+        // Chrome's startup self-raise, not the user — and input-timing heuristics false-fire+        // whenever the user is typing anywhere (the 1.8.44-48 bug that mis-sized fresh windows).+        // After this window ends, pup NEVER auto-re-parks, so a user click is never fought.         b._bgResult = osBackgroundWindowByPid(child.pid, debugPort, priorForeground);         console.log(`[bg] initial park for "${profileName}": ${b._bgResult}`);-        const _bgTimers = [];         for (const delay of [300, 700, 1200]) {-          const t = setTimeout(() => {+          setTimeout(() => {             try {-              if (b._userForeground) return; // user has it — never re-park-              const r = osBackgroundWindowByPid(child.pid, debugPort, priorForeground, true);+              const r = osBackgroundWindowByPid(child.pid, debugPort, priorForeground);               if (r && r !== 'no-window') b._bgResult = r;-              if (r === 'user-foreground') {-                b._userForeground = true;-                for (const tt of _bgTimers) clearTimeout(tt);-                console.log(`[bg] user raised "${profileName}" — respecting it, halting re-park`);-              }             } catch (e) {}           }, delay);-          _bgTimers.push(t);         }       }       return b;