app
Pup - the AI's own browser
Public Made by Adomby adom
pup gives your AI a real browser on your desktop that it drives completely, in the background, signed in as you: windows that never jump in front of your work, one shared profile that learns your logins, and every window labeled on the taskbar with its AI thread's name.
← Commit history
v2.0.641 (insiders): background birth measured to one frame: park while invisible + bottomed the instant Chrome first shows it (busy hold 100 ms, 300 ms total); no ab attention-clear on open/navigate (it raised + activated every newborn); birth watcher armed after a cold launch (thread tile before the first show on cold opens too); normal unfocused birth; ms log timestamps + z-watch; install.sh/package.json say adom-bridge
10 files changed
+161−27
rust/Cargo.lock+12−12@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-pup-bridge"-version = "2.0.634"+version = "2.0.641" dependencies = [ "pup-browser", "pup-cdp",@@ -655,7 +655,7 @@ dependencies = [ [[package]] name = "pup-browser"-version = "2.0.634"+version = "2.0.641" dependencies = [ "pup-core", "pup-host",@@ -667,7 +667,7 @@ dependencies = [ [[package]] name = "pup-cdp"-version = "2.0.634"+version = "2.0.641" dependencies = [ "serde_json", "thiserror",@@ -677,7 +677,7 @@ dependencies = [ [[package]] name = "pup-core"-version = "2.0.634"+version = "2.0.641" dependencies = [ "serde", "serde_json",@@ -687,7 +687,7 @@ dependencies = [ [[package]] name = "pup-dashboard"-version = "2.0.634"+version = "2.0.641" dependencies = [ "pup-cdp", "pup-core",@@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "pup-host"-version = "2.0.634"+version = "2.0.641" dependencies = [ "serde", "serde_json",@@ -709,14 +709,14 @@ dependencies = [ [[package]] name = "pup-host-mac"-version = "2.0.634"+version = "2.0.641" dependencies = [ "pup-host", ] [[package]] name = "pup-host-win"-version = "2.0.634"+version = "2.0.641" dependencies = [ "pup-core", "pup-host",@@ -725,7 +725,7 @@ dependencies = [ [[package]] name = "pup-identity"-version = "2.0.634"+version = "2.0.641" dependencies = [ "fontdue", "image",@@ -735,7 +735,7 @@ dependencies = [ [[package]] name = "pup-session"-version = "2.0.634"+version = "2.0.641" dependencies = [ "pup-core", "serde",@@ -745,7 +745,7 @@ dependencies = [ [[package]] name = "pup-vault"-version = "2.0.634"+version = "2.0.641" dependencies = [ "pup-core", "pup-host",@@ -756,7 +756,7 @@ dependencies = [ [[package]] name = "pup-verbs"-version = "2.0.634"+version = "2.0.641" dependencies = [ "base64", "png 0.17.16",
rust/Cargo.toml+1−1@@ -4,7 +4,7 @@ members = ["core", "host", "host-win", "host-mac", "cdp", "browser", "session", # One version for the whole bridge, in lockstep with src/BRIDGE_VERSION (tools/ship.sh bumps both). [workspace.package]-version = "2.0.634"+version = "2.0.641" edition = "2021" license = "MIT"
rust/core/src/lib.rs+6−2@@ -211,8 +211,12 @@ pub fn set_log_file(path: std::path::PathBuf) { } pub fn log_file() -> Option<std::path::PathBuf> { LOG_FILE.get().and_then(|m| m.lock().unwrap().clone()) } +static LAST_LOG: std::sync::Mutex<String> = std::sync::Mutex::new(String::new());+/// The most recent log line (2.0.636): lets a watcher say what pup had just done when something moved.+pub fn last_log() -> String { LAST_LOG.lock().map(|l| l.clone()).unwrap_or_default() } pub fn log(line: impl AsRef<str>) { let line = line.as_ref();+ if !line.starts_with("[zwatch]") { if let Ok(mut l) = LAST_LOG.lock() { *l = line.chars().take(120).collect(); } } println!("{line}"); let Some(path) = log_file() else { return }; use std::io::Write;@@ -224,7 +228,7 @@ pub fn now_ms() -> u64 { std::time::SystemTime::now().duration_since(std::time:: pub fn iso_now() -> String { // No chrono dependency: ISO-8601 UTC from the unix clock.- let secs = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0);+ let dur = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default(); let secs = dur.as_secs(); let ms = dur.subsec_millis(); let days = secs / 86400; let rem = secs % 86400; let (h, m, s) = (rem / 3600, (rem % 3600) / 60, rem % 60); // civil-from-days (Howard Hinnant)@@ -233,7 +237,7 @@ pub fn iso_now() -> String { let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1; let mo = if mp < 10 { mp + 3 } else { mp - 9 }; let y = if mo <= 2 { y + 1 } else { y };- format!("{y:04}-{mo:02}-{d:02}T{h:02}:{m:02}:{s:02}.000Z")+ format!("{y:04}-{mo:02}-{d:02}T{h:02}:{m:02}:{s:02}.{ms:03}Z") } /// Background-loop cadence stats (Node: _loopStats / loopReport) for pup_status.loops.
rust/host-win/src/lib.rs+53−3@@ -122,9 +122,56 @@ mod imp { ok } }+ fn park_on_first_show(&self, hwnd: i64, timeout_ms: u64) -> Option<u64> {+ extern "system" { fn IsWindowVisible(hwnd: HWND) -> i32; fn GetForegroundWindow() -> HWND; }+ let t0 = std::time::Instant::now();+ unsafe {+ while IsWindowVisible(hwnd as HWND) == 0 {+ if t0.elapsed().as_millis() as u64 > timeout_ms { return None; }+ std::thread::sleep(std::time::Duration::from_micros(100));+ }+ let shown = t0.elapsed().as_millis() as u64;+ SetWindowPos(hwnd as HWND, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);+ let t1 = std::time::Instant::now();+ // 2.0.641: Chrome re-tops the window during its first frames (z-watch: a 22 ms episode inside the+ // 1 ms-cadence hold). Spin without sleeping for the first 100 ms so the gap between Chrome's+ // re-top and pup's re-bottom is microseconds, under one composed frame; then 1 ms until 300 ms.+ while t1.elapsed().as_millis() < 300 {+ if GetForegroundWindow() as i64 != hwnd { SetWindowPos(hwnd as HWND, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER); }+ if t1.elapsed().as_millis() < 100 { std::thread::yield_now(); } else { std::thread::sleep(std::time::Duration::from_millis(1)); }+ }+ Some(shown)+ }+ }+ fn stop_flash(&self, hwnd: i64) -> bool {+ #[repr(C)] struct FLASHWINFO { cb_size: u32, hwnd: HWND, flags: u32, count: u32, timeout: u32 }+ extern "system" { fn FlashWindowEx(p: *const FLASHWINFO) -> i32; }+ let f = FLASHWINFO { cb_size: std::mem::size_of::<FLASHWINFO>() as u32, hwnd: hwnd as HWND, flags: 0 /* FLASHW_STOP */, count: 0, timeout: 0 };+ unsafe { FlashWindowEx(&f); true }+ } fn set_visible_no_activate(&self, hwnd: i64, visible: bool) -> bool {- extern "system" { fn ShowWindow(hwnd: HWND, ncmd: i32) -> i32; }- unsafe { ShowWindow(hwnd as HWND, if visible { 4 /* SW_SHOWNOACTIVATE */ } else { 0 /* SW_HIDE */ }); true }+ extern "system" { fn ShowWindow(hwnd: HWND, ncmd: i32) -> i32; fn GetWindow(hwnd: HWND, cmd: u32) -> HWND; fn IsWindow(hwnd: HWND) -> i32; }+ // 2.0.636: a hidden window that is re-shown, even with SW_SHOWNOACTIVATE, comes back at the TOP of+ // its band: the taskbar-button rebuild (hide + re-show) put a background pup window over the user's+ // work for a sample (measured on the conference-room box, 2026-09-19, 01:07:56). Remember the window+ // that was directly above it at hide time and re-insert under that one on show; if that window is+ // gone, the bottom. Never the top, never activated.+ static PREV: std::sync::Mutex<Vec<(i64, i64)>> = std::sync::Mutex::new(Vec::new());+ const GW_HWNDPREV: u32 = 3;+ unsafe {+ if !visible {+ let prev = GetWindow(hwnd as HWND, GW_HWNDPREV) as i64;+ let mut m = PREV.lock().unwrap_or_else(|e| e.into_inner());+ m.retain(|(h, _)| *h != hwnd); m.push((hwnd, prev));+ ShowWindow(hwnd as HWND, 0 /* SW_HIDE */); return true;+ }+ let prev = { let mut m = PREV.lock().unwrap_or_else(|e| e.into_inner()); let p = m.iter().find(|(h, _)| *h == hwnd).map(|(_, p)| *p); m.retain(|(h, _)| *h != hwnd); p };+ let after: HWND = match prev { Some(p) if p != 0 && IsWindow(p as HWND) != 0 => p as HWND, _ => HWND_BOTTOM };+ SetWindowPos(hwnd as HWND, after, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);+ ShowWindow(hwnd as HWND, 4 /* SW_SHOWNOACTIVATE */);+ SetWindowPos(hwnd as HWND, after, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);+ true+ } } fn show_at_bottom_no_activate(&self, hwnd: i64) -> bool { extern "system" { fn ShowWindow(hwnd: HWND, ncmd: i32) -> i32; }@@ -134,8 +181,11 @@ mod imp { // (chrome.windows.create state:minimized), and both restore ON TOP if you show them first. // Bottom it while it is still not on screen, restore it without activating, then bottom it // once more in case the restore re-ordered it. The user never sees a frame of it in front.+ extern "system" { fn IsWindowVisible(hwnd: HWND) -> i32; fn IsIconic(hwnd: HWND) -> i32; } SetWindowPos(hwnd as HWND, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);- ShowWindow(hwnd as HWND, 4 /* SW_SHOWNOACTIVATE */);+ // 2.0.640: ShowWindow(SW_SHOWNOACTIVATE) on a window that is ALREADY visible and not minimized+ // re-raises it (z-watch: 19 ms on top on every birth, 2.0.639). Only show what needs showing.+ if IsWindowVisible(hwnd as HWND) == 0 || IsIconic(hwnd as HWND) != 0 { ShowWindow(hwnd as HWND, 4 /* SW_SHOWNOACTIVATE */); } SetWindowPos(hwnd as HWND, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW) != 0 } }
rust/host/src/lib.rs+10@@ -62,6 +62,16 @@ pub trait HostWindows: Send + Sync { /// Move/resize without activation or z-order change. fn set_bounds_no_activate(&self, hwnd: i64, left: i32, top: i32, width: i32, height: i32) -> bool; fn foreground(&self) -> i64;+ /// 2.0.637: stop a taskbar flash / clear the attention tint NATIVELY (FlashWindowEx FLASHW_STOP). Never+ /// activates or re-orders anything. Replaces ab's desktop_clear_window_attention on pup's own paths, which+ /// RAISES and briefly ACTIVATES the window (adom-bridge#199; z-watch measured 40-80 ms of the newborn as the+ /// foreground window on every birth).+ fn stop_flash(&self, hwnd: i64) -> bool { let _ = hwnd; false }+ /// 2.0.640: spin (sub-millisecond) until `hwnd` becomes visible, order it to the bottom in that same+ /// instant, and keep it there for the first 300 ms unless the user has made it the foreground. Returns+ /// the ms until the first show, None if it never showed within `timeout_ms`. A per-poll EnumWindows was+ /// 22 ms behind Chrome's first show (z-watch, 2.0.639); the compositor paints a frame in 16.+ fn park_on_first_show(&self, hwnd: i64, timeout_ms: u64) -> Option<u64> { let _ = (hwnd, timeout_ms); None } /// Milliseconds since the last keyboard/mouse input (GetLastInputInfo); None where unknown. fn idle_ms(&self) -> Option<u64> { None } /// The primary monitor's work area (x, y, w, h), excluding the taskbar.
rust/verbs/src/activity.rs+12@@ -130,6 +130,18 @@ pub fn clear_attention(ctx: &Ctx, hwnd: i64, caller: &Caller) -> String { /// The birth signal: `flash` = finite four-count orange flash (never forever); `progress` = the /// icon-apply bar (started by the caller) plus the attention-tint clear; `off` = only the clear.+/// 2.0.637: clear the attention tint the open/navigate flash leaves, WITHOUT ab. ab's clear verb raises and+/// briefly activates the window (adom-bridge#199): the z-watch saw every newborn become the FOREGROUND window+/// for 40-80 ms about a second after the jump list committed, which the foreground gate caught one run in three.+/// Let the pulses show, then stop the flash natively; a window the user has foregrounded is left alone.+pub fn clear_attention_later(ctx: &Ctx, hwnd: i64) {+ if !cfg!(windows) { return; }+ let Some(a) = ctx.arc() else { return };+ std::thread::Builder::new().name("flash-stop".into()).spawn(move || {+ std::thread::sleep(Duration::from_millis(2500));+ if a.host.foreground() != hwnd { a.host.stop_flash(hwnd); }+ }).ok();+} pub fn on_open(ctx: &Ctx, sid: &str, hwnd: i64, caller: &Caller) { if !cfg!(windows) { return; } if mode(ctx) == "flash" && !minimal_touch(ctx) { let _ = ctx.ab.call("desktop_flash_window", json!({ "hwnd": hwnd, "mode": "count", "count": 4 }), caller, Duration::from_secs(6)); ctx.sessions.with_mut(sid, |x| x.flash_count += 1); }
rust/verbs/src/extdrive.rs+1−1@@ -636,7 +636,7 @@ fn with_lane_progress<T>(ctx: &Ctx, work: impl FnOnce() -> T) -> T { /// since 2.0.568). The in-page JS composer this used to carry is gone. fn dress_after(ctx: &Ctx, caller: &Caller) { let Some(hwnd) = lane_hwnd(ctx) else { return };- let _ = crate::activity::clear_attention(ctx, hwnd, caller);+ crate::activity::clear_attention_later(ctx, hwnd); // 2.0.637: never ab's clear here (it raises + activates) if ctx.settings.get("overlayBadges", "on") == "off" { return; } for s in ctx.sessions.all().into_iter().filter(|s| crate::transport::is_ext(s) && s.hwnd.is_some()) { crate::identity::overlay(ctx, &s.id, caller); } }
rust/verbs/src/lib.rs+60−6@@ -1487,7 +1487,19 @@ fn birth_watch(ctx: &Ctx, caller: &Caller, sid: &str, plan_name: &str, url: &str stamped = a.host.stamp_identity(w.hwnd, app_id, display, &icon.to_string_lossy(), &identity::relaunch_for(&sid2)); t_stamp = t_w.elapsed().as_millis(); }+ // 2.0.639: PARK WHILE INVISIBLE. The z-watch (2.0.638) showed Chrome's first show putting every newborn+ // on top for 140-250 ms until the park that waited for the token bind. The watcher holds the handle+ // ~150 ms before that first show; bottom it now, so the first frame Chrome draws is already behind.+ let mut pre_parked = false;+ if !w.visible { pre_parked = a.host.send_to_bottom_no_activate(w.hwnd); } let mut t_vis = if w.visible { Some(t_found) } else { None };+ if pre_parked {+ // 2.0.640: native spin, bottomed in the same instant it shows, held 300 ms (see park_on_first_show)+ match a.host.park_on_first_show(w.hwnd, 6000) {+ Some(ms) => { t_vis = Some(t_w.elapsed().as_millis()); pup_core::log(format!("[window] \"{sid2}\" pre-parked while invisible, shown {ms}ms later and bottomed in that instant, held for 300 ms")); }+ None => { pup_core::log(format!("[window] \"{sid2}\" pre-parked while invisible, never shown within 6 s")); }+ }+ } while t_vis.is_none() && t_w.elapsed() < Duration::from_secs(6) { if a.host.frame_windows_all(Some("Chrome_WidgetWin_1")).iter().any(|x| x.hwnd == w.hwnd && x.visible) { t_vis = Some(t_w.elapsed().as_millis()); } else { std::thread::sleep(Duration::from_millis(1)); } } let _ = tx.send((w.hwnd, w.visible, stamped, t_found, t_stamp, t_vis)); }).ok();@@ -1506,6 +1518,31 @@ fn birth_report(watcher: Option<BirthRx>, sid: &str) -> Option<(i64, bool)> { Some((h, stamped)) } +/// 2.0.638 instrumentation (2.0.636 watched only AFTER the park and saw nothing once ab's clear was gone, while+/// the gate still caught the NEWBORN on top ~1 s after the open began, sometimes on its birth page). This watches+/// from BEFORE the extension creates the window: whenever the top visible window is a lane Chrome window that did+/// not exist before this birth, log when, its title, whether it is the foreground, and the last thing pup logged.+fn spawn_zwatch(ctx: &Ctx, sid: &str, before: std::collections::HashSet<i64>) {+ let Some(a) = ctx.arc() else { return };+ let sid2 = sid.to_string(); let t0 = Instant::now();+ std::thread::Builder::new().name("zwatch".into()).spawn(move || {+ let mut on_top: Option<(i64, u128)> = None;+ while t0.elapsed() < Duration::from_millis(12000) {+ let pids = crate::extdrive_pids();+ let top = a.host.frame_windows_all(None).into_iter()+ .find(|w| w.visible && !w.title.is_empty() && w.rect.0 > -30000 && w.rect.2 > w.rect.0 + 50+ && !matches!(w.class.as_str(), "Windows.UI.Core.CoreWindow" | "Xaml_WindowedPopupClass" | "Shell_TrayWnd" | "Shell_SecondaryTrayWnd" | "Progman" | "WorkerW" | "TextInputHost" | "ApplicationFrameWindow" | "Windows.UI.Composition.DesktopWindowContentBridge"));+ let now = t0.elapsed().as_millis();+ let ours = top.as_ref().filter(|w| w.class == "Chrome_WidgetWin_1" && pids.contains(&w.pid) && !before.contains(&w.hwnd));+ match (ours, on_top) {+ (Some(w), None) => { on_top = Some((w.hwnd, now)); pup_core::log(format!("[zwatch] \"{sid2}\" lane window {} ON TOP at +{now}ms title=\"{}\" foreground={} last: {}", w.hwnd, w.title.chars().take(40).collect::<String>(), a.host.foreground() == w.hwnd, pup_core::last_log())); }+ (None, Some((h, t))) => { on_top = None; pup_core::log(format!("[zwatch] \"{sid2}\" lane window {h} left the top at +{now}ms after {}ms, last: {}", now - t, pup_core::last_log())); }+ _ => {}+ }+ std::thread::sleep(Duration::from_millis(15));+ }+ }).ok();+} pub fn open_window_ext(ctx: &Ctx, caller: &Caller, args: &Value, sid: &str, owner: Option<&str>, url: &str) -> Value { if let Some(s) = ctx.sessions.get(sid) { if transport::is_ext(&s) {@@ -1519,11 +1556,6 @@ pub fn open_window_ext(ctx: &Ctx, caller: &Caller, args: &Value, sid: &str, owne let mut birth_move_ms: Option<i64> = None; // 2.0.621: R1's on-screen move, measured by the extension; set by whichever open arm runs let mut birth_token = String::new(); // 2.0.630: the birth page's title token, the deterministic handle let birth_guard = BIRTH_LOCK.lock().unwrap_or_else(|e| e.into_inner());- let taken = taken_hwnds(ctx, sid);- let before: std::collections::HashSet<i64> = ctx.host.frame_windows_all(Some("Chrome_WidgetWin_1")).into_iter().map(|w| w.hwnd).collect();- // the same birth watcher the CDP lane uses: identity on the window BEFORE Chrome shows it- let plan_name = owner.map(|o| o.to_string()).unwrap_or_else(|| sid.to_string());- let (plan, _early_ok, watcher) = birth_watch(ctx, caller, sid, &plan_name, url, &before, &taken, true); let cold = !extdrive::alive(ctx); if cold { // 2.0.545: launch on about:blank, apply the consumer identity (wire rule + JS shim), and only then go@@ -1555,6 +1587,16 @@ pub fn open_window_ext(ctx: &Ctx, caller: &Caller, args: &Value, sid: &str, owne } if !extdrive::alive(ctx) { return err("ext_lane_start_failed", "the extension-driven browser did not start (two launches, no worker poll within 25 s each)", Some("pup_readiness shows the browser state; a Chrome for Testing still holding the profile is the usual cause. pup_close {} then open again.")); } }+ // 2.0.635: arm the birth watcher HERE, after a cold launch, not before it. The watcher lives 4 s; armed+ // before the launch it expired while Chrome was still starting, so a cold birth never got the early native+ // stamp and wore the stock Chrome-for-Testing icon until the deferred navigate finished (measured on+ // John's laptop, 2026-09-19: 12 s of the wrong icon; warm births were stamped before their first show).+ let taken = taken_hwnds(ctx, sid);+ let before: std::collections::HashSet<i64> = ctx.host.frame_windows_all(Some("Chrome_WidgetWin_1")).into_iter().map(|w| w.hwnd).collect();+ // the same birth watcher the CDP lane uses: identity on the window BEFORE Chrome shows it+ let plan_name = owner.map(|o| o.to_string()).unwrap_or_else(|| sid.to_string());+ let (plan, _early_ok, watcher) = birth_watch(ctx, caller, sid, &plan_name, url, &before, &taken, true);+ spawn_zwatch(ctx, sid, before.clone()); // the tab and Chrome window for this session: the launch URL's tab on a cold start, a NEW window otherwise let (chrome_tab, chrome_win, got_url, got_title) = if cold { extdrive::apply_consumer_brands(ctx);@@ -1620,6 +1662,18 @@ pub fn open_window_ext(ctx: &Ctx, caller: &Caller, args: &Value, sid: &str, owne (None, Some((wh, stamped))) => { s.hwnd = Some(wh); early_stamped = stamped; pup_core::log(format!("[window] \"{sid}\" birth token {birth_token} not found within 3 s; bound the set-diff handle {wh}")); } (None, None) => { if let Some(h) = extdrive::lane_hwnd_for(ctx, &s) { s.hwnd = Some(h); } } }+ // 2.0.635 backstop: if the watcher missed the invisible moment, stamp NOW, at the bind, before the first+ // show below. The button exists already (a minimized window has one), but the stock icon then lasts the+ // milliseconds to this call instead of the seconds to the deferred navigate and the full identity pass.+ if !early_stamped {+ if let (Some(h), Some((app_id, display, icon))) = (s.hwnd, plan.as_ref()) {+ let t_s = Instant::now();+ if ctx.host.stamp_identity(h, app_id, display, &icon.to_string_lossy(), &identity::relaunch_for(sid)) {+ early_stamped = true;+ pup_core::log(format!("[window] \"{sid}\" identity stamped at the bind in {}ms (the birth watcher missed the invisible moment)", t_s.elapsed().as_millis()));+ }+ }+ } // 2.0.604 (John, 2026-09-17: "why do you keep shoving windows in front of me without warning me? // ... you shoved the window in my face while i was busy typing in another window"). Chrome creates the // window unfocused but at z1, ON TOP of the user's work, and the park used to run AFTER the deferred@@ -1697,7 +1751,7 @@ pub fn open_window_ext(ctx: &Ctx, caller: &Caller, args: &Value, sid: &str, owne // the early park above already put it behind. Re-assert ONLY if it is still on top and the user has // not taken it: a blind re-assert here would undo a click the human made in the meantime. if ctx.host.foreground() != h { let _ = ctx.host.send_to_bottom_no_activate(h); }- activity::on_open(ctx, sid, h, caller); let _ = activity::clear_attention(ctx, h, caller);+ activity::on_open(ctx, sid, h, caller); activity::clear_attention_later(ctx, h); // 2.0.637: never ab's clear here (it raises + activates) } if let Ok(lane) = transport::lane_for(ctx, &s) { let mut s2 = s.clone(); transport::refresh_all(&lane, &mut s2); ctx.sessions.insert(s2.clone()); ctx.sessions.save_file(&s2); s = s2; } // 2.0.621: no focus handback anywhere. pup-user-foreground deleted it in August because it foregrounded
src/extension/manifest.json+1−1@@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Adom Pup",- "version": "0.4.14",+ "version": "0.4.15", "description": "Toolbar for Adom Pup browser windows: annotate, snip, view toggle, dashboard, owner info. Thin trigger surface - the pup bridge is the engine.", "action": { "default_popup": "popup.html",
src/extension/worker.js+5−1@@ -264,7 +264,11 @@ async function pupExtPollOnce() { // real window came up unparked (measured: 4.5 s on top on every cold start). The caller navigates to // the real URL after the window is bound and parked; `url` here only says where it will go. const token = Math.random().toString(36).slice(2, 10) + Date.now().toString(36);- const w = await chrome.windows.create({ url: chrome.runtime.getURL('birth.html') + '?token=' + token, focused: false, state: 'minimized' });+ // 0.4.15: NORMAL, unfocused. The minimized birth still showed the window ON TOP for 140-250 ms before its+ // minimize (z-watch, 2.0.638) and surfaced again for ~20 ms when pup restored it. pup now parks the window+ // while it is still invisible (the birth watcher catches it ~150 ms before the first show), so there is no+ // reason to minimize and no restore to surface.+ const w = await chrome.windows.create({ url: chrome.runtime.getURL('birth.html') + '?token=' + token, focused: false, state: 'normal' }); const birthMoveMs = 0; const tab = w.tabs && w.tabs[0]; if (tab) await pupExtWaitComplete(tab.id, 8000);