Adom Desktop - Puppeteer Bridge
Public Made by Adomby adom
The Adom Desktop Puppeteer (pup) bridge — drive a real headful browser on the user's desktop from the cloud: open/screenshot/eval/record. Cold-start drives the browser ALREADY on the machine (installed Chrome, else Microsoft Edge — every Windows PC ships with Edge) with a fresh isolated profile, so the common case needs NO download; Chrome for Testing is fetched only as a last resort. Two artifacts: the Releases zip is the bridge runtime (also bundled in Adom Desktop); the pkg installs the container-side Claude skills.
Changelog — Adom Desktop Puppeteer Bridge
1.9.43 — 2026-07-20 — Why some icons had overlays and others did not, plus stacked session titles
John: "why do you sometimes have overlays on your icons and sometimes not?" It was never a rule, it was a race and two silent failures. Measured on the live windows:
- PLACEHOLDER FAVICONS. example.com declares . That fetched "successfully" as an empty blob, so the overlay was abandoned and /favicon.ico was never tried. Junk hrefs are now skipped before fetching.
- NO RETRY, EVER. If the favicon had not loaded when the overlay ran, applyAppOverlay gave up silently and nothing re-ran it, so that window had no overlay for its whole life. It now retries once shortly after, logs when it finds nothing, and takes a second pass after navigation since many sites inject their icon link after first paint.
- STACKED SESSION TITLES (the reason an adopted window kept the default icon). retagSessionTitle stripped only a TRAILING "(session: ...)", but the old session's MutationObserver had already re-appended its own, so the strip missed and we produced "BMV080 - Adom Wiki (session: thr) (session: thr-split1-mkh7)". The window still answered to the PARENT's suffix, collided with it, and its branding landed unreliably. Now every occurrence is stripped, and all title observers share one named handle so an adopted tab can disconnect the previous session's observer instead of fighting it forever.
1.9.42 — 2026-07-20 — Stop the "event-driven" drag check from behaving like a poll
Chasing a suspected false-split, the detector proved CORRECT (an untouched 3-tab control window was checked repeatedly and every tab reported the same window id, no split; the earlier adom-tsci splits were real drags). But the audit exposed a worse problem: 1360 window-opened events across 10 drains in about a minute. A busy Windows desktop fires that event for tooltips, menus and task switching, and every drain was running a per-tab CDP check across every multi-tab session, which is exactly the wasteful polling this design was supposed to avoid.
Two cheap guards: skip entirely when no session has 2+ tabs (a single-tab window cannot be split), and throttle the check to at most once every 1.5s regardless of event volume. Detection latency is unchanged in practice since a real drag is followed by more events.
1.9.41 — 2026-07-20 — Self-audit of the live taskbar: two icon bugs found and fixed
John: "look at your taskbar icons and audit yourself." Doing that on the real taskbar found two defects that no amount of reasoning had surfaced:
- An Adom app was wearing the WEB globe. classifyUrl demanded BOTH a *.adom.cloud host AND a /proxy/ path segment, so an app served at a bare slug host (project-manager-xxxx.adom.cloud) fell through to "web". Any *.adom.cloud host is now an Adom app.
- A long-lived window still wore the plain DEFAULT tile with no category at all. Windows that predate the category work, or that were recovered across a bridge restart, were never re-evaluated. brandSweep now clears each session's cached category and re-runs the full refresh, so every live window converges on a correct category icon.
Also corrected a claim I made to John: the extra split sessions I could not explain came from adom-tsci, a different window than the hybrid one he was testing, not from his drag.
1.9.40 — 2026-07-20 — Adopted windows are re-tagged, so they stop being invisible and colliding
John dragged a tab out of a 3-category window and got sloppy results: one new window kept the bare Chrome icon, and a window wore the favicon of an app it had nothing to do with. One root cause behind both, and it was careless: an ADOPTED tab kept the title of the session it came FROM. All three windows still read "(session: hybrid)".
Consequences, both observed live: AD could never resolve the new window by "(session: )", so brandPupWindow/overlay/jump-list refresh failed outright ("could not refresh taskbar presence") and the window kept the default Chrome icon; and because several windows answered to the SAME suffix, any title-based operation hit an arbitrary one, which is how a shotlog favicon landed on a window that was not shotlog.
Fix: retagSessionTitle() strips any existing pup suffix, installs the new session's suffix, and re-arms the MutationObserver. It runs on the adopted tab BEFORE any branding (everything downstream resolves the window by that suffix), and re-asserts the source window's own tag since its tab set changed underneath it.
1.9.39 — 2026-07-20 — The dark-teal window grows 30 percent, and the glyph keeps its margin
I had misread the last request: I grew the line drawing INTO the window's margins, which is exactly the bad graphic design John was objecting to. Correct reading: the dark-teal window itself grows 30 percent (156 x 133 on the 244 tile, up from 120 x 102), and the glyph scales with it while keeping a real margin inside, 16 percent on every side. Nothing touches an edge. Also fixed the stroke weight, which the rescale had thinned to a hairline (it is now computed to land at the same visual weight as the original regardless of the glyph box size).
Rule to keep: every drawing gets margins, at every nesting level. Tile margin around the window, window margin around the glyph.
1.9.38 — 2026-07-20 — Drop the toolbar line, glyphs much bigger, real Adom mark for apps
John, still squinting at the taskbar: drop the line separating the browser chrome from the content (it collided with the glyph and earned nothing), and make the inner glyph bigger again. Done: the window body is a plain solid rounded rect and the category glyph is roughly 1.7x its original size, filling the body. The tile margins that match the Adom favicon are unchanged.
Also fixed the app icon: my hand-drawn Adom mark rendered as a blob, so the icon now embeds the REAL two-lobe mark taken straight from the shipped pup tile art, scaled to fit the window body.
1.9.37 — 2026-07-20 — Inner category glyphs 30 percent larger
John, after living with them: the inner glyph was too small to read at taskbar size, but the frame margins were right because they match the Adom favicon. So the CONTENT glyph is scaled 1.3x about the content-area centre, growing into the window's empty space; the browser frame, its toolbar line and the tile margins are untouched. Much more legible at 24px, which is the size Windows actually paints. README icon art regenerated to match.
1.9.36 — 2026-07-20 — Never leave a stale taskbar overlay after a drag-out
The drag-out adoption worked (John dragged a tab out and the new window took pup's identity), but the window it LEFT kept showing the dragged site's favicon in its overlay even though that tab was gone. Cause: the refresh was fire-and-forget, and a window whose tab set just changed (or a window just created) is not findable by title for a beat, so the refresh silently no-oped.
New refreshWindowChrome(session, id) refreshes a window's whole taskbar presence together (identity icon, favicon overlay, jump list) and RETRIES with backoff until the shell can see the window, then logs loudly if it never can. Both sides of a drag-out use it: the adopted window and the source window. The source also clears its cached category and overlay first, so a window that went from mixed back to plain wiki actually repaints instead of short-circuiting on "same category".
John's rule, recorded: overlays must always be as up to date as they can be. A stale favicon is a lie about what the window contains.
1.9.35 — 2026-07-20 — THE drag-out bug: AD answers in two shapes and pup only understood one
Root cause found. AD's /command replies in TWO shapes depending on path and version: the relay wraps the payload as {output:""}, while the loopback direct API can return the payload BARE. pup assumed the wrapped shape everywhere, so a bare payload silently collapsed to "" and every field read as undefined. The drag-out drain therefore saw ZERO events while an identical manual call saw dozens, and it failed silently because zero events is a legitimate state.
Fixed with one defensive parser (adPayload) that accepts either shape, used by the watch lookup and the event drain. This class of bug has now bitten twice (desktop_caption, desktop_ui_events), so the parser is the single place to fix it rather than each call site.
1.9.34 — 2026-07-20 — Drag-out diagnostics actually deployed, plus a UIA watch leak fix
Two things, one of them a process failure worth naming. The 1.9.33 diagnostic build never reached
the desktop: the manifest was updated LOCALLY but never pushed, so the manifest URL still served
1.9.32 and bridge_install correctly reinstalled 1.9.32 while reporting "Installed". Every conclusion
drawn from that build's logs was worthless. Lesson: after release upload, ALWAYS repo push the
manifest and verify the SERVED version before installing (a bare "Installed" proves nothing).
Also fixed a real leak this hunt exposed: pup subscribed to the global window-opened UIA watch on every bridge start and never unsubscribed, accumulating watches (w1..w4 observed live). It now reuses an existing global window-opened watch when one is present, since all events land in one shared ring drained by sequence number.
Carries the drag-out diagnostics: every step of detection now logs (drain counts, per-tab window ids, adoption or the reason none happened) so a silent failure cannot hide again.
1.9.32 — 2026-07-20 — Fix: the drag-out event was filtered away by its own (empty) name
1.9.31 subscribed to window-opened correctly but filtered the events by window NAME (/Chrome|Chromium/). A window fires window-opened at CREATION, before its title exists, so the dragged-out Chrome window arrived with an EMPTY name and the one event that mattered was discarded (verified in the live ring: the needed event was seq 32, name ""). The filter is gone: any window-opened triggers the follow-up, which is cheap and idempotent anyway. Also drains on browser_list_tabs. Confirmed the underlying detection is sound: after John's drag, the dragged tab reported window origin 335,292 while its siblings shared 0,0.
1.9.31 — 2026-07-20 — Drag a tab out and it becomes its own pup window (event-driven, no polling)
John dragged a tab out of a pup window: Chrome made a NEW OS window, which carried none of pup's identity (bare Chrome-for-Testing icon), and because the dragged page keeps its "(session: )" title suffix, ONE session suddenly spanned TWO windows, making AD's title-based resolution ambiguous.
Detection is EVENT-DRIVEN, not a scan (John: scanning every second is wasteful). AD exposes a global UIA subscription, desktop_ui_watch {event:"window-opened"}, whose events land in a 500-deep ring drained with desktop_ui_events {sinceSeq}. pup subscribes once at boot and drains on activity it is already doing (the 30s health tick plus every tab verb), so there is no dedicated timer: instant while you are working, one tick when idle. Only Chrome-class window events trigger the follow-up, and the follow-up is one cheap CDP Browser.getWindowForTarget per tab of multi-tab sessions.
Policy (option A): a tab that no longer shares its session's window is ADOPTED as its own session, with its own sessionId, identity, CATEGORY icon, taskbar overlay and jump list. That keeps "one session = one window" true, keeps window titles unique, and means a dragged-out digikey tab correctly shows the globe instead of inheriting the wiki book.
1.9.30 — 2026-07-20 — Fix the raise that reported success while the window stayed hidden
Root cause of "the toggle closed my window and never brought it back". osRaiseSessionWindow resolved
the browser PID as _detachedPid || process().pid, so for a DETACHED launch (every Windows pup
browser) it ran PowerShell SetForegroundWindow against the LAUNCHER pid's MainWindowHandle, which is
a different window than the session's. PowerShell dutifully reported "focused" each time while the
real window stayed hidden and unfindable. The proven browser_raise_os_window handler resolves the pid
as process() ONLY, which is null when detached, so it SKIPS PowerShell and relies on the CDP
place+maximize+bringToFront that actually works. The helper now matches it exactly. Verified live:
the same window that ignored seven in-code raises came back instantly via the verb's path.
1.9.29 — 2026-07-20 — CRITICAL: the wiki toggle destroyed tabs and could leave NO window
John clicked the jump-list login toggle on a 5-tab research window. The window closed, the captions fired, and nothing came back. Two separate bugs, both severe:
- TAB LOSS. The toggle relaunched only the ACTIVE tab's url and closed the rest, silently destroying every other tab in the window. It now snapshots ALL tab urls plus which one was active BEFORE teardown, relaunches on the active url, restores the siblings in order, and re-activates the tab the user was on.
- NO WINDOW RETURNED. The relaunch succeeded (session alive, page at 0,0, logged in) but the window never became visible to the shell: the identity stamp logged "No visible window with title containing (session: ...)" and the user was left with nothing at all. The toggle now VERIFIES the window is findable after the raise, retries heal+raise with backoff, and if it still cannot be seen says so in an on-screen caption instead of reporting success.
A switch that cannot show its window is a failed switch. Reporting ok:true while the user stares at an empty desktop is the worst possible outcome and is what this release exists to prevent.
1.9.28 — 2026-07-20 — Category re-stamp retries, and stamp failures are loud
The 1.9.27 acid test flipped the AUMID to .mixed but the window stamp silently returned false once and the tile stayed latched (the category path stamped exactly once, unlike the auth path which retries). Stamp failures now log their verdict (response error or not-applied raw), and the category-change re-stamp retries with backoff like the auth path.
1.9.27 — 2026-07-20 — Live tile repaints via category-carrying appIds (AD's measured fix)
AD measured every repaint mechanism on Win11 (WIN11_TASKBAR_ICON.md Part 7): the taskbar tile is baked when the button is created and never re-read - WM_SETICON, DeleteTab/AddTab, SHChangeNotify, FRAMECHANGED, RedrawWindow and the TaskbarCreated broadcast all leave it unchanged. The only live repaint is making the shell create a NEW button, which a DIFFERENT appId does (~1s, no flicker). So the per-session appId now carries the category: Adom.Pup... A category flip is register-new, stamp, unregister-old; the jump list re-attaches to the new AUMID automatically. Registrations are keyed by the full appId (a Set - state is baked into the id). Session teardown captures the current appId before the session record is deleted so cleanup cannot leak. Tradeoff, by Windows design: pins bind to one AUMID, so pins do not follow category flips on per-session windows; grouped mode keeps the stable pinnable Adom.Pup.
1.9.26 — 2026-07-20 — Signed-in icon applies on the FIRST stamp (Windows latches button icons)
Proven live: ex-wikiin stamped and registered icon=wiki-in, yet its taskbar button kept the plain book. Windows latches a button's icon at window creation; later in-place re-stamps often do not repaint. Fix: a session launching on the authed wiki profile is authed-intent, so _wikiAuthed is seeded at session creation, BEFORE the first identity stamp - the first icon is already book-plus-person. The post-open auth probe still corrects the flag and tab glyph if the ~30-day session expired. Remaining known gap (AD ask): a category flip on a LIVE window (wiki to mixed by adding a tab) re-stamps correctly but the button may hold the old icon until the window recreates; needs an AD-side force-refresh of the taskbar tile.
1.9.25 — 2026-07-20 — Signed-in wiki icon is now unmistakably different (book + person)
John called it: the outline-vs-solid book distinction technically existed but read identical at taskbar size, which is the same as not existing. The signed-in wiki icon is now the book plus a SOLID PERSON BUST in the lower content area (a signed-in reader) - a genuinely different silhouette that survives 24px and 16px. Logged-out stays the plain book. Lesson recorded: a fill change alone is not a state indicator at taskbar sizes; change the silhouette.
1.9.24 — 2026-07-20 — Category icons v3: solid window body, knockout glyphs, real margins
John: the content glyph was nearly touching the frame edges (no margins, bad graphic design), so make the browser window SOLID and put the line drawings inside it. Done: the window body is solid dark teal with a teal toolbar separator, the category glyph is a teal knockout sized to about 55 percent of the content area with two-plus grid units of margin on every side. Reads clearly at 16px.
1.9.23 — 2026-07-20 — Category icons v2: browser window first, category as its content
John's design correction, and the right semantic: pup windows ARE Chrome windows, so every category icon now shares the same browser-window line drawing (frame + toolbar, the 80%) and the CATEGORY is what is inside the window (the 20%): open book = wiki public, solid book = wiki signed in, the ADOM MARK = an Adom app (the terminal glyph made no sense for apps), globe = web, two overlapping panes = mixed. Dark teal glyphs on the teal tile, 24x24 house grid, BMP frames.
1.9.22 — 2026-07-20 — Category icon updates the moment the category changes
Adding a digikey tab to a wiki window should flip its icon to "mixed" immediately, but the base icon re-stamp sat behind the 10-minute brand debounce, so category flips lagged. updateTabCountBadge (which already runs on every tab open/close/switch/navigate) now compares the window's category to the last stamped one and re-stamps at once when it changed. The debounce still suppresses redundant same-icon re-stamps.
1.9.21 — 2026-07-20 — Dark teal glyphs (family-consistent) + the white-icon disease was Explorer's cache
Two things. (1) John: the category glyphs should be DARK TEAL, not white - the original pup tile draws its Adom mark and browser glyph in #003d40 on the teal, so white broke the family. All five regenerated with #003d40 glyphs (BMP frames kept). (2) The white-taskbar-icon mystery from 1.9.19 and 1.9.20 was neither the ICO format nor pup: after the AD downgrade/upgrade thrash, EXPLORER's own icon cache was poisoned - every icon change reported success while the shell painted its white fallback, even for the old proven icon with no relaunch set. An Explorer restart rebuilds the cache. Diagnostic for the future: when stamps report applied but the button paints the generic white page, suspect the SHELL's cache, not your code - isolate by stamping the known-good icon; if that is white too, restart Explorer.
1.9.20 — 2026-07-20 — Category icons paint now: ICO frames must be BMP, not PNG
The 1.9.19 category icons applied cleanly (AD reported the stamp and registration OK) but the taskbar painted the generic white-page fallback. Root cause: the ICOs were written with PNG-compressed frames (PIL's default), and the shell path that resolves a window's RelaunchIconResource cannot parse them, so it fell back. AD's own HICON loading handles PNG frames, which is why every API response said success while the button stayed white. Regenerated all five category icons with classic BMP frames (bitmap_format="bmp"; pure-BMP verified, no PNG signatures) under new filenames per the icon-cache rule. The old hand-built adom-pup-browser3.ico that always painted is BMP-frame, which corroborates the diagnosis.
1.9.19 — 2026-07-20 — One session one window, category icons, and no emoji anywhere
Three things, in the order John asked for them.
ONE SESSION = ONE OS WINDOW. browser.newPage() opens a TAB in whatever window a profile already has, so two sessions sharing the adom-wiki-authed jar collapsed into a single window. A window title reflects only its ACTIVE tab, so AD could not resolve "the window titled (session: )" for the background one, and every per-window operation keyed on that title silently failed: icon stamp, taskbar overlay, flash, jump list. That is why the login icon would not repaint after a view toggle. Sessions now get their own window via CDP Target.createTarget {newWindow:true}, same cookie jar, separate window.
CATEGORY ICONS. A pup window's icon now says what KIND of thing it is showing. Each is the teal Adom tile carrying one monochrome #e6edf3 glyph on the 24x24 house grid, per the brand icon law (custom hand-drawn, single colour, no gradients, no shadows, no emoji): wiki open book, outline Adom wiki, public wiki-in open book, SOLID Adom wiki, signed in app window + prompt an Adom app (localhost or a cloud slug proxy URL) web globe anything else on the internet mixed stacked windows tabs spanning more than one category Solid vs hollow is the one difference that survives Windows painting the button at 24px; a padlock turned to mush there. Mixed content gets its own icon rather than picking a winner.
NO EMOJI. The brand rule is absolute ("no emoji, anywhere"), and the bridge was full of them. Tab titles now lead with a monochrome geometric TEXT glyph, U+25CF solid for signed in and U+25CB hollow for public, mirroring the icons. Jump-list titles dropped their emoji entirely (the task already carries a real icon), and captions are plain words. The title strip regex clears both the new glyphs and legacy emoji so old windows heal instead of stacking prefixes.
Also reverted the green overlay plate from 1.9.16 to 1.9.18: the category icon carries login state now, so tinting the favicon plate was a redundant and off-law second colour signal.
1.9.18 — 2026-07-20 — Fix: the green plate never painted (overlay ran before auth was known)
The 1.9.16/1.9.17 plate colour was correct but never appeared. Ordering bug in browser_open_window:
applyAppOverlay paints the overlay EARLY, and session._wikiAuthed is only assigned a few lines
LATER from the auth probe - so the plate was always composited in the logged-out colour, even for a
window opened with wikiView:"authed". (The log tell: the per-session AUMID re-registers with
icon=auth AFTER the stamps, proving the state landed late.) Now the overlay is repainted once the
auth state is recorded.
1.9.17 — 2026-07-20 — Use the Adom brand green (#3fb950), not an ad-hoc one
John caught it: the signed-in green was #16a34a, which is Tailwind's green-600, not an Adom colour.
The design system defines --green: #3fb950 and reserves it for "success only" - which is exactly
what "signed in" is - so both the overlay plate and the main-icon glyph now use that token. Nothing
else about the indicator changed. (The logged-out plate stays #0b2325, the pre-existing dark plate.)
1.9.16 — 2026-07-20 — Wiki login state = the favicon's PLATE COLOUR (John's idea, the right one)
John: "why don't you just use a different background than the background you place onto the favicon in the overlay?" Exactly right, and better than everything I tried. The overlay already composites each site favicon onto a dark rounded plate (#0b2325), so that backdrop was free real estate: dark plate = logged out / public / any non-Adom site (unchanged) GREEN plate = signed in to the Adom wiki The favicon KEEPS the slot - you still see which site the active tab is on - and the plate is a far bigger colour target at 16px than any glyph tucked into the 24px base icon. Cache key now includes the plate state, the tooltip says "Adom wiki: SIGNED IN", and a login-state flip repaints the overlay. .ico favicons (which sharp cannot decode) fall back to drawing the green plate alone rather than losing the signal.
The 1.9.14 main-icon variant (solid green browser glyph) stays as a second, redundant signal for Alt-Tab and the flyout header, where taskbar overlays are not painted.
1.9.15 — 2026-07-20 — Retry the icon re-stamp (the flip silently lost a race)
1.9.14 tracked login state correctly but the taskbar icon did not visibly change. Cause: after a view toggle the window is BRAND NEW, and AD resolves it by the "(session: )" title suffix that pup injects only once the relaunched page loads. The single immediate re-stamp fired first and AD answered "No visible window with title containing (session: ...)" - visible in the log as a [badge] FAILED with no matching [identity] stamped line. Now retried with backoff until the title exists, and the failure is logged loudly instead of passing silently.
1.9.14 — 2026-07-19 — Login state on the MAIN icon; overlay stays favicons (reverted 1.9.13)
REVERTED 1.9.13's overlay-slot padlock. John was explicit: the overlay is for FAVICONS ("i LOVE seeing those favicons in that overlay") and the login indicator belongs on the MAIN icon. That was his instruction from the start and I should not have moved it. The overlay is favicon-only again, exactly as it was in 1.9.0.
The main-icon indicator is now what he actually asked for - the lower-right browser-window glyph FILLED IN SOLID when signed in - with one deviation for legibility: it fills with signed-in GREEN rather than dark. Rendered at true 24px (what Windows actually paints), a dark fill blended into the glyph's own dark stroke; the colour flip is what survives downscaling. Geometry is identical between the two icons so they register perfectly and only the fill changes. adom-pup-browser3.ico - browser-window glyph, teal/hollow = logged OUT / non-wiki window adom-pup-wiki-authed2.ico - same glyph FILLED SOLID GREEN = signed in to the Adom wiki Also dropped the 1.9.12 padlock variant: changing SHAPE is exactly the detail that dies at 24px.
1.9.13 — 2026-07-19 — Login state moves to the OVERLAY slot (readable at real taskbar size)
Honest follow-up to 1.9.12: the base-icon corner glyph (browser outline vs padlock) works mechanically, but a full-res capture of the real taskbar showed pup buttons render at ~24px, so that corner glyph is only ~7px — too small to be the "good human indicator" John asked for. Moved the signal to the OVERLAY slot, which Windows paints ~16px bottom-right and designs for exactly this: GREEN plate + closed padlock = signed in to the Adom wiki SLATE plate + open padlock = logged out / public view Colour carries it at 16px where shape cannot. The slot is free on wiki windows because the favicon that normally rides there is the ADOM LOGO — redundant with the pup base icon, which is already Adom. NON-Adom sites (ti.com, digikey) keep their favicon, which genuinely says where you are. The 1.9.12 base-icon swap stays as a second signal for Alt-Tab and the flyout header, where overlays are not painted. Tooltip spells it out: "Adom wiki: SIGNED IN / public - logged out".
1.9.12 — 2026-07-19 — The taskbar ICON now shows Adom-wiki login state
John: "can you show an indicator in the pup icon whether the adom wiki is logged in or not? maybe that browser window icon gets filled in solid". Done, as a MAIN-ICON swap (the overlay slot stays the active tab's favicon, which is more useful): adom-pup-browser3.ico — browser-window OUTLINE glyph = logged OUT / non-wiki window adom-pup-wiki-authed1.ico — SOLID PADLOCK glyph = signed in to the Adom wiki Same art otherwise, only the lower-right corner glyph differs. Solid-vs-outline is deliberate: a mass/contrast change is what survives downscaling to 16-32px (fine shape detail does not), and the padlock carries the meaning. Rendered from SVG at 16/24/32/48/64/128/256.
Plumbing: session._wikiAuthed tracks the state and setWikiAuthedState() re-stamps the window identity AND re-registers the per-session AUMID when it flips (so Alt-Tab and the flyout header follow too). Two cache traps handled — the identity cacheKey now includes the state, and the AUMID registry is a Map of appId->state instead of a Set, because both cache by key/path and would otherwise pin the stale tile. State is set on open, on the view toggle (both directions), and on the navigate auth re-check, so a window that logs in mid-session updates its icon on its own.
1.9.11 — 2026-07-19 — OS captions live, jump-list header branded, in-page toast retired
AD shipped all four asks; pup now consumes them.
- desktop_caption on the loopback API (AD 1.9.144). The earlier silent no-op was a wider AD hole
than "relay-only": ~90 inline verbs matched only their BARE names, and the CLI strips the
desktop_prefix client-side — which is exactly why a CLI test passed while pup's identical call did nothing. Both spellings work now. Fixed pup's arg name too: it isexpiresInMs, NOTduration. - In-page toast RETIRED. With a real OS surface available, pup should not inject DOM into the user's page. Captions are also the only feedback that survives the window teardown mid-switch.
- Jump-list header icon (AD 1.9.141). Not a registry fix — there is no ApplicationIcon value on
AUMID keys and the registry cannot brand the header at all. The header obeys the window's Relaunch*
TRIO, and Windows ignores RelaunchIconResource unless RelaunchCommand is ALSO set. stampPupIdentity
now passes relaunchCommand (a real
browser_focus_windowfor that session, carrying a foregroundReason since the raise gate demands one, with Windows-argv-escaped quotes). - Jump-list cmd flash (AD 1.9.143) needed no pup change (AD interposes a GUI-subsystem shim); pup re-sets jump lists at every open/navigate, so pre-1.9.143 lists heal automatically.
- Log rotation: answered, bounded. Per-spawn
<name>.logtruncates on respawn; the appended<name>.ad.logis hard-capped (trimmed to newest 64 KB at 128 KB). No janitor needed on pup's side.
1.9.10 — 2026-07-19 — In-page toast feedback (desktop_caption is relay-only) + branding race fix
Root-caused why the 1.9.9 captions never appeared: AD's loopback direct API — the path bridges use via chrome.adCommand — does NOT expose desktop_caption (confirmed live: "'desktop_caption' is not a built-in desktop verb"; the relay path has it, which is why a CLI test showed one). Filed with AD. pup now renders its own IN-PAGE toast (wikiToast — a fixed-position Adom-teal pill injected into the page, auto-fade): "Switching…" into the old page at click time, and the result ("🔓 Adom wiki: now the LOGGED-IN view (John Lauer)" / "📖 …now the PUBLIC (logged-out) view") into the relaunched page right where the user is looking. The AD caption call stays (now logged, not silent) and lights up the moment AD ships direct-API captions. Also fixed the [badge] FAILED race: the toggle branded the window before the title suffix existed — branding now waits 1.5s like every other path.
1.9.9 — 2026-07-19 — FIX: clicking the jump-list task did nothing (Windows argv quoting)
THE bug that made the whole feature look broken. The jump-list task line is parsed by the CLI's argv parser, which STRIPS unescaped double quotes — so the raw JSON args arrived as {sessionId:wiki-demo,view:authed} and the CLI died instantly with "Invalid JSON args: key must be a string at line 1 column 2" (exit 1). The user saw a 50ms cmd flash and nothing else. Reproduced and fixed on the real command line: the JSON is now wrapped in quotes with inner quotes backslash-escaped so argv delivers it intact as one argument.
Also added an on-screen AD caption (desktop_caption) so a click gives INSTANT feedback during the few-second relaunch: "🔓 Switching to the LOGGED-IN view of the Adom wiki…" on click, replaced on completion by "🔓 Adom wiki: now the LOGGED-IN view ()" / "📖 …now the PUBLIC (logged-out) view", or a prompt to do the one-time sign-in.
TESTING LESSON (baked into pup-bridge-debug): I had only tested the VERB directly via the CLI, which bypasses the exact path a click takes. The bug lived entirely in that untested segment. Test the ACTUAL entry point the user hits, not a convenient proxy for it.
1.9.8 — 2026-07-19 — Wiki toggle: snappy click, non-blocking raise, self-correcting glyph
Follow-up to 1.9.7's foreground raise, from ralph-testing it live. Three fixes: (1) the OS raise now uses async PowerShell (runPsHiddenPromise) instead of sync — the sync call blocked pup's whole event loop for up to 8s, stalling every other request. (2) browser_wiki_set_view now RESPONDS the instant the window is raised and runs branding/overlay/jump-list/auth-recheck in the BACKGROUND, so a jump-list click doesn't wait ~30s on cosmetics (that stall was likely why the switch felt like it "never worked"). (3) The tab glyph is set optimistically on switch and then self-corrects via a delayed auth re-check — fixes a logged-in window (/auth/me=200) briefly mislabeling "📖 Public" because the first auth check raced the page load.
1.9.7 — 2026-07-19 — Wiki toggle now REALLY pops to the foreground when you click it
The taskbar wiki view-toggle worked functionally (verified: it relaunches the window under the authed profile, /auth/me=200, tab title flips to "🔓 Logged in") but the user couldn't SEE it: the callback only did a CDP maximize, so the relaunched window came back behind whatever they were looking at. Now browser_wiki_set_view does a real OS SetForegroundWindow raise (extracted into a reusable osRaiseSessionWindow helper from the proven browser_raise_os_window path — CDP place + clear WS_EX_NOACTIVATE + SetForegroundWindow, with a WMI child-PID fallback) plus a taskbar flash in case Windows' foreground lock denies the raise. Clicking the jump-list task now unmistakably brings the switched window to the front. (The ~50ms cmd flash on click is AD's console-subsystem CLI launcher — tracked separately for an AD CREATE_NO_WINDOW fix.)
1.9.6 — 2026-07-19 — Kill the ~60s respawn loop (crash-poison recovery) + jump-list task icon
Two fixes. (1) THE BIG ONE: pup was respawning every ~60s for hours (AD's lifecycle log showed spawn-with-no-reap = pup self-crashing, not AD killing it). Root cause: recoverSessions() reconnects to Chromes from ~/.adom/pup-sessions/*.json on boot; a NATIVE crash during recovery (puppeteer.connect / a CDP call) kills the process before the per-session try/catch can delete the file, so the same poison file re-crashes every boot forever (in-process try/catch and the uncaughtException handler can't stop a native crash). Fix: a persistent circuit breaker — a recover-attempt marker is written BEFORE the risky ops; a file that still carries an incomplete attempt next boot is quarantined (deleted), a clean recovery clears it. One strike quarantines, so any poison file self-heals in a single crash. (2) The taskbar jump-list toggle now carries the Adom Pup icon (iconPath) instead of Windows' generic blank-document glyph.
1.9.5 — 2026-07-19 — Name the Adom wiki in the jump-list toggle (was too generic)
The taskbar toggle read "🔓 Switch to logged-in view" — generic enough to look like it'd apply to any site (John's catch). Now it leads with the wiki: "🔓 Adom wiki: switch to logged-in view" / "📖 Adom wiki: switch to public view", with descriptions that spell out logged-in = private source/drafts/owner cards vs public = what the world sees. Self-describing at a glance.
1.9.4 — 2026-07-19 — Fix the concurrent-launch wedge + liveness/recovery hints
Root-caused a live cold-start wedge: after a bridge restart, two near-simultaneous
browser_open_window calls for the SAME profile each spawned their own detached Chrome onto the
same --user-data-dir, collided on Chrome's SingletonLock, and mutually taskkilled each other's
process — so NONE came up and the bridge looked hung for minutes (verbs timed out, AD LED dim).
Fix: _launchInFlight dedup — the first launch per profile registers a Promise and concurrent
callers JOIN it instead of racing a second Chrome. Also added AI-facing recovery hints so other
threads don't relearn this: browser_readiness now explains that a status probe does NOT respawn a
down bridge (only a window/tab verb does) and warns against spamming opens during "bridge_starting";
the pup SKILL and dev-skill gained a liveness/restart/wedge troubleshooting section (the AD LED +
browser.bridgeRunning are truth; bridge_list status:"running" is not liveness).
1.9.3 — 2026-07-19 — Per-window wiki view toggle in the taskbar jump list (#208 shipped)
AD 1.9.139+ shipped #208 (desktop_set_window_jumplist). Every WIKI pup window now carries a taskbar RIGHT-CLICK task to flip between the logged-in and public view — "🔓 Switch to logged-in view" on a public window, "📖 Switch to public view" on an authed one. The jump list attaches to the per-session AUMID (registry-only, from 1.9.2), so it's a PER-WINDOW menu. Clicking it runs AD's bundled CLI → the new browser_wiki_set_view verb, which relaunches THAT window under the other cookie jar (profile = userDataDir is fixed at launch, so a view switch is a relaunch) and brings it on-screen (a user click, so foregrounding is warranted). The menu is added/cleared on open and on every navigation (drops off when a window leaves Adom); split mode only (grouped mode shares one button); self-gates on older AD. If the authed jar hasn't done the one-time SSO yet, the response flags wikiLoginNeeded and the user logs in once in that window.
1.9.2 — 2026-07-19 — Register per-session AUMIDs (#207 shipped): Adom icon in alt-tab + jumplist
AD 1.9.136+ shipped #207 (desktop_register_app_identity {shortcut:false} + unregister). pup now REGISTERS each split-mode per-session AUMID (Adom.Pup.) registry-only at window create, so Alt-Tab and the taskbar right-click jumplist header resolve the Adom icon/name instead of falling back to the chrome.exe icon. Unregistered on session close (no registry cruft, no Start Menu spam). Self-gates on older AD. The base "Adom.Pup" stays the shortcut-registered pinnable launcher; grouped mode already uses it.
1.9.1 — 2026-07-19 — Wiki mode label is now WORDS, and updates after login
John saw a bare 📖 emoji and had no idea what it meant; the logged-in window showed nothing at all (glyph checked auth at open, before login completed). Fixed: the tab title now leads with a clear WORD label — "🔓 Logged in · " or "📖 Public · <title>" — and it re-checks auth + refreshes on every Adom navigation, so a window that logs in mid-session flips to "🔓 Logged in".</p> <h2>1.9.0 — 2026-07-19 — Favicons win the overlay (John: more useful than counts)</h2> <p>The taskbar overlay now shows the ACTIVE tab's site favicon for ANY tab count — you always see what the window is showing (ti.com, mouser, …). The tab count moves to the hover tooltip ("Adom Pup — 3 tabs (icon = active tab)"). Overlay follows tab switches (updates on browser_switch_tab). Fallback to a plain count badge only when the active page serves no favicon. Grouped-mode dual counter unchanged.</p> <h2>1.8.99 — 2026-07-19 — Auth gating is Adom-wide; overlay = site favicon (1 tab) or count (2+)</h2> <ul> <li>Authed treatment now gates on ANY adom.inc host (isAdomUrl), not just wiki.adom.inc — other Adom properties get the logged-in view + hints too. NON-Adom sites (ti.com, digikey.com, adom.cloud proxies) get NOTHING: no auth routing, hints, or glyph.</li> <li>Overlay rule resolved (John's 3-vendor-tabs question): 1 tab → wear THAT site's favicon on a dark plate (you see it's ti.com); 2+ tabs → show the tab COUNT (no single favicon represents a multi-site window; hover tooltip carries the detail). No stacking — the 32px slot can't.</li> </ul> <h2>1.8.98 — 2026-07-19 — Profiles live OUTSIDE the bridge cache (persistence actually persists)</h2> <p>Root-cause fix behind "the wiki login didn't survive": PROFILES_DIR/SESSIONS_DIR were inside the bridge dir (bridges-cache\puppeteer), which AD CLOBBERS on every bridge_install — so every pup update wiped every persistent profile, including the wiki-auth session. Moved both to the stable ~/.adom zone (pup-profiles / pup-sessions) with a one-time best-effort migration. Now "log in once" genuinely lasts across updates, restarts, and reboots — for the wiki-auth vault and any user profile. (The dev-skill "persistent profile = automatic 30-day storage" claim was only true once this landed; a restart_bridge never clobbered, a bridge_install always did, which masked it.)</p> <h2>1.8.97 — 2026-07-19 — Gentle in-tab wiki mode indicator</h2> <p>The AI picks the wiki view, so the user needs to SEE which one. A leading glyph on the TAB TITLE shows it: 🔓 = logged-in view, 📖 = public/anonymous. (The native tab-strip empty space isn't reachable by the bridge; the tab title is, and it's where the user's already looking.) Idempotent</p> <ul> <li>MutationObserver so the SPA can't wipe it; doesn't fight the session-suffix injector.</li> </ul> <h2>1.8.96 — 2026-07-19 — Wiki authed: shared login across ALL threads, own window each; sharper hints</h2> <p>Proven live: a brand-new thread (own sessionId) opening wikiView:"authed" is authenticated by reusing the one-time login — each thread gets its OWN window, they share only the vault cookie jar.</p> <ul> <li>Routing fix: wikiView:"authed" now ALWAYS routes to the reserved adom-wiki-authed vault profile (previously only when the thread passed no profile of its own) — so the single login serves every thread regardless of what profile they'd otherwise use.</li> <li>Hints rewritten around the 80% case: PUBLIC is the default (logged out, what the world sees); wikiView:"authed" is the opt-in logged-in view; a thread can't self-login (it's the user's SSO) but ONE login unlocks authed for EVERY thread for ~30 days. Survives bridge restarts/reboots.</li> </ul> <h2>1.8.95 — 2026-07-19 — Suppress the browser sign-in / work-profile prompt (CfT DOES have it)</h2> <p>Correction to a load-bearing wrong assumption: Chrome for Testing shows the same "Sign in to Chromium? Set up a work profile" dialog that poisoned Aditya — it is NOT immune (proven live). pup now launches CfT with browser sign-in and sync DISABLED (--allow-browser-signin=false --disable-sync --disable-features=SigninIntercept, DiceWebSigninInterception,ProfilePicker,ForceSignInReauth,SyncPromoAfterSignin --disable-signin-promo), so the dialog never appears for any user. Site logins (e.g. the wiki session cookie) are plain cookies and unaffected. The auth-URL steering hint stays as backup. Dev skill corrected.</p> <h2>1.8.94 — 2026-07-19 — Wiki dual-view: single-window, hint-only (no two-window nag)</h2> <p>Reverted the "show BOTH views" push — double-reloading the same page in two windows is annoying for the reload-100x loop. Now ONE window is normal; hints just make the AI AWARE it can switch this window to wikiView:"authed" (or log the user in once) when a task needs the logged-in angle. The deliberate public-vs-private visibility check flips the SAME window between views.</p> <h2>1.8.93 — 2026-07-19 — Wiki dual-view: public (default) + logged-in (wikiView:"authed")</h2> <p>pup can now open a wiki.adom.inc page from EITHER viewpoint, and hints teach the AI to show BOTH so the user can verify publish visibility ("did private source leak to the public?").</p> <ul> <li>Default open = PUBLIC/anonymous (fresh profile, logged out — what the world sees).</li> <li><code>browser_open_window {url, wikiView:"authed"}</code> routes to a reserved PERSISTENT profile whose on-disk cookie jar holds the 30-day SSO session. First use returns <code>wikiLoginNeeded:true</code> with a hint to raise the window for a one-time hydrogen.adom.inc SSO; thereafter it stays logged in.</li> <li>No token→cookie shortcut exists (wiki auth is cookie-based SSO; the container CLI bearer is a separate credential) — the persistent profile IS the mechanism, so no manual cookie capture/cache/reinject is needed; the 30-day cookie lives in the profile and re-auth only fires on real expiry (a logged-out check catches it). Dual-view hints on every wiki open; the pup-adom-wiki skill documents the verify-visibility workflow.</li> </ul> <h2>1.8.92 — 2026-07-19 — App-favicon overlay: a pup window WEARS the app that drives it</h2> <p>John's standard: every Adom app already serves a favicon (<link rel="icon">, monochrome white mark per adom-ui-design; the canonical webview identity per hd-tab-icons) — so pup now auto-fetches the loaded page's favicon and wears it in the taskbar overlay, composited onto a dark rounded plate (white marks need dark backing). "That pup window with the shotlog icon IS my shotlog thread." Explicit overrides: browser_open_window {appIconB64, appName}. An app-looking URL (localhost / *.adom.cloud proxy) serving NO favicon gets a 🎨 reprimand hint teaching the standard (adom-ui-design + app-creator). When an app icon is worn it owns the overlay slot and the tab count moves to the hover tooltip; split-mode count badges remain for icon-less windows; grouped mode's dual counter is unchanged. Favicon refreshes on navigate.</p> <h2>1.8.90 — 2026-07-18 — Grouped counter: rounded-rect plates</h2> <p>Bare digits were hard to read without a background; rounded rectangles fit digits tighter than circles so the type stays large. Dark rect / white digit = windows; teal rect / dark digit = total tabs.</p> <h2>1.8.89 — 2026-07-18 — Circle-less counter: windows digit white</h2> <p>The dark windows digit vanished on the dark taskbar (the circles had been doing the contrast work). Windows count is now WHITE with a dark halo; tabs stays teal. Both bright, still color-distinguished.</p> <h2>1.8.88 — 2026-07-18 — Circle-less grouped counter (trial)</h2> <p>Grouped dual counter drops the circles: bold bare digits (windows dark, tabs teal) with a hairline contrast halo, ~25% taller than the circled variant allowed.</p> <h2>1.8.87 — 2026-07-18 — Grouped dual-counter digits enlarged again</h2> <p>Digits in the grouped top-row counters bumped another ~12% (14px single-digit windows, 13px single-digit tabs) for taskbar legibility.</p> <h2>1.8.86 — 2026-07-18 — Grouped dual counter: both numbers on the top row</h2> <p>Per John: windows (dark) and total tabs (teal) now sit side by side across the TOP of the overlay instead of diagonally.</p> <h2>1.8.85 — 2026-07-18 — Badge digits 15% bigger</h2> <p>Per John: badge numbers bumped ~15% across the static split badges and the grouped dual counter.</p> <h2>1.8.84 — 2026-07-18 — Grouped-mode dual counter (windows + total tabs); flat badges</h2> <ul> <li>Grouped mode's single button now shows TWO counters: window count (dark circle, upper-right) and total tab count (light teal circle, dark text, lower-left). Rendered at runtime via sharp from an SVG template (combos are dynamic), cached per pair; falls back to the static windows-count badge if sharp is unavailable. Single-window grouped shows a plain tab count.</li> <li>All badge circles are now FLAT — no white outline (per John), split badges regenerated.</li> </ul> <h2>1.8.83 — 2026-07-18 — Lighter tab-count badge</h2> <p>Per John: the badge circle no longer fills the whole overlay slot — ~60% size anchored upper-right, hairline translucent outline instead of the heavy white ring. (Overlay icons are passed as base64, not paths, so no icon-cache filename dance needed for these.)</p> <h2>1.8.82 — 2026-07-18 — browser_configure: taskbar grouping is now a user preference</h2> <p>John's insight from the AUMID grouping bug: BOTH behaviors are legitimate preferences. New <code>browser_configure {taskbarGrouping: "split"|"grouped"}</code> (persisted in ~/.adom/pup-settings.json, survives bridge updates):</p> <ul> <li><code>split</code> (default): per-session AUMID — one taskbar button per window, each badged with its own tab count.</li> <li><code>grouped</code>: shared AUMID — all pup windows stack under one "Adom Pup" button whose badge shows the WINDOW count. Switching re-stamps live windows immediately (brand sweep) and refreshes badges. AIs are told to ask the user before changing it.</li> </ul> <h2>1.8.81 — 2026-07-18 — Per-session AUMID: one taskbar button per window again</h2> <p>The shared Adom.Pup AUMID made Windows GROUP all pup windows into a single taskbar button — destroying per-window differentiation and rendering the tab-count overlay wrong (it showed whichever window last updated). Identity is now stamped as <code>Adom.Pup.<sessionId></code> per window: each session gets its own button with its own accurate badge. The per-window RelaunchIconResource/DisplayNameResource (already set by AD's verb) brand each unregistered per-session AUMID; the registered plain "Adom.Pup" remains the pinnable Start Menu launcher. The brand sweep re-stamps live windows on deploy, splitting existing groups immediately.</p> <h2>1.8.80 — 2026-07-18 — Tab-count overlay badge (differentiate pup windows at a glance)</h2> <p>John: an 8-tab review window and a 1-tab window are indistinguishable teal tiles. The overlay slot (freed by the identity takeover) now shows the window's TAB COUNT for ≥2 tabs — dark circle, white digit, 9+ cap, tooltip "Adom Pup — N tabs". No badge = 1 tab, so many-tab windows pop out. Updated on open_tab / close_tab / popup auto-attach; only on identity-capable AD (≥1.9.134) — older AD keeps the brand badge in that slot.</p> <h2>1.8.79 — 2026-07-18 — Brand sweep: no more mixed icon generations</h2> <p>Identity is per-HWND and stamped at birth, so windows born across rebrands/updates left the taskbar showing MIXED icon generations (plus dead-session ghosts inflating the count). New <code>brandSweep</code> re-asserts each live session's title suffix (title churn can make a window un-resolvable) and re-stamps it to the CURRENT identity — at startup (post-reattach) and after every browser_rescan. Any future icon change now propagates to already-open windows within seconds of the bridge update instead of never.</p> <h2>1.8.78 — 2026-07-18 — AD-version awareness: warn when the raise gate is bypassed</h2> <p>Every AD capability gap self-gates silently (identity → badge, bottom/force → PS fallback) EXCEPT one: AD <1.9.124 intercepts browser_raise_os_window natively, so pup's foreground gate silently does not exist there — a policy hole, not cosmetics. pup now probes the AD version at startup and, on affected boxes: logs a warning, turns the status chip yellow with an update-AD note, exposes <code>adVersion</code> + <code>raiseGateBypassed</code> on /status, and appends an upgrade advisory to open-response hints. Boxes on current AD see nothing.</p> <h2>1.8.77 — 2026-07-18 — pup owns its own provisioning: warmup ALWAYS ensures CfT</h2> <p>John: "isn't that your decision? why would you need AD to change anything?" Correct — the skip-CfT-when-Chrome-exists policy was the dead installed-browser-first doctrine living on in pup's OWN warmup(). Now warmup (runs at every bridge spawn = AD install/update + machine start) background-fetches CfT whenever it isn't cached, regardless of installed browsers — so a fresh box provisions pup's browser at INSTALL time and the first "open in pup" never waits. Low-disk guard unchanged. AD's job ends at "pup is installed"; no AD policy involved (issue #204 withdrawn — resolved pup-side; AD's v1.9.76 prewarmer is redundant for pup and the install singleton dedupes if both fire).</p> <h2>1.8.76 — 2026-07-18 — Icon glyph tucked into the corner (clear gap from the leaves)</h2> <p>1.8.75's browser glyph touched the dark leaf and blended into it. The glyph is now smaller and tucked into the lower-right corner with clear teal space on all sides. Ships as <code>adom-pup-browser3.ico</code> (new filename per the icon-cache rule); v2 art removed.</p> <h2>1.8.75 — 2026-07-18 — Identity icon simplified per John: line-art browser glyph, no white</h2> <p>1.8.74's plated badge was overdone. The icon is now the favicon with a MINIMAL line-art browser window in the leaf dark-teal (#003d40) inlaid lower-right — teal fill, dark outline + title-bar line, zero white. Ships as <code>adom-pup-browser2.ico</code> (new filename per the icon-cache rule); the v1 badge art is removed. Master SVG <code>adom-pup-browser2.svg</code>; future home remains the brand-icons wiki repo.</p> <h2>1.8.74 — 2026-07-18 — New pup identity icon: favicon + browser-window inlay</h2> <p>The Adom Pup icon is now the Adom favicon with a browser-window glyph inlaid in the lower-right (dark plate, white window frame, teal screen) — the icon itself says "this is an Adom browser window." Ships as <code>icons/adom-pup-browser.ico</code> (NEW filename per the icon-cache rule; the old adom-pup.ico is removed). Master art <code>adom-pup-browser.svg</code> lives in src/ for now; future home is the Adom brand-icons wiki repo so the mark is globally reusable beyond pup.</p> <h2>1.8.73 — 2026-07-18 — Adom Pup window identity: full icon/name takeover (AD ≥1.9.134)</h2> <p>pup windows now read "Adom Pup" with the Adom icon in the taskbar, alt-tab, and hover — no Chrome logo, no CfT "Test" tile. Built on AD 1.9.134's verbs (shipped to pup's spec after the 2026-07-18 prototype):</p> <ul> <li><strong>Self-registration at bridge start</strong> (<code>desktop_register_app_identity</code>, loopback direct-API): HKCU AUMID <code>Adom.Pup</code> + a Start Menu "Adom Pup" shortcut whose PIN launches a REAL managed pup window via adom-desktop-cli (the pinning trap, solved). The bridge owns its branding: the icon ships in this release zip (<code>icons/adom-pup.ico</code>; art changes require a NEW filename — Windows caches decoded icons by path).</li> <li><strong>Per-window stamp at CREATE</strong> (<code>desktop_set_window_identity</code> with <code>cacheKey</code>), not just at park — identity is per-HWND, and stamping late would leave the window reading "Chrome for Testing" through its whole load. Re-stamped at park/heal/tab hot paths.</li> <li><strong>Overlay badge demoted to fallback</strong>: on AD <1.9.134 (unknown verbs → self-gating) the teal overlay badge continues exactly as before; on capable AD the full identity replaces it. <code>desktop_taskbar</code> remains in use for flash/progress.</li> <li>Mechanical root (from the prototype): icon HICONs are process-owned USER objects — AD holds them in a process-lifetime cache, which is why AD-core is the only correct owner.</li> </ul> <h2>1.8.72 — 2026-07-18 — THE DEMARCATION GATE: no silent branded fallback, ever</h2> <p>1.8.71's "converge by the second session" still ran the FIRST session on branded Chrome — and the first session is exactly the Aditya session. Now an unpinned new-window open REQUIRES cached CfT: if absent, pup kicks the ~150 MB install in the background and returns the pollable chrome_for_testing_installing (poll browser_readiness, retry) instead of silently driving the user's browser. The installed Chrome/Edge is reachable ONLY by explicit browser_use pin (hints carry the identity-bleed warning). Existing/live sessions exempt. Companion AD ask filed (issue #204): flip prewarm_cft auto→always so install-time prewarm erases the first-use wait.</p> <h2>1.8.71 — 2026-07-18 — CfT-first hybrid pick order (#202) + auth-URL steering (the Aditya incident)</h2> <ul> <li><strong>Pick order flipped to: cached CfT → installed Chrome → Edge → CfT-download-last-resort.</strong> Chrome for Testing is the automation-first binary with no profile/sync/sign-in machinery — the branded browsers' identity features bleed into pup's "anonymous" windows (the documented sign-in → profile-creation → consolidation → lost-user cascade). No first-open stall anywhere: a branded-fallback launch kicks a ONE-TIME background CfT install, so every box converges to deterministic CfT by its second session. <code>browser_use</code> still pins explicitly.</li> <li><strong>Codec caveat retired with evidence:</strong> CfT (real Chrome build) decodes H.264/AAC/MP3/VP9 ("probably" via canPlayType on a live box) — the gap was legacy Chromium's, not CfT's. Only Widevine DRM playback is out of scope.</li> <li><strong>Auth-URL steering:</strong> opening/navigating to a known auth/login URL (Google, Microsoft, Okta, Auth0, GitHub, AWS, Apple, Salesforce, Atlassian, OpenAI, generic /oauth2/authorize) returns a 🔑 hint: pup is the anonymous sandbox — sign the USER in via abe (their real browser) or AD-core desktop_open_url (plain native launch) instead; continue in pup only for throwaway accounts.</li> </ul> <h2>1.8.70 — 2026-07-15 — THE FOREGROUND GATE (abe parity): reason required, recorded, auditable</h2> <p>Real incident: a thread background-opened a window then raised it 2 minutes later; the raise verb required no reason, recorded nothing, and didn't even log — forensics needed the focus-latch timestamp. pup now gates foregrounding the way abe does (dual key, one call):</p> <ul> <li><code>browser_open_window {foreground:true}</code> WITHOUT <code>foregroundReason</code> → the open succeeds but the window stays BACKGROUND, with <code>foregroundDenied:'foreground_reason_required'</code> + a hint (abe semantics: withhold the foregrounding, not the work). With a reason (min 10 chars): granted. Also accepts abe's arg name <code>userRequestedForeground</code>.</li> <li><code>browser_raise_os_window</code>/<code>browser_focus_window</code> WITHOUT <code>foregroundReason</code> → REFUSED with <code>errorCode: foreground_reason_required</code> and the contract in the hint.</li> <li>Every granted foreground is logged (<code>[foreground] ... GRANTED — reason: ...</code>), recorded in <code>lastAgentUpdate</code> (<code>FOREGROUND: <reason></code>), and surfaced as <code>lastForeground {ts, verb, reason}</code> in session status — "who put this window on my screen and why" is now one query.</li> <li>browser_describe teaches the whole contract, including return-to-background after the show-me.</li> </ul> <h2>1.8.69 — 2026-07-15 — Sized opens keep their x/y (multi-monitor placement works); window-management granted</h2> <ul> <li>The sized-open background re-assert was still the LEGACY PS park, which moves the window to 0,0 sized-to-monitor — silently clobbering the caller's x/y/w/h (every sized open snapped back to the primary monitor's origin, making second-monitor placement impossible). It is now AD <code>state:'bottom' force:false</code> — z-order only, geometry untouched, user-held windows respected.</li> <li><code>window-management</code> added to the permissive default grants so <code>getScreenDetails()</code> works inside pup pages (it used to hang forever on a CDP-suppressed permission prompt) — pup can now enumerate the REAL monitor layout from Chrome itself. Chrome sees monitors AD's <code>desktop_list_monitors</code> misses (ADOMGPU reports 1 monitor via AD but isExtended:true in Chrome; AD gap to file).</li> </ul> <h2>1.8.68 — 2026-07-15 — Park AFTER the page settles: the paint-raise happens invisibly off-screen</h2> <p>Even with 1.8.67's re-asserts, the raise-then-re-bottom left a visible blip: another thread's window (adom/wiki) briefly popped over John's work between its first paint and the re-assert. Root: the park placed the window on-screen BEFORE the navigation settled, so the paint-raise was visible. Now launchSession waits for the page to settle (load event, capped 2.5s, skipped if already complete) while the window is still OFF-SCREEN, and only then parks (bottom → place → z-confirm). The first paint's raise hits an invisible off-screen window; the window's first visible frame is full-size, z-bottomed, z-confirmed. One late re-assert (3s) remains for late paints. The open response still awaits the full verdict.</p> <h2>1.8.67 — 2026-07-15 — Re-assert bottom after the open's navigation (the post-park raise)</h2> <p>Even with 1.8.66's z-confirmed park, the w3 batch sat on top with honest 'backgrounded' verdicts: the open's FIRST cross-origin nav (file:// welcome → https) swaps renderer processes and Chromium re-shows the window at first paint — raising it — and heavy sites paint AFTER the park's z-confirm passed (the real goto runs concurrently with the park). A later same-window navigate does NOT raise (tested), and neither does the badge (tested). Fix: re-assert z-bottom right after the open's goto AND once more at the page 'load' event, plus after open_tab's new-tab first paint. All re-asserts use force:false so a window the user is actively holding foreground is never yanked. Verified: bottoming sticks once the page settles (8s watch, no spontaneous re-raise).</p> <h2>1.8.66 — 2026-07-15 — PLACEMENT DOCTRINE: verify the z-order, revert if unconfirmed</h2> <p>1.8.65's "fix" was worse: AD desktop_set_window_bounds ALSO raises the window (despite restore:false), and desktop_list_monitors reports logical px while set_window_bounds takes physical — five windows on top of the user's work AND undersized. Placement is rebuilt around distrust:</p> <ul> <li>Geometry via CDP plain bounds only (left/top/width/height, normal state), sized from the page's own screen.availWidth/Height with retries. No CDP maximize (raises), no AD bounds (raises).</li> <li>After placement: RE-BOTTOM via AD state:bottom force, then CONFIRM THE Z-ORDER via AD desktop_list_windows — pup's window must be strictly below at least one non-pup window and not topmost. Retried once; if still unconfirmed, the window is moved BACK OFF-SCREEN and the verdict is park-failed. A window that might be covering the user never stays on-screen.</li> </ul> <h2>1.8.65 — 2026-07-15 — NEVER use CDP maximize: it RAISES the window (the covering-windows breach)</h2> <p>John caught five "backgrounded" windows stacked at z-1..3 directly under his editor — covering his work. Root cause, latent since 1.8.55: CDP <code>Browser.setWindowBounds {windowState:'maximized'}</code> is ShowWindow(SW_MAXIMIZE) in Chromium, which RAISES the window to the top of the z-order (no focus steal, so every focus-based check passed while the windows sat on top). The earlier "small-then-grow" sighting was the same raise making the staging visible.</p> <ul> <li>Placement is now AD <code>desktop_set_window_bounds {restore:false}</code> (MoveWindow only: no activation, NO z-order change) sized to the primary monitor from AD <code>desktop_list_monitors</code> (cached 10 min). The window ends visually full-screen, still at the z-bottom where force-bottom put it.</li> <li>The park threads the hwnd from the bottoming step into placement (no re-lookup in the common path).</li> <li>Dev-skill dead-end added: never use CDP windowState:'maximized' for background placement.</li> </ul> <h2>1.8.64 — 2026-07-15 — Background windows appear already-maximized (no more small-then-grow)</h2> <p>John: "why when you open a new window is it smaller than my screen and then i see it grow larger?" Because the park staged the window ON-screen at 1200×800 before maximizing, and a z-bottom window is visible wherever the user's windows don't cover — so the small stage showed, then the grow. Now the park stages the window JUST past the LEFT EDGE of the primary monitor (fully invisible but adjacent, so the OS maximize targets that monitor — maximizing from deep off-screen produces garbage bounds, tested) and only then maximizes: the window's first visible frame is full-size. Position-verify and self-heal thresholds tightened from -5000 to -1000 so a window stuck at the -1310 staging spot reads as failed/heal-worthy instead of healthy.</p> <h2>1.8.63 — 2026-07-15 — force:true adopted (AD 1.9.115): the launch park is one AD call</h2> <p>AD 1.9.115 shipped the follow-up: <code>desktop_set_window_state {state:'bottom', force:true}</code> bottoms the window EVEN IF it is currently foreground (verified on ADOMGPU: sentToBack:true despite wasForeground:true). pup now always passes force:true — the launch park is a single AD call in the common case. The legacy PS fallback is RETAINED but auto-obsoletes: it only fires on AD 1.9.114 (force ignored, skip-on-foreground) or older (no state:'bottom' at all); delete it once the fleet is ≥1.9.115. It was kept deliberately — removing it while AdomLapper was still on 1.9.114 would have regressed the post-restart foreground-reassignment case on John's primary box.</p> <h2>1.8.61 — 2026-07-14 — 100% direct-API window management (AD shipped state:'bottom')</h2> <p>AD 1.9.114 shipped pup's feature request: <code>desktop_set_window_state {state:'bottom'}</code> — native z-order send-to-back, background drive, title-resolved, returns <code>{sentToBack, wasForeground}</code>. The park now uses it, deleting pup's LAST PowerShell from the open path (one AD call replaces find_window + the P/Invoke SetWindowPos script). Legacy PS fallback kept for desktops on older AD.</p> <h2>1.8.60 — 2026-07-14 — Launch park bottoms UNCONDITIONALLY (escape hatch removed)</h2> <p>"Pup window is foreground at park time" is NOT a user signal: Windows reassigns the foreground to a fresh window when the previous foreground window was just destroyed (e.g. a bridge-update restart), which force-foregrounded windows the user never clicked (nxp.com, another thread's cpconn). The user-click escape hatch is gone; a user who clicked during the ~2s launch gap clicks once more and the window stays (nothing ever re-parks).</p> <h2>1.8.59 — 2026-07-14 — HARD RULE: never move a window on-screen until z-bottom is CONFIRMED</h2> <p>Under a 3-parallel-open load, one window's hwnd lookup lagged, the z-bottom step failed, and the park moved the window on-screen anyway — an un-bottomed window lands on TOP of the user's work (the nxp.com pop; no focus stolen, but visually covering, which is just as forbidden).</p> <ul> <li>The park now retries hwnd-resolve + z-bottom up to 8× (~2.5s); if 'bottomed' is never confirmed, the window STAYS off-screen and the verdict is an honest <code>park-failed</code> — invisible beats popping.</li> <li><code>park-failed</code> windows self-heal opportunistically: any browser_status / list query retries the heal, on top of the existing reattach / open_tab / switch_tab touchpoints.</li> <li>1.8.58: badge calls check AD's real result + log <code>[badge] applied/FAILED</code>; the tab-path debounce arms only on success (one silent failure no longer means a bare icon for 10 minutes).</li> </ul> <h2>1.8.54 → 1.8.57 — 2026-07-14 — Honest click signal; OS maximize; focus-latch fix; self-heal stranded windows</h2> <ul> <li><strong>1.8.54:</strong> <code>GetForegroundWindow</code> replaces <code>document.hasFocus()</code> for user-click detection — hasFocus LIES for CDP-created windows (false-fired <code>user-foreground</code> on every open).</li> <li><strong>1.8.55:</strong> park places via CDP <code>windowState:'maximized'</code> (OS-true full size) instead of pixel math from a screen probe that could fail mid-navigation (shipped 1600×900 windows once).</li> <li><strong>1.8.56:</strong> flash <code>pending/cleared</code> focus latch fixed — the hook now also injects into the CURRENT document (<code>evaluateOnNewDocument</code> alone only fires on the NEXT navigation, so clicked windows kept reporting <code>pending</code>).</li> <li><strong>1.8.57 (the adom-shotlog fix):</strong> a window created under a build whose park failed sat stranded off-screen forever — taskbar click "showed" an invisible window, badge gone. pup now <strong>self-heals</strong>: cheap CDP bounds check at reattach / <code>open_tab</code> / <code>switch_tab</code>; only if actually off-screen, re-park + re-badge (never touches a healthy or user-foreground window). Badge also re-applies (debounced 10 min) on the tab hot path so long-lived shared windows stay badged across bridge restarts. Anonymous window CREATORS now get an owner-tip in the open response (an ownerless long-lived window has zero cross-thread protection). Enriched <code>browser_describe</code> entries for <code>open_tab</code>/<code>switch_tab</code> (return shape, updateNote, background/click contract).</li> </ul> <h2>1.8.53 — 2026-07-14 — Direct-API park: CDP + AD verbs replace the PowerShell window-hunt</h2> <p>John caught a parallel-open window (catch-2) still stuck OFF-SCREEN while the bridge claimed "backgrounded" — the PS park moved the wrong window and its focus-only self-check passed anyway. And he asked the right question: "why are you using powershell? that's heavy. why aren't you just calling direct api's?" Rebuilt the park on direct APIs:</p> <ul> <li><strong>Geometry via CDP <code>Browser.setWindowBounds</code></strong> — addresses the EXACT window by CDP windowId, so parking the wrong window is impossible. ~10ms, no process spawn.</li> <li><strong>Window lookup via AD <code>desktop_find_window</code></strong> (native, title-suffix match) — no EnumWindows PS, no pid guessing.</li> <li><strong>The park runs where the window is BORN</strong> (right after <code>newPage()</code> + welcome page in launchSession) — no wait loop at all; the <code>await</code> IS the event. <code>tryDetachedLaunch</code> just marks <code>_needsPark</code> (reused windows are never re-parked).</li> <li><strong>Verification checks POSITION</strong> (CDP <code>getWindowBounds</code>), not just focus — the exact lie catch-2 told is now detected as <code>park-failed</code>.</li> <li><strong>One residual Win32 call</strong>: z-bottom (<code>osSendHwndToBottom</code>, single P/Invoke) — CDP has no z-order and AD has no send-to-back verb yet (AD issue filed). Runs while the window is still off-screen so its latency is invisible.</li> <li><strong>Badge fires the instant the park lands</strong> (title suffix already set) instead of blind 1.5s/3.5s timers — the teal Adom favicon appears with the window, not seconds later.</li> </ul> <h2>1.8.52 — 2026-07-14 — Close the launch gap: a click during the first ~2s now shows the window</h2> <p>With 1.8.51, a taskbar click during the launch gap (window still at its off-screen coords, park PS still starting) activated an invisible off-screen window — the user saw nothing for ~2s. Now the park checks the foreground first, and the check is UNAMBIGUOUS because <code>--no-startup-window</code> means Chrome cannot raise itself: pup-window-foreground can only be the user. If the user clicked during the gap, the park places the window on-screen at the TOP full-size and leaves them focused on it (<code>user-foreground</code>); otherwise it parks to z-bottom as usual. Either way it exits — still no loops.</p> <h2>1.8.51 — 2026-07-14 — <code>--no-startup-window</code>: eliminate the focus steal at the SOURCE (no more war)</h2> <p>John proved a taskbar click within the first couple seconds still got yanked (the 1.8.50 stability watch cannot tell an early user click from a Chrome self-raise — no watch can). Fix: make the war impossible instead of fighting it better.</p> <ul> <li><strong>Background launches use <code>--no-startup-window</code>.</strong> Windows only grants foreground rights to a process at launch; with this flag the browser starts with NO window, and when pup creates the window via CDP moments later the browser is a background process — the OS itself DENIES it the foreground. No steal can happen.</li> <li><strong>Therefore: no stability watch, no timers, no re-parks — deleted.</strong> The park is now: wait for the window to exist → hand focus back if somehow taken (normally a no-op) → one SetWindowPos to z-bottom → verify → EXIT. A user click at ANY moment — 200ms in or 2 minutes in — has nothing running against it.</li> <li>Removed the duplicate post-open background re-assert (now only after an explicit sized open, where CDP setWindowBounds can re-activate; guarded by a foreground check).</li> </ul> <h2>1.8.50 — 2026-07-14 — One event-driven watchdog replaces the blind-timer park (John: "waiting 1.2s is for the birds")</h2> <p>The launch park no longer fires at guessed delays. <code>osBackgroundWatchdog</code> = ONE hidden PS process:</p> <ol> <li>WAIT for the real window to EXIST (EnumWindows @50ms, up to 4s) — react to the event, not a timer.</li> <li>The moment it exists: hand focus back (only if Chrome actually took it), THEN park on-screen at z-bottom (deactivate-first = no pop). Once.</li> <li>Stability watch: if Chrome re-grabs the foreground during startup, hand back + re-park within 50ms; exit after 500ms of calm (typically <1s total).</li> <li>Exit. Afterwards NOTHING re-parks — a user's taskbar click brings the window up full-size, forever. The open response awaits the watchdog's SELF-VERIFIED verdict, so <code>background</code> is the real outcome, never a guess mid-park.</li> </ol> <h2>1.8.49 — 2026-07-14 — Kill the input-timing heuristic (it false-fired whenever the user was typing)</h2> <p>Opening windows while John was actively using the laptop: 2 of 3 came up <code>user-foreground</code>, small (centered 80%) instead of parked full-size. Root cause: the "did the user click it?" check used <code>GetLastInputInfo</code>, 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."</p> <ul> <li><strong>Launch passes (300/700/1200ms) now ALWAYS park — no user detection.</strong> 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.</li> <li><strong>Late re-parks (post-resize) keep a simple foreground check only:</strong> after startup Chrome doesn't self-raise, so pup-is-foreground = the user took it → leave it exactly as they have it.</li> <li><strong>Removed the centered-80% "show" placement</strong> — windows are always full-monitor size; the user's own click is what brings them up (restoring exactly what Windows restores).</li> </ul> <h2>1.8.48 — 2026-07-14 — Background = on-screen z-bottom (minimize reverted: it broke screenshots)</h2> <p>1.8.47's minimize broke screenshots: a minimized window reports <code>visibilityState: hidden</code>, so <code>Page.captureScreenshot</code> hangs (~27s then errors) even with occlusion disabled. Reverted to the on-screen-occluded park (which keeps rendering → screenshots work), now correct on all the pieces:</p> <ul> <li><strong>Real window handle</strong> via <code>EnumWindows</code> (1.8.46) — no more phantom-helper-window misses.</li> <li><strong>Deactivate-then-park order</strong> (1.8.46) — hand focus back while off-screen, THEN move on-screen at z-bottom, so it never flashes on top (no pop).</li> <li><strong>Short re-park window</strong> (~1.2s: 300/700/1200ms) — just long enough to counter Chrome's startup foreground-grab. After that pup NEVER re-parks, so a user click is not fought. Within the window, <code>GetLastInputInfo</code> distinguishes a real click (→ bring on-screen + latch off further passes) from Chrome self-raising (→ re-park).</li> </ul> <h2>1.8.47 — 2026-07-14 — Background = MINIMIZE (no more re-park loop fighting the user's clicks)</h2> <p>The on-screen-z-bottom park needed a re-park retry loop to counter Chrome's startup foreground-grab, and that loop fought the user: click a window to view it and pup yanked it back. Rebuilt the background primitive around <strong>minimize</strong>, which removes the whole conflict:</p> <ul> <li><strong>Minimizing hands the foreground back to the user automatically</strong> (no foreground-lock fight, no AttachThreadInput) — no focus steal, and no visible pop (the window launched off-screen).</li> <li><strong>The restore rectangle is set on-screen</strong> (<code>SetWindowPlacement</code>), so a taskbar click restores the window centered + visible — <strong>and it STAYS</strong>, because there is NO on-screen re-park loop to yank it.</li> <li><strong>Screenshots still work</strong> because Chrome already launches with occlusion detection disabled (<code>--disable-features=CalculateNativeWinOcclusion</code> + <code>--disable-backgrounding-occluded-windows</code>), so a minimized window keeps compositing frames.</li> <li>The only late passes are safety re-minimizes that RESPECT a user restore: the instant the window is un-minimized by a real click (physical input), pup latches <code>_userForeground</code> and never touches it again.</li> <li><code>browser_lower_os_window</code> / post-resize re-park also minimize now. <code>raise</code> restores via CDP as before.</li> </ul> <h2>1.8.46 — 2026-07-14 — Resolve the REAL browser window (the background park was targeting a phantom)</h2> <p><strong>The bug behind "I click a pup window and it never shows."</strong> The background park resolved the window via <code>Get-Process(pid).MainWindowHandle</code>, which on some boxes (John's laptop) returns a <strong>hidden Chrome helper window</strong>, not the real browser window. So <code>SetWindowPos</code> moved a phantom, the real window stayed at its off-screen launch coords (<code>screenX -21333</code>), and the "backgrounded" self-check passed against the phantom — a <strong>false positive</strong>. Clicking the taskbar activated an off-screen window = nothing visible. Proven by contrast: CDP <code>Browser.setWindowBounds</code> (which <code>raise</code> uses) put the same window at <code>0,0 1708×1068</code> first try.</p> <ul> <li><strong>Resolve the real window by ENUMERATION</strong> (<code>EnumWindows</code>): the largest VISIBLE top-level <code>Chrome_WidgetWin_1</code> owned by the browser's pid (or its children). Replaces the flaky <code>MainWindowHandle</code> in <code>osBackgroundWindowByPid</code> AND <code>osClearFlashByActivate</code>. The park now actually moves the real window on-screen at z-bottom, so a taskbar click shows it — and the self-verify is honest.</li> </ul> <h2>1.8.45 — 2026-07-14 — User↔AI window balance + real flash clear + programmatic flash state</h2> <p>Three related fixes, all from John's live testing. Governing rule now written into <code>pup-bridge-dev</code> ("The user↔AI balance"): pup windows are background AI tasks that must not disturb the user, AND real windows the human must be able to click, SEE, and use normally — both at once.</p> <ul> <li><strong>A user click now actually SHOWS the window.</strong> v1.8.44 made the re-park respect a deliberate user raise, but it <em>bailed without moving the window on-screen</em> — so clicking a still-off-screen window activated an invisible window (user saw nothing). Now the user-raise path brings the window ON-SCREEN (centered ~80%, <code>SWP_SHOWWINDOW</code>, HWND_TOP) and leaves it foreground. Background↔foreground handoff.</li> <li><strong><code>browser_alert_window {stop}</code> truly clears the flash.</strong> <code>FLASHW_STOP</code> only stops the <em>pulse</em>; the Win11 attention tint persists until the window is ACTIVATED. New <code>osClearFlashByActivate</code> clears it by activating the window OFF-SCREEN (invisible → tint clears) then handing focus back — no pop, no focus steal. Reports a real <code>cleared</code> / <code>already-foreground</code>, never a fire-and-forget guess.</li> <li><strong>Programmatic flash state.</strong> <code>getSessionInfo.flash</code> = <code>none|pending|cleared</code> (from <code>_flashState</code> vs an injected <code>window.__pupFocusTs</code> focus latch) — answers "which windows are still flashing?" without a screenshot.</li> </ul> <h2>1.8.44 — 2026-07-14 — Respect a deliberate user raise (click-to-watch no longer forced back)</h2> <p>A user clicking a background pup window's taskbar button to WATCH a page load saw it snap back to the background. Cause: the open-time re-park passes (600ms/1.4s/2.5s) that counter Chrome's startup self-raise ran <code>SetWindowPos HWND_BOTTOM</code> <strong>unconditionally</strong>, so a click landing inside that 2.5s window got yanked back.</p> <ul> <li><strong><code>osBackgroundWindowByPid(..., respectUser)</code></strong> now distinguishes a real user raise from Chrome self-raising: if the pup window is foreground AND <code>GetLastInputInfo</code> shows a physical input within the last ~1.5s (Chrome's self-raise is programmatic — no input), it returns <code>"user-foreground"</code> and skips the park.</li> <li>The retry driver <strong>latches</strong> on that (<code>_userForeground</code>) and <strong>cancels the remaining passes</strong> — once you take the window, pup never fights you for it. The post-resize re-park honors the same latch. The initial off-screen park is unaffected (runs with <code>respectUser=0</code>; you can't have clicked an off-screen window).</li> </ul> <h2>1.8.35 → 1.8.43 — 2026-07-13/14 — Flash routes through AD; taskbar badge; snappy flash; cleanup nudge</h2> <ul> <li><strong>Taskbar flash + clear route through AD-core</strong> (<code>desktop_flash_window</code>) instead of pup's own <code>FlashWindowEx</code> — one mechanism, native, reliable. <code>browser_alert_window {stop}</code> now returns AD's real <code>applied</code> result (no more fire-and-forget <code>cleared:true</code> lies). Snappy: flashes on open before the page finishes loading.</li> <li><strong>Adom favicon badge</strong> overlaid on the pup taskbar button via AD's <code>desktop_taskbar</code> overlay.</li> <li><strong><code>🧹 CLEANUP</code> hint</strong> in the open response + <code>browser_status</code> when windows are ≥30m old or ≥5 open.</li> </ul> <h2>1.8.24 → 1.8.34 — 2026-07-13 — Background-by-default, actually correct (no pop / no cover / no focus steal / no cmd flash)</h2> <p>The long-standing "pup window pops up, covers my work, steals my typing" problem, solved properly and made deterministic + self-verifying. <strong>The full technique + the dead-ends are documented in the <code>pup-bridge-dev</code> skill ("Background-by-default without stealing focus") — read that before touching window placement; do NOT re-derive it.</strong> Summary:</p> <ul> <li><strong>Launch OFF-SCREEN</strong> (<code>--window-position=-32000,-32000</code>, not <code>--start-maximized</code>) so it never visually pops; CDP still renders it (screenshots work).</li> <li><strong>Park on-screen at z-bottom</strong> sized to the monitor (<code>SetWindowPos HWND_BOTTOM SWP_NOACTIVATE</code>) → behind the user's windows (invisible) but taskbar-clickable.</li> <li><strong>Hand keyboard focus back with <code>AttachThreadInput</code></strong> (snapshot the user's window pre-launch; attach input threads to bypass the foreground lock; <code>SetForegroundWindow</code> back to it; re-run over ~2.5s as the browser re-grabs during startup). Deterministic <code>backgrounded</code> instead of a flaky focus steal.</li> <li><strong>All window-management PowerShell runs HIDDEN</strong> (<code>execFileSync</code> + <code>windowsHide</code> + <code>-WindowStyle Hidden</code> via <code>runPsHidden</code>), so no cmd/terminal window ever flashes.</li> <li><strong>Self-verifying:</strong> every open re-reads <code>GetForegroundWindow()</code> and reports a <code>background</code> field (<code>backgrounded</code>/<code>still-foreground</code>). navigate/reload/switch_tab no longer re-raise a background window.</li> <li>Dead-ends rejected (all worse): start-maximized+lower (visible pop), bare <code>SetForegroundWindow</code> (lock), minimize-and-leave (breaks screenshots), <code>WS_EX_NOACTIVATE</code> (breaks taskbar-click + Chrome self-activates), minimize-loop (flicker), pup-side localhost probe (AD already reprimands localhost on failure).</li> </ul> <h2>1.8.23 — 2026-07-10 — Agent-update signalling (auto-flash), keystroke-safe raise, render_html</h2> <p>Field report from an agent that staged a 4-tab task and lost track of which window it was updating. All five items shipped (AD relays verb args verbatim → no AD-side change):</p> <ul> <li><strong>Auto-flash on agent updates.</strong> Mutating verbs (<code>open_window</code>/<code>open_tab</code>/<code>navigate</code>/<code>reload</code>/<code>back</code>/ <code>forward</code>/<code>switch_tab</code>/<code>click</code>/<code>type</code>/<code>press_key</code>/<code>input_dispatch</code>/<code>eval</code>/<code>evaluate</code>) flash the window's taskbar via real <code>FlashWindowEx</code> (<code>FLASHW_TRAY|FLASHW_TIMERNOFG</code>) so the user notices the agent touched it — <strong>never steals focus</strong>, <strong>self-suppresses while foreground</strong>, <strong>clears on click</strong>. Debounced: one flash per window per ~5s burst. Suppress per-call with <code>{silent:true}</code>, treat a read as read-only with <code>browser_eval {readOnly:true}</code>, or silence a whole session with <code>browser_open_window {autoFlash:false}</code>.</li> <li><strong><code>updateNote</code> + <code>lastAgentUpdate</code>.</strong> Pass <code>updateNote</code> on any mutating verb; pup records <code>{tabId,verb,note,ts}</code> and surfaces it top-level <strong>and per-tab</strong> in <code>browser_list_tabs</code> / <code>list_windows</code> / status — answers "which tab did the agent just change?".</li> <li><strong>Raise no longer leaks keystrokes.</strong> <code>browser_raise_os_window</code>/<code>focus_window</code> blur the page's active element before <code>bringToFront()</code>. Docs steer agents to <code>browser_alert_window</code> (or the auto-flash) for "I updated this", reserving raise for an explicit show-me.</li> <li><strong><code>browser_open_tab</code> returns <code>tabId</code> + <code>tabCount</code></strong> explicitly (no more diffing <code>list_tabs</code>).</li> <li><strong>New <code>browser_render_html {sessionId,html,waitUntil?,updateNote?,silent?}</code></strong> — renders a local HTML string via <code>page.setContent</code> (UTF-8-correct; a <code>data:</code> URL mojibakes accented chars without a charset).</li> <li>Note: <code>desktop_screenshot_window titleContains/sessionId</code> is an <strong>AD-core</strong> verb — routed to AD, not pup. The <code>fullPage</code> nested-scroll <code>nestedScroll:true</code> warning shipped in 1.8.21.</li> </ul> <h2>1.6.0 → 1.8.17 — 2026-07 — Generic browser, background-by-default, cross-thread ownership, bridge→AD notify</h2> <p>Consolidated summary of the arc since 1.1.0 (per-release detail lives in the wiki Releases tab):</p> <ul> <li><strong>Became a generic, non-profile browser.</strong> Removed all native-real-browser drive code — driving the user's real signed-in Chrome/Edge is now the Adom browser <strong>extension's</strong> <code>nbrowser_*</code> job. Every pup session is a fresh isolated Chrome/Edge process. Installed-browser-first: Chrome → Edge → cached CfT, spawn-verified with fallthrough; Chrome for Testing only as a last resort.</li> <li><strong>Loopback bind</strong> (<code>ADOM_BIND_HOST</code>, 127.0.0.1) so no Windows Firewall prompt.</li> <li><strong>Background by default → hard guarantee (1.8.17):</strong> windows never steal focus; sizing/positioning no longer foregrounds either. <code>foreground:true</code> is the only way to show a window.</li> <li><strong>Window ownership (1.8.16):</strong> sessions carry an advisory <code>owner</code>; declared cross-thread navigate/close is refused (<code>session_owned_by_another_thread</code>), anonymous grabs are reprimanded — stops AI threads stealing each other's windows. <code>browser_status</code>/<code>list_windows</code> expose <code>owner</code> + <code>ageMinutes</code>.</li> <li><strong>Chrome install drives the user through the UAC (1.8.12–1.8.15):</strong> on a locked-down box, escalates to an elevated install and — via AD ≥1.9.84's direct-API — fires a native "click YES" toast from the bridge itself, pinging the attended device (cross-AD <code>target</code>) and re-arming if it expires. Degrades cleanly on older AD (raise UAC once + relay hint, never spam).</li> <li><strong>Missing verbs + catalog completeness (1.8.8–1.8.11):</strong> added browser_click/type/press_key/back/ forward/evaluate and documented 13 working-but-unadvertised verbs in <code>bridge.json</code> + <code>browser_describe</code>.</li> <li><strong>Title-suffix resilience (1.8.17):</strong> persistent <code>evaluateOnNewDocument</code> injector so AD's title-based raise/lower window lookup stops flaking right after a navigation.</li> <li><strong>Skillpack restructure:</strong> public <code>pup</code> + <code>pup-bridge-dev</code> + <code>pup-bridge-publish</code> as <code>skills/<name>/SKILL.md</code> (<code>user-invocable:false</code> on the dev/publish pair).</li> </ul> <h2>1.1.0 — 2026-06-29 — Cold-start self-heal + ownership extraction</h2> <p><strong>Why:</strong> On a fresh PC (Hydrogen Desktop bundles Adom Desktop; the installer pre-installs neither Node nor Chrome for Testing), the first "open … in pup" failed with a generic "Failed to launch Chrome after 3 attempts" + a developer-only string ("run npx puppeteer browsers install chrome") — no <code>errorCode</code>, no auto-install, no actionable hint. The AI couldn't recover.</p> <p><strong>Fixed — the bridge now self-heals Chrome for Testing (CfT):</strong></p> <ul> <li><strong><code>src/chrome.js</code></strong> (new) — detect (validates the <em>actual</em> executable, not just "some dir exists"), auto-install programmatically via the bundled <code>@puppeteer/browsers</code> (no npx), pinned to the exact buildId this puppeteer expects, with download-progress + install-state dedupe + a readiness snapshot.</li> <li><strong><code>browser_open_window</code></strong> — a cold-start gate (CfT path only; native-drive skips it) that detects a missing CfT, kicks off a NON-BLOCKING background install, and returns a structured <code>{ errorCode:"chrome_for_testing_installing", installing:true, statusVerb:"browser_readiness", _hint:"poll browser_readiness then retry" }</code> — instead of blindly spawning a missing binary 3× then throwing. On install failure → <code>chrome_install_failed</code>/<code>chrome_download_failed</code> with a real hint (no more silent continue-to-doomed-launch).</li> <li><strong><code>browser_readiness</code></strong> (new verb) — <code>{ ready, chromeForTestingInstalled, installing, installProgressPct, ... }</code>. Poll until <code>ready:true</code>.</li> <li><strong><code>browser_prewarm</code></strong> (new verb) — install CfT WITHOUT opening a window (warm before first use); <code>{wait:true}</code> blocks.</li> <li><strong>Status chip</strong> — <code>/status</code> now emits <code>led</code>/<code>summary</code>/<code>tooltip</code> and tells the TRUTH: yellow "Installing Chrome N%" / "Chrome not installed", red on install failure, green when ready (was always green, even with no Chrome). <code>/status</code> + <code>/health</code> both carry <code>version</code> + <code>chrome</code> readiness.</li> <li><strong>Deep launch throw</strong> is now Chrome-aware (structured <code>chrome_for_testing_not_installed</code> when CfT is genuinely missing vs a memory/locked-profile failure when it's present).</li> </ul> <p><strong>Hygiene:</strong></p> <ul> <li><code>BRIDGE_VERSION</code> ↔ <code>bridge.json</code> version lockstep fixed (was 1.0.14 vs 1.0.16).</li> <li><code>bridge.json</code>: <code>statusVerb:"browser_readiness"</code> + <code>updateManifestUrl</code> so AD auto-updates from this page; new verbs registered in <code>verbs[]</code> + <code>browser_describe</code>.</li> </ul> <p><strong>Ownership:</strong> extracted from <code>adom-desktop/plugins/puppeteer</code> into this standalone wiki repo (<code>wiki.adom.inc/adom/adom-desktop-puppeteer-bridge</code>), mirroring the cloud-owned kicad/fusion model. Adom Desktop keeps a bundled SEED for first-run/offline and auto-pulls newer versions from here via <code>updateManifestUrl</code>.</p>
# Changelog — Adom Desktop Puppeteer Bridge
## 1.9.43 — 2026-07-20 — Why some icons had overlays and others did not, plus stacked session titles
John: "why do you sometimes have overlays on your icons and sometimes not?" It was never a rule, it
was a race and two silent failures. Measured on the live windows:
1. PLACEHOLDER FAVICONS. example.com declares <link rel="icon" href="data:,">. That fetched
"successfully" as an empty blob, so the overlay was abandoned and /favicon.ico was never tried.
Junk hrefs are now skipped before fetching.
2. NO RETRY, EVER. If the favicon had not loaded when the overlay ran, applyAppOverlay gave up
silently and nothing re-ran it, so that window had no overlay for its whole life. It now retries
once shortly after, logs when it finds nothing, and takes a second pass after navigation since
many sites inject their icon link after first paint.
3. STACKED SESSION TITLES (the reason an adopted window kept the default icon). retagSessionTitle
stripped only a TRAILING "(session: ...)", but the old session's MutationObserver had already
re-appended its own, so the strip missed and we produced
"BMV080 - Adom Wiki (session: thr) (session: thr-split1-mkh7)". The window still answered to the
PARENT's suffix, collided with it, and its branding landed unreliably. Now every occurrence is
stripped, and all title observers share one named handle so an adopted tab can disconnect the
previous session's observer instead of fighting it forever.
## 1.9.42 — 2026-07-20 — Stop the "event-driven" drag check from behaving like a poll
Chasing a suspected false-split, the detector proved CORRECT (an untouched 3-tab control window was
checked repeatedly and every tab reported the same window id, no split; the earlier adom-tsci splits
were real drags). But the audit exposed a worse problem: 1360 window-opened events across 10 drains
in about a minute. A busy Windows desktop fires that event for tooltips, menus and task switching,
and every drain was running a per-tab CDP check across every multi-tab session, which is exactly the
wasteful polling this design was supposed to avoid.
Two cheap guards: skip entirely when no session has 2+ tabs (a single-tab window cannot be split),
and throttle the check to at most once every 1.5s regardless of event volume. Detection latency is
unchanged in practice since a real drag is followed by more events.
## 1.9.41 — 2026-07-20 — Self-audit of the live taskbar: two icon bugs found and fixed
John: "look at your taskbar icons and audit yourself." Doing that on the real taskbar found two
defects that no amount of reasoning had surfaced:
1. An Adom app was wearing the WEB globe. classifyUrl demanded BOTH a *.adom.cloud host AND a
/proxy/ path segment, so an app served at a bare slug host (project-manager-xxxx.adom.cloud)
fell through to "web". Any *.adom.cloud host is now an Adom app.
2. A long-lived window still wore the plain DEFAULT tile with no category at all. Windows that
predate the category work, or that were recovered across a bridge restart, were never
re-evaluated. brandSweep now clears each session's cached category and re-runs the full
refresh, so every live window converges on a correct category icon.
Also corrected a claim I made to John: the extra split sessions I could not explain came from
adom-tsci, a different window than the hybrid one he was testing, not from his drag.
## 1.9.40 — 2026-07-20 — Adopted windows are re-tagged, so they stop being invisible and colliding
John dragged a tab out of a 3-category window and got sloppy results: one new window kept the bare
Chrome icon, and a window wore the favicon of an app it had nothing to do with. One root cause
behind both, and it was careless: an ADOPTED tab kept the title of the session it came FROM. All
three windows still read "(session: hybrid)".
Consequences, both observed live: AD could never resolve the new window by "(session: <newId>)", so
brandPupWindow/overlay/jump-list refresh failed outright ("could not refresh taskbar presence") and
the window kept the default Chrome icon; and because several windows answered to the SAME suffix,
any title-based operation hit an arbitrary one, which is how a shotlog favicon landed on a window
that was not shotlog.
Fix: retagSessionTitle() strips any existing pup suffix, installs the new session's suffix, and
re-arms the MutationObserver. It runs on the adopted tab BEFORE any branding (everything downstream
resolves the window by that suffix), and re-asserts the source window's own tag since its tab set
changed underneath it.
## 1.9.39 — 2026-07-20 — The dark-teal window grows 30 percent, and the glyph keeps its margin
I had misread the last request: I grew the line drawing INTO the window's margins, which is exactly
the bad graphic design John was objecting to. Correct reading: the dark-teal window itself grows 30
percent (156 x 133 on the 244 tile, up from 120 x 102), and the glyph scales with it while keeping a
real margin inside, 16 percent on every side. Nothing touches an edge. Also fixed the stroke weight,
which the rescale had thinned to a hairline (it is now computed to land at the same visual weight as
the original regardless of the glyph box size).
Rule to keep: every drawing gets margins, at every nesting level. Tile margin around the window,
window margin around the glyph.
## 1.9.38 — 2026-07-20 — Drop the toolbar line, glyphs much bigger, real Adom mark for apps
John, still squinting at the taskbar: drop the line separating the browser chrome from the content
(it collided with the glyph and earned nothing), and make the inner glyph bigger again. Done: the
window body is a plain solid rounded rect and the category glyph is roughly 1.7x its original size,
filling the body. The tile margins that match the Adom favicon are unchanged.
Also fixed the app icon: my hand-drawn Adom mark rendered as a blob, so the icon now embeds the REAL
two-lobe mark taken straight from the shipped pup tile art, scaled to fit the window body.
## 1.9.37 — 2026-07-20 — Inner category glyphs 30 percent larger
John, after living with them: the inner glyph was too small to read at taskbar size, but the frame
margins were right because they match the Adom favicon. So the CONTENT glyph is scaled 1.3x about
the content-area centre, growing into the window's empty space; the browser frame, its toolbar line
and the tile margins are untouched. Much more legible at 24px, which is the size Windows actually
paints. README icon art regenerated to match.
## 1.9.36 — 2026-07-20 — Never leave a stale taskbar overlay after a drag-out
The drag-out adoption worked (John dragged a tab out and the new window took pup's identity), but
the window it LEFT kept showing the dragged site's favicon in its overlay even though that tab was
gone. Cause: the refresh was fire-and-forget, and a window whose tab set just changed (or a window
just created) is not findable by title for a beat, so the refresh silently no-oped.
New refreshWindowChrome(session, id) refreshes a window's whole taskbar presence together (identity
icon, favicon overlay, jump list) and RETRIES with backoff until the shell can see the window, then
logs loudly if it never can. Both sides of a drag-out use it: the adopted window and the source
window. The source also clears its cached category and overlay first, so a window that went from
mixed back to plain wiki actually repaints instead of short-circuiting on "same category".
John's rule, recorded: overlays must always be as up to date as they can be. A stale favicon is a
lie about what the window contains.
## 1.9.35 — 2026-07-20 — THE drag-out bug: AD answers in two shapes and pup only understood one
Root cause found. AD's /command replies in TWO shapes depending on path and version: the relay wraps
the payload as {output:"<json string>"}, while the loopback direct API can return the payload BARE.
pup assumed the wrapped shape everywhere, so a bare payload silently collapsed to "" and every field
read as undefined. The drag-out drain therefore saw ZERO events while an identical manual call saw
dozens, and it failed silently because zero events is a legitimate state.
Fixed with one defensive parser (adPayload) that accepts either shape, used by the watch lookup and
the event drain. This class of bug has now bitten twice (desktop_caption, desktop_ui_events), so the
parser is the single place to fix it rather than each call site.
## 1.9.34 — 2026-07-20 — Drag-out diagnostics actually deployed, plus a UIA watch leak fix
Two things, one of them a process failure worth naming. The 1.9.33 diagnostic build never reached
the desktop: the manifest was updated LOCALLY but never pushed, so the manifest URL still served
1.9.32 and bridge_install correctly reinstalled 1.9.32 while reporting "Installed". Every conclusion
drawn from that build's logs was worthless. Lesson: after `release upload`, ALWAYS `repo push` the
manifest and verify the SERVED version before installing (a bare "Installed" proves nothing).
Also fixed a real leak this hunt exposed: pup subscribed to the global window-opened UIA watch on
every bridge start and never unsubscribed, accumulating watches (w1..w4 observed live). It now
reuses an existing global window-opened watch when one is present, since all events land in one
shared ring drained by sequence number.
Carries the drag-out diagnostics: every step of detection now logs (drain counts, per-tab window
ids, adoption or the reason none happened) so a silent failure cannot hide again.
## 1.9.32 — 2026-07-20 — Fix: the drag-out event was filtered away by its own (empty) name
1.9.31 subscribed to window-opened correctly but filtered the events by window NAME
(/Chrome|Chromium/). A window fires window-opened at CREATION, before its title exists, so the
dragged-out Chrome window arrived with an EMPTY name and the one event that mattered was discarded
(verified in the live ring: the needed event was seq 32, name ""). The filter is gone: any
window-opened triggers the follow-up, which is cheap and idempotent anyway. Also drains on
browser_list_tabs. Confirmed the underlying detection is sound: after John's drag, the dragged tab
reported window origin 335,292 while its siblings shared 0,0.
## 1.9.31 — 2026-07-20 — Drag a tab out and it becomes its own pup window (event-driven, no polling)
John dragged a tab out of a pup window: Chrome made a NEW OS window, which carried none of pup's
identity (bare Chrome-for-Testing icon), and because the dragged page keeps its "(session: <id>)"
title suffix, ONE session suddenly spanned TWO windows, making AD's title-based resolution
ambiguous.
Detection is EVENT-DRIVEN, not a scan (John: scanning every second is wasteful). AD exposes a global
UIA subscription, desktop_ui_watch {event:"window-opened"}, whose events land in a 500-deep ring
drained with desktop_ui_events {sinceSeq}. pup subscribes once at boot and drains on activity it is
already doing (the 30s health tick plus every tab verb), so there is no dedicated timer: instant
while you are working, one tick when idle. Only Chrome-class window events trigger the follow-up,
and the follow-up is one cheap CDP Browser.getWindowForTarget per tab of multi-tab sessions.
Policy (option A): a tab that no longer shares its session's window is ADOPTED as its own session,
with its own sessionId, identity, CATEGORY icon, taskbar overlay and jump list. That keeps "one
session = one window" true, keeps window titles unique, and means a dragged-out digikey tab
correctly shows the globe instead of inheriting the wiki book.
## 1.9.30 — 2026-07-20 — Fix the raise that reported success while the window stayed hidden
Root cause of "the toggle closed my window and never brought it back". osRaiseSessionWindow resolved
the browser PID as `_detachedPid || process().pid`, so for a DETACHED launch (every Windows pup
browser) it ran PowerShell SetForegroundWindow against the LAUNCHER pid's MainWindowHandle, which is
a different window than the session's. PowerShell dutifully reported "focused" each time while the
real window stayed hidden and unfindable. The proven browser_raise_os_window handler resolves the pid
as `process()` ONLY, which is null when detached, so it SKIPS PowerShell and relies on the CDP
place+maximize+bringToFront that actually works. The helper now matches it exactly. Verified live:
the same window that ignored seven in-code raises came back instantly via the verb's path.
## 1.9.29 — 2026-07-20 — CRITICAL: the wiki toggle destroyed tabs and could leave NO window
John clicked the jump-list login toggle on a 5-tab research window. The window closed, the captions
fired, and nothing came back. Two separate bugs, both severe:
1. TAB LOSS. The toggle relaunched only the ACTIVE tab's url and closed the rest, silently
destroying every other tab in the window. It now snapshots ALL tab urls plus which one was
active BEFORE teardown, relaunches on the active url, restores the siblings in order, and
re-activates the tab the user was on.
2. NO WINDOW RETURNED. The relaunch succeeded (session alive, page at 0,0, logged in) but the
window never became visible to the shell: the identity stamp logged "No visible window with
title containing (session: ...)" and the user was left with nothing at all. The toggle now
VERIFIES the window is findable after the raise, retries heal+raise with backoff, and if it
still cannot be seen says so in an on-screen caption instead of reporting success.
A switch that cannot show its window is a failed switch. Reporting ok:true while the user stares at
an empty desktop is the worst possible outcome and is what this release exists to prevent.
## 1.9.28 — 2026-07-20 — Category re-stamp retries, and stamp failures are loud
The 1.9.27 acid test flipped the AUMID to .mixed but the window stamp silently returned false once
and the tile stayed latched (the category path stamped exactly once, unlike the auth path which
retries). Stamp failures now log their verdict (response error or not-applied raw), and the
category-change re-stamp retries with backoff like the auth path.
## 1.9.27 — 2026-07-20 — Live tile repaints via category-carrying appIds (AD's measured fix)
AD measured every repaint mechanism on Win11 (WIN11_TASKBAR_ICON.md Part 7): the taskbar tile is
baked when the button is created and never re-read - WM_SETICON, DeleteTab/AddTab, SHChangeNotify,
FRAMECHANGED, RedrawWindow and the TaskbarCreated broadcast all leave it unchanged. The only live
repaint is making the shell create a NEW button, which a DIFFERENT appId does (~1s, no flicker).
So the per-session appId now carries the category: Adom.Pup.<sid>.<category>. A category flip is
register-new, stamp, unregister-old; the jump list re-attaches to the new AUMID automatically.
Registrations are keyed by the full appId (a Set - state is baked into the id). Session teardown
captures the current appId before the session record is deleted so cleanup cannot leak. Tradeoff,
by Windows design: pins bind to one AUMID, so pins do not follow category flips on per-session
windows; grouped mode keeps the stable pinnable Adom.Pup.
## 1.9.26 — 2026-07-20 — Signed-in icon applies on the FIRST stamp (Windows latches button icons)
Proven live: ex-wikiin stamped and registered icon=wiki-in, yet its taskbar button kept the plain
book. Windows latches a button's icon at window creation; later in-place re-stamps often do not
repaint. Fix: a session launching on the authed wiki profile is authed-intent, so _wikiAuthed is
seeded at session creation, BEFORE the first identity stamp - the first icon is already
book-plus-person. The post-open auth probe still corrects the flag and tab glyph if the ~30-day
session expired. Remaining known gap (AD ask): a category flip on a LIVE window (wiki to mixed by
adding a tab) re-stamps correctly but the button may hold the old icon until the window recreates;
needs an AD-side force-refresh of the taskbar tile.
## 1.9.25 — 2026-07-20 — Signed-in wiki icon is now unmistakably different (book + person)
John called it: the outline-vs-solid book distinction technically existed but read identical at
taskbar size, which is the same as not existing. The signed-in wiki icon is now the book plus a
SOLID PERSON BUST in the lower content area (a signed-in reader) - a genuinely different silhouette
that survives 24px and 16px. Logged-out stays the plain book. Lesson recorded: a fill change alone
is not a state indicator at taskbar sizes; change the silhouette.
## 1.9.24 — 2026-07-20 — Category icons v3: solid window body, knockout glyphs, real margins
John: the content glyph was nearly touching the frame edges (no margins, bad graphic design), so
make the browser window SOLID and put the line drawings inside it. Done: the window body is solid
dark teal with a teal toolbar separator, the category glyph is a teal knockout sized to about 55
percent of the content area with two-plus grid units of margin on every side. Reads clearly at 16px.
## 1.9.23 — 2026-07-20 — Category icons v2: browser window first, category as its content
John's design correction, and the right semantic: pup windows ARE Chrome windows, so every category
icon now shares the same browser-window line drawing (frame + toolbar, the 80%) and the CATEGORY is
what is inside the window (the 20%): open book = wiki public, solid book = wiki signed in, the ADOM
MARK = an Adom app (the terminal glyph made no sense for apps), globe = web, two overlapping panes =
mixed. Dark teal glyphs on the teal tile, 24x24 house grid, BMP frames.
## 1.9.22 — 2026-07-20 — Category icon updates the moment the category changes
Adding a digikey tab to a wiki window should flip its icon to "mixed" immediately, but the base
icon re-stamp sat behind the 10-minute brand debounce, so category flips lagged. updateTabCountBadge
(which already runs on every tab open/close/switch/navigate) now compares the window's category to
the last stamped one and re-stamps at once when it changed. The debounce still suppresses redundant
same-icon re-stamps.
## 1.9.21 — 2026-07-20 — Dark teal glyphs (family-consistent) + the white-icon disease was Explorer's cache
Two things. (1) John: the category glyphs should be DARK TEAL, not white - the original pup tile
draws its Adom mark and browser glyph in #003d40 on the teal, so white broke the family. All five
regenerated with #003d40 glyphs (BMP frames kept). (2) The white-taskbar-icon mystery from 1.9.19
and 1.9.20 was neither the ICO format nor pup: after the AD downgrade/upgrade thrash, EXPLORER's
own icon cache was poisoned - every icon change reported success while the shell painted its white
fallback, even for the old proven icon with no relaunch set. An Explorer restart rebuilds the cache.
Diagnostic for the future: when stamps report applied but the button paints the generic white page,
suspect the SHELL's cache, not your code - isolate by stamping the known-good icon; if that is white
too, restart Explorer.
## 1.9.20 — 2026-07-20 — Category icons paint now: ICO frames must be BMP, not PNG
The 1.9.19 category icons applied cleanly (AD reported the stamp and registration OK) but the
taskbar painted the generic white-page fallback. Root cause: the ICOs were written with
PNG-compressed frames (PIL's default), and the shell path that resolves a window's
RelaunchIconResource cannot parse them, so it fell back. AD's own HICON loading handles PNG frames,
which is why every API response said success while the button stayed white. Regenerated all five
category icons with classic BMP frames (bitmap_format="bmp"; pure-BMP verified, no PNG signatures)
under new filenames per the icon-cache rule. The old hand-built adom-pup-browser3.ico that always
painted is BMP-frame, which corroborates the diagnosis.
## 1.9.19 — 2026-07-20 — One session one window, category icons, and no emoji anywhere
Three things, in the order John asked for them.
1. ONE SESSION = ONE OS WINDOW. browser.newPage() opens a TAB in whatever window a profile already
has, so two sessions sharing the adom-wiki-authed jar collapsed into a single window. A window
title reflects only its ACTIVE tab, so AD could not resolve "the window titled (session: <id>)"
for the background one, and every per-window operation keyed on that title silently failed:
icon stamp, taskbar overlay, flash, jump list. That is why the login icon would not repaint
after a view toggle. Sessions now get their own window via CDP Target.createTarget
{newWindow:true}, same cookie jar, separate window.
2. CATEGORY ICONS. A pup window's icon now says what KIND of thing it is showing. Each is the teal
Adom tile carrying one monochrome #e6edf3 glyph on the 24x24 house grid, per the brand icon law
(custom hand-drawn, single colour, no gradients, no shadows, no emoji):
wiki open book, outline Adom wiki, public
wiki-in open book, SOLID Adom wiki, signed in
app window + prompt an Adom app (localhost or a cloud slug proxy URL)
web globe anything else on the internet
mixed stacked windows tabs spanning more than one category
Solid vs hollow is the one difference that survives Windows painting the button at 24px; a
padlock turned to mush there. Mixed content gets its own icon rather than picking a winner.
3. NO EMOJI. The brand rule is absolute ("no emoji, anywhere"), and the bridge was full of them.
Tab titles now lead with a monochrome geometric TEXT glyph, U+25CF solid for signed in and
U+25CB hollow for public, mirroring the icons. Jump-list titles dropped their emoji entirely
(the task already carries a real icon), and captions are plain words. The title strip regex
clears both the new glyphs and legacy emoji so old windows heal instead of stacking prefixes.
Also reverted the green overlay plate from 1.9.16 to 1.9.18: the category icon carries login state
now, so tinting the favicon plate was a redundant and off-law second colour signal.
## 1.9.18 — 2026-07-20 — Fix: the green plate never painted (overlay ran before auth was known)
The 1.9.16/1.9.17 plate colour was correct but never appeared. Ordering bug in browser_open_window:
applyAppOverlay paints the overlay EARLY, and `session._wikiAuthed` is only assigned a few lines
LATER from the auth probe - so the plate was always composited in the logged-out colour, even for a
window opened with wikiView:"authed". (The log tell: the per-session AUMID re-registers with
icon=auth AFTER the stamps, proving the state landed late.) Now the overlay is repainted once the
auth state is recorded.
## 1.9.17 — 2026-07-20 — Use the Adom brand green (#3fb950), not an ad-hoc one
John caught it: the signed-in green was #16a34a, which is Tailwind's green-600, not an Adom colour.
The design system defines `--green: #3fb950` and reserves it for "success only" - which is exactly
what "signed in" is - so both the overlay plate and the main-icon glyph now use that token. Nothing
else about the indicator changed. (The logged-out plate stays #0b2325, the pre-existing dark plate.)
## 1.9.16 — 2026-07-20 — Wiki login state = the favicon's PLATE COLOUR (John's idea, the right one)
John: "why don't you just use a different background than the background you place onto the favicon
in the overlay?" Exactly right, and better than everything I tried. The overlay already composites
each site favicon onto a dark rounded plate (#0b2325), so that backdrop was free real estate:
dark plate = logged out / public / any non-Adom site (unchanged)
GREEN plate = signed in to the Adom wiki
The favicon KEEPS the slot - you still see which site the active tab is on - and the plate is a far
bigger colour target at 16px than any glyph tucked into the 24px base icon. Cache key now includes
the plate state, the tooltip says "Adom wiki: SIGNED IN", and a login-state flip repaints the
overlay. .ico favicons (which sharp cannot decode) fall back to drawing the green plate alone
rather than losing the signal.
The 1.9.14 main-icon variant (solid green browser glyph) stays as a second, redundant signal for
Alt-Tab and the flyout header, where taskbar overlays are not painted.
## 1.9.15 — 2026-07-20 — Retry the icon re-stamp (the flip silently lost a race)
1.9.14 tracked login state correctly but the taskbar icon did not visibly change. Cause: after a
view toggle the window is BRAND NEW, and AD resolves it by the "(session: <id>)" title suffix that
pup injects only once the relaunched page loads. The single immediate re-stamp fired first and AD
answered "No visible window with title containing (session: ...)" - visible in the log as a
[badge] FAILED with no matching [identity] stamped line. Now retried with backoff until the title
exists, and the failure is logged loudly instead of passing silently.
## 1.9.14 — 2026-07-19 — Login state on the MAIN icon; overlay stays favicons (reverted 1.9.13)
REVERTED 1.9.13's overlay-slot padlock. John was explicit: the overlay is for FAVICONS ("i LOVE
seeing those favicons in that overlay") and the login indicator belongs on the MAIN icon. That was
his instruction from the start and I should not have moved it. The overlay is favicon-only again,
exactly as it was in 1.9.0.
The main-icon indicator is now what he actually asked for - the lower-right browser-window glyph
FILLED IN SOLID when signed in - with one deviation for legibility: it fills with signed-in GREEN
rather than dark. Rendered at true 24px (what Windows actually paints), a dark fill blended into
the glyph's own dark stroke; the colour flip is what survives downscaling. Geometry is identical
between the two icons so they register perfectly and only the fill changes.
adom-pup-browser3.ico - browser-window glyph, teal/hollow = logged OUT / non-wiki window
adom-pup-wiki-authed2.ico - same glyph FILLED SOLID GREEN = signed in to the Adom wiki
Also dropped the 1.9.12 padlock variant: changing SHAPE is exactly the detail that dies at 24px.
## 1.9.13 — 2026-07-19 — Login state moves to the OVERLAY slot (readable at real taskbar size)
Honest follow-up to 1.9.12: the base-icon corner glyph (browser outline vs padlock) works
mechanically, but a full-res capture of the real taskbar showed pup buttons render at ~24px, so
that corner glyph is only ~7px — too small to be the "good human indicator" John asked for.
Moved the signal to the OVERLAY slot, which Windows paints ~16px bottom-right and designs for
exactly this:
GREEN plate + closed padlock = signed in to the Adom wiki
SLATE plate + open padlock = logged out / public view
Colour carries it at 16px where shape cannot. The slot is free on wiki windows because the favicon
that normally rides there is the ADOM LOGO — redundant with the pup base icon, which is already
Adom. NON-Adom sites (ti.com, digikey) keep their favicon, which genuinely says where you are.
The 1.9.12 base-icon swap stays as a second signal for Alt-Tab and the flyout header, where
overlays are not painted. Tooltip spells it out: "Adom wiki: SIGNED IN / public - logged out".
## 1.9.12 — 2026-07-19 — The taskbar ICON now shows Adom-wiki login state
John: "can you show an indicator in the pup icon whether the adom wiki is logged in or not? maybe
that browser window icon gets filled in solid". Done, as a MAIN-ICON swap (the overlay slot stays
the active tab's favicon, which is more useful):
adom-pup-browser3.ico — browser-window OUTLINE glyph = logged OUT / non-wiki window
adom-pup-wiki-authed1.ico — SOLID PADLOCK glyph = signed in to the Adom wiki
Same art otherwise, only the lower-right corner glyph differs. Solid-vs-outline is deliberate: a
mass/contrast change is what survives downscaling to 16-32px (fine shape detail does not), and the
padlock carries the meaning. Rendered from SVG at 16/24/32/48/64/128/256.
Plumbing: session._wikiAuthed tracks the state and setWikiAuthedState() re-stamps the window
identity AND re-registers the per-session AUMID when it flips (so Alt-Tab and the flyout header
follow too). Two cache traps handled — the identity cacheKey now includes the state, and the AUMID
registry is a Map of appId->state instead of a Set, because both cache by key/path and would
otherwise pin the stale tile. State is set on open, on the view toggle (both directions), and on
the navigate auth re-check, so a window that logs in mid-session updates its icon on its own.
## 1.9.11 — 2026-07-19 — OS captions live, jump-list header branded, in-page toast retired
AD shipped all four asks; pup now consumes them.
- **desktop_caption on the loopback API (AD 1.9.144).** The earlier silent no-op was a wider AD hole
than "relay-only": ~90 inline verbs matched only their BARE names, and the CLI strips the
`desktop_` prefix client-side — which is exactly why a CLI test passed while pup's identical call
did nothing. Both spellings work now. Fixed pup's arg name too: it is `expiresInMs`, NOT `duration`.
- **In-page toast RETIRED.** With a real OS surface available, pup should not inject DOM into the
user's page. Captions are also the only feedback that survives the window teardown mid-switch.
- **Jump-list header icon (AD 1.9.141).** Not a registry fix — there is no ApplicationIcon value on
AUMID keys and the registry cannot brand the header at all. The header obeys the window's Relaunch*
TRIO, and Windows ignores RelaunchIconResource unless RelaunchCommand is ALSO set. stampPupIdentity
now passes relaunchCommand (a real `browser_focus_window` for that session, carrying a
foregroundReason since the raise gate demands one, with Windows-argv-escaped quotes).
- **Jump-list cmd flash (AD 1.9.143)** needed no pup change (AD interposes a GUI-subsystem shim);
pup re-sets jump lists at every open/navigate, so pre-1.9.143 lists heal automatically.
- **Log rotation:** answered, bounded. Per-spawn `<name>.log` truncates on respawn; the appended
`<name>.ad.log` is hard-capped (trimmed to newest 64 KB at 128 KB). No janitor needed on pup's side.
## 1.9.10 — 2026-07-19 — In-page toast feedback (desktop_caption is relay-only) + branding race fix
Root-caused why the 1.9.9 captions never appeared: AD's loopback direct API — the path bridges use
via chrome.adCommand — does NOT expose desktop_caption (confirmed live: "'desktop_caption' is not a
built-in desktop verb"; the relay path has it, which is why a CLI test showed one). Filed with AD.
pup now renders its own IN-PAGE toast (wikiToast — a fixed-position Adom-teal pill injected into the
page, auto-fade): "Switching…" into the old page at click time, and the result ("🔓 Adom wiki: now
the LOGGED-IN view (John Lauer)" / "📖 …now the PUBLIC (logged-out) view") into the relaunched page
right where the user is looking. The AD caption call stays (now logged, not silent) and lights up
the moment AD ships direct-API captions. Also fixed the [badge] FAILED race: the toggle branded the
window before the title suffix existed — branding now waits 1.5s like every other path.
## 1.9.9 — 2026-07-19 — FIX: clicking the jump-list task did nothing (Windows argv quoting)
THE bug that made the whole feature look broken. The jump-list task line is parsed by the CLI's
argv parser, which STRIPS unescaped double quotes — so the raw JSON args arrived as
{sessionId:wiki-demo,view:authed} and the CLI died instantly with "Invalid JSON args: key must be a
string at line 1 column 2" (exit 1). The user saw a 50ms cmd flash and nothing else. Reproduced and
fixed on the real command line: the JSON is now wrapped in quotes with inner quotes
backslash-escaped so argv delivers it intact as one argument.
Also added an on-screen AD caption (desktop_caption) so a click gives INSTANT feedback during the
few-second relaunch: "🔓 Switching to the LOGGED-IN view of the Adom wiki…" on click, replaced on
completion by "🔓 Adom wiki: now the LOGGED-IN view (<user>)" / "📖 …now the PUBLIC (logged-out)
view", or a prompt to do the one-time sign-in.
TESTING LESSON (baked into pup-bridge-debug): I had only tested the VERB directly via the CLI,
which bypasses the exact path a click takes. The bug lived entirely in that untested segment. Test
the ACTUAL entry point the user hits, not a convenient proxy for it.
## 1.9.8 — 2026-07-19 — Wiki toggle: snappy click, non-blocking raise, self-correcting glyph
Follow-up to 1.9.7's foreground raise, from ralph-testing it live. Three fixes: (1) the OS raise now
uses async PowerShell (runPsHiddenPromise) instead of sync — the sync call blocked pup's whole event
loop for up to 8s, stalling every other request. (2) browser_wiki_set_view now RESPONDS the instant
the window is raised and runs branding/overlay/jump-list/auth-recheck in the BACKGROUND, so a
jump-list click doesn't wait ~30s on cosmetics (that stall was likely why the switch felt like it
"never worked"). (3) The tab glyph is set optimistically on switch and then self-corrects via a
delayed auth re-check — fixes a logged-in window (/auth/me=200) briefly mislabeling "📖 Public"
because the first auth check raced the page load.
## 1.9.7 — 2026-07-19 — Wiki toggle now REALLY pops to the foreground when you click it
The taskbar wiki view-toggle worked functionally (verified: it relaunches the window under the
authed profile, /auth/me=200, tab title flips to "🔓 Logged in") but the user couldn't SEE it: the
callback only did a CDP maximize, so the relaunched window came back behind whatever they were
looking at. Now browser_wiki_set_view does a real OS SetForegroundWindow raise (extracted into a
reusable osRaiseSessionWindow helper from the proven browser_raise_os_window path — CDP place +
clear WS_EX_NOACTIVATE + SetForegroundWindow, with a WMI child-PID fallback) plus a taskbar flash
in case Windows' foreground lock denies the raise. Clicking the jump-list task now unmistakably
brings the switched window to the front. (The ~50ms cmd flash on click is AD's console-subsystem
CLI launcher — tracked separately for an AD CREATE_NO_WINDOW fix.)
## 1.9.6 — 2026-07-19 — Kill the ~60s respawn loop (crash-poison recovery) + jump-list task icon
Two fixes. (1) THE BIG ONE: pup was respawning every ~60s for hours (AD's lifecycle log showed
spawn-with-no-reap = pup self-crashing, not AD killing it). Root cause: recoverSessions() reconnects
to Chromes from ~/.adom/pup-sessions/*.json on boot; a NATIVE crash during recovery (puppeteer.connect
/ a CDP call) kills the process before the per-session try/catch can delete the file, so the same
poison file re-crashes every boot forever (in-process try/catch and the uncaughtException handler
can't stop a native crash). Fix: a persistent circuit breaker — a recover-attempt marker is written
BEFORE the risky ops; a file that still carries an incomplete attempt next boot is quarantined
(deleted), a clean recovery clears it. One strike quarantines, so any poison file self-heals in a
single crash. (2) The taskbar jump-list toggle now carries the Adom Pup icon (iconPath) instead of
Windows' generic blank-document glyph.
## 1.9.5 — 2026-07-19 — Name the Adom wiki in the jump-list toggle (was too generic)
The taskbar toggle read "🔓 Switch to logged-in view" — generic enough to look like it'd apply to
any site (John's catch). Now it leads with the wiki: "🔓 Adom wiki: switch to logged-in view" /
"📖 Adom wiki: switch to public view", with descriptions that spell out logged-in = private
source/drafts/owner cards vs public = what the world sees. Self-describing at a glance.
## 1.9.4 — 2026-07-19 — Fix the concurrent-launch wedge + liveness/recovery hints
Root-caused a live cold-start wedge: after a bridge restart, two near-simultaneous
browser_open_window calls for the SAME profile each spawned their own detached Chrome onto the
same --user-data-dir, collided on Chrome's SingletonLock, and mutually taskkilled each other's
process — so NONE came up and the bridge looked hung for minutes (verbs timed out, AD LED dim).
Fix: `_launchInFlight` dedup — the first launch per profile registers a Promise and concurrent
callers JOIN it instead of racing a second Chrome. Also added AI-facing recovery hints so other
threads don't relearn this: browser_readiness now explains that a status probe does NOT respawn a
down bridge (only a window/tab verb does) and warns against spamming opens during "bridge_starting";
the pup SKILL and dev-skill gained a liveness/restart/wedge troubleshooting section (the AD LED +
browser.bridgeRunning are truth; bridge_list status:"running" is not liveness).
## 1.9.3 — 2026-07-19 — Per-window wiki view toggle in the taskbar jump list (#208 shipped)
AD 1.9.139+ shipped #208 (desktop_set_window_jumplist). Every WIKI pup window now carries a
taskbar RIGHT-CLICK task to flip between the logged-in and public view — "🔓 Switch to logged-in
view" on a public window, "📖 Switch to public view" on an authed one. The jump list attaches to
the per-session AUMID (registry-only, from 1.9.2), so it's a PER-WINDOW menu. Clicking it runs
AD's bundled CLI → the new browser_wiki_set_view verb, which relaunches THAT window under the
other cookie jar (profile = userDataDir is fixed at launch, so a view switch is a relaunch) and
brings it on-screen (a user click, so foregrounding is warranted). The menu is added/cleared on
open and on every navigation (drops off when a window leaves Adom); split mode only (grouped mode
shares one button); self-gates on older AD. If the authed jar hasn't done the one-time SSO yet,
the response flags wikiLoginNeeded and the user logs in once in that window.
## 1.9.2 — 2026-07-19 — Register per-session AUMIDs (#207 shipped): Adom icon in alt-tab + jumplist
AD 1.9.136+ shipped #207 (desktop_register_app_identity {shortcut:false} + unregister). pup now
REGISTERS each split-mode per-session AUMID (Adom.Pup.<sessionId>) registry-only at window create,
so Alt-Tab and the taskbar right-click jumplist header resolve the Adom icon/name instead of
falling back to the chrome.exe icon. Unregistered on session close (no registry cruft, no Start
Menu spam). Self-gates on older AD. The base "Adom.Pup" stays the shortcut-registered pinnable
launcher; grouped mode already uses it.
## 1.9.1 — 2026-07-19 — Wiki mode label is now WORDS, and updates after login
John saw a bare 📖 emoji and had no idea what it meant; the logged-in window showed nothing at
all (glyph checked auth at open, before login completed). Fixed: the tab title now leads with a
clear WORD label — "🔓 Logged in · <title>" or "📖 Public · <title>" — and it re-checks auth +
refreshes on every Adom navigation, so a window that logs in mid-session flips to "🔓 Logged in".
## 1.9.0 — 2026-07-19 — Favicons win the overlay (John: more useful than counts)
The taskbar overlay now shows the ACTIVE tab's site favicon for ANY tab count — you always see
what the window is showing (ti.com, mouser, …). The tab count moves to the hover tooltip
("Adom Pup — 3 tabs (icon = active tab)"). Overlay follows tab switches (updates on
browser_switch_tab). Fallback to a plain count badge only when the active page serves no favicon.
Grouped-mode dual counter unchanged.
## 1.8.99 — 2026-07-19 — Auth gating is Adom-wide; overlay = site favicon (1 tab) or count (2+)
- Authed treatment now gates on ANY adom.inc host (isAdomUrl), not just wiki.adom.inc — other
Adom properties get the logged-in view + hints too. NON-Adom sites (ti.com, digikey.com,
adom.cloud proxies) get NOTHING: no auth routing, hints, or glyph.
- Overlay rule resolved (John's 3-vendor-tabs question): 1 tab → wear THAT site's favicon on a
dark plate (you see it's ti.com); 2+ tabs → show the tab COUNT (no single favicon represents a
multi-site window; hover tooltip carries the detail). No stacking — the 32px slot can't.
## 1.8.98 — 2026-07-19 — Profiles live OUTSIDE the bridge cache (persistence actually persists)
Root-cause fix behind "the wiki login didn't survive": PROFILES_DIR/SESSIONS_DIR were inside the
bridge dir (bridges-cache\puppeteer), which AD CLOBBERS on every bridge_install — so every pup
update wiped every persistent profile, including the wiki-auth session. Moved both to the stable
~/.adom zone (pup-profiles / pup-sessions) with a one-time best-effort migration. Now "log in
once" genuinely lasts across updates, restarts, and reboots — for the wiki-auth vault and any
user profile. (The dev-skill "persistent profile = automatic 30-day storage" claim was only true
once this landed; a restart_bridge never clobbered, a bridge_install always did, which masked it.)
## 1.8.97 — 2026-07-19 — Gentle in-tab wiki mode indicator
The AI picks the wiki view, so the user needs to SEE which one. A leading glyph on the TAB TITLE
shows it: 🔓 = logged-in view, 📖 = public/anonymous. (The native tab-strip empty space isn't
reachable by the bridge; the tab title is, and it's where the user's already looking.) Idempotent
+ MutationObserver so the SPA can't wipe it; doesn't fight the session-suffix injector.
## 1.8.96 — 2026-07-19 — Wiki authed: shared login across ALL threads, own window each; sharper hints
Proven live: a brand-new thread (own sessionId) opening wikiView:"authed" is authenticated by
reusing the one-time login — each thread gets its OWN window, they share only the vault cookie jar.
- Routing fix: wikiView:"authed" now ALWAYS routes to the reserved adom-wiki-authed vault profile
(previously only when the thread passed no profile of its own) — so the single login serves
every thread regardless of what profile they'd otherwise use.
- Hints rewritten around the 80% case: PUBLIC is the default (logged out, what the world sees);
wikiView:"authed" is the opt-in logged-in view; a thread can't self-login (it's the user's SSO)
but ONE login unlocks authed for EVERY thread for ~30 days. Survives bridge restarts/reboots.
## 1.8.95 — 2026-07-19 — Suppress the browser sign-in / work-profile prompt (CfT DOES have it)
Correction to a load-bearing wrong assumption: Chrome for Testing shows the same "Sign in to
Chromium? Set up a work profile" dialog that poisoned Aditya — it is NOT immune (proven live).
pup now launches CfT with browser sign-in and sync DISABLED
(--allow-browser-signin=false --disable-sync --disable-features=SigninIntercept,
DiceWebSigninInterception,ProfilePicker,ForceSignInReauth,SyncPromoAfterSignin --disable-signin-promo),
so the dialog never appears for any user. Site logins (e.g. the wiki session cookie) are plain
cookies and unaffected. The auth-URL steering hint stays as backup. Dev skill corrected.
## 1.8.94 — 2026-07-19 — Wiki dual-view: single-window, hint-only (no two-window nag)
Reverted the "show BOTH views" push — double-reloading the same page in two windows is annoying
for the reload-100x loop. Now ONE window is normal; hints just make the AI AWARE it can switch
this window to wikiView:"authed" (or log the user in once) when a task needs the logged-in angle.
The deliberate public-vs-private visibility check flips the SAME window between views.
## 1.8.93 — 2026-07-19 — Wiki dual-view: public (default) + logged-in (wikiView:"authed")
pup can now open a wiki.adom.inc page from EITHER viewpoint, and hints teach the AI to show BOTH
so the user can verify publish visibility ("did private source leak to the public?").
- Default open = PUBLIC/anonymous (fresh profile, logged out — what the world sees).
- `browser_open_window {url, wikiView:"authed"}` routes to a reserved PERSISTENT profile whose
on-disk cookie jar holds the 30-day SSO session. First use returns `wikiLoginNeeded:true` with
a hint to raise the window for a one-time hydrogen.adom.inc SSO; thereafter it stays logged in.
- No token→cookie shortcut exists (wiki auth is cookie-based SSO; the container CLI bearer is a
separate credential) — the persistent profile IS the mechanism, so no manual cookie
capture/cache/reinject is needed; the 30-day cookie lives in the profile and re-auth only fires
on real expiry (a logged-out check catches it). Dual-view hints on every wiki open; the
pup-adom-wiki skill documents the verify-visibility workflow.
## 1.8.92 — 2026-07-19 — App-favicon overlay: a pup window WEARS the app that drives it
John's standard: every Adom app already serves a favicon (<link rel="icon">, monochrome white
mark per adom-ui-design; the canonical webview identity per hd-tab-icons) — so pup now
auto-fetches the loaded page's favicon and wears it in the taskbar overlay, composited onto a
dark rounded plate (white marks need dark backing). "That pup window with the shotlog icon IS my
shotlog thread." Explicit overrides: browser_open_window {appIconB64, appName}. An app-looking
URL (localhost / *.adom.cloud proxy) serving NO favicon gets a 🎨 reprimand hint teaching the
standard (adom-ui-design + app-creator). When an app icon is worn it owns the overlay slot and
the tab count moves to the hover tooltip; split-mode count badges remain for icon-less windows;
grouped mode's dual counter is unchanged. Favicon refreshes on navigate.
## 1.8.90 — 2026-07-18 — Grouped counter: rounded-rect plates
Bare digits were hard to read without a background; rounded rectangles fit digits tighter than
circles so the type stays large. Dark rect / white digit = windows; teal rect / dark digit =
total tabs.
## 1.8.89 — 2026-07-18 — Circle-less counter: windows digit white
The dark windows digit vanished on the dark taskbar (the circles had been doing the contrast
work). Windows count is now WHITE with a dark halo; tabs stays teal. Both bright, still
color-distinguished.
## 1.8.88 — 2026-07-18 — Circle-less grouped counter (trial)
Grouped dual counter drops the circles: bold bare digits (windows dark, tabs teal) with a
hairline contrast halo, ~25% taller than the circled variant allowed.
## 1.8.87 — 2026-07-18 — Grouped dual-counter digits enlarged again
Digits in the grouped top-row counters bumped another ~12% (14px single-digit windows, 13px
single-digit tabs) for taskbar legibility.
## 1.8.86 — 2026-07-18 — Grouped dual counter: both numbers on the top row
Per John: windows (dark) and total tabs (teal) now sit side by side across the TOP of the
overlay instead of diagonally.
## 1.8.85 — 2026-07-18 — Badge digits 15% bigger
Per John: badge numbers bumped ~15% across the static split badges and the grouped dual counter.
## 1.8.84 — 2026-07-18 — Grouped-mode dual counter (windows + total tabs); flat badges
- Grouped mode's single button now shows TWO counters: window count (dark circle, upper-right)
and total tab count (light teal circle, dark text, lower-left). Rendered at runtime via sharp
from an SVG template (combos are dynamic), cached per pair; falls back to the static
windows-count badge if sharp is unavailable. Single-window grouped shows a plain tab count.
- All badge circles are now FLAT — no white outline (per John), split badges regenerated.
## 1.8.83 — 2026-07-18 — Lighter tab-count badge
Per John: the badge circle no longer fills the whole overlay slot — ~60% size anchored
upper-right, hairline translucent outline instead of the heavy white ring. (Overlay icons are
passed as base64, not paths, so no icon-cache filename dance needed for these.)
## 1.8.82 — 2026-07-18 — browser_configure: taskbar grouping is now a user preference
John's insight from the AUMID grouping bug: BOTH behaviors are legitimate preferences. New
`browser_configure {taskbarGrouping: "split"|"grouped"}` (persisted in ~/.adom/pup-settings.json,
survives bridge updates):
- `split` (default): per-session AUMID — one taskbar button per window, each badged with its own
tab count.
- `grouped`: shared AUMID — all pup windows stack under one "Adom Pup" button whose badge shows
the WINDOW count.
Switching re-stamps live windows immediately (brand sweep) and refreshes badges. AIs are told to
ask the user before changing it.
## 1.8.81 — 2026-07-18 — Per-session AUMID: one taskbar button per window again
The shared Adom.Pup AUMID made Windows GROUP all pup windows into a single taskbar button —
destroying per-window differentiation and rendering the tab-count overlay wrong (it showed
whichever window last updated). Identity is now stamped as `Adom.Pup.<sessionId>` per window:
each session gets its own button with its own accurate badge. The per-window
RelaunchIconResource/DisplayNameResource (already set by AD's verb) brand each unregistered
per-session AUMID; the registered plain "Adom.Pup" remains the pinnable Start Menu launcher.
The brand sweep re-stamps live windows on deploy, splitting existing groups immediately.
## 1.8.80 — 2026-07-18 — Tab-count overlay badge (differentiate pup windows at a glance)
John: an 8-tab review window and a 1-tab window are indistinguishable teal tiles. The overlay
slot (freed by the identity takeover) now shows the window's TAB COUNT for ≥2 tabs — dark
circle, white digit, 9+ cap, tooltip "Adom Pup — N tabs". No badge = 1 tab, so many-tab windows
pop out. Updated on open_tab / close_tab / popup auto-attach; only on identity-capable AD
(≥1.9.134) — older AD keeps the brand badge in that slot.
## 1.8.79 — 2026-07-18 — Brand sweep: no more mixed icon generations
Identity is per-HWND and stamped at birth, so windows born across rebrands/updates left the
taskbar showing MIXED icon generations (plus dead-session ghosts inflating the count). New
`brandSweep` re-asserts each live session's title suffix (title churn can make a window
un-resolvable) and re-stamps it to the CURRENT identity — at startup (post-reattach) and after
every browser_rescan. Any future icon change now propagates to already-open windows within
seconds of the bridge update instead of never.
## 1.8.78 — 2026-07-18 — AD-version awareness: warn when the raise gate is bypassed
Every AD capability gap self-gates silently (identity → badge, bottom/force → PS fallback) EXCEPT
one: AD <1.9.124 intercepts browser_raise_os_window natively, so pup's foreground gate silently
does not exist there — a policy hole, not cosmetics. pup now probes the AD version at startup and,
on affected boxes: logs a warning, turns the status chip yellow with an update-AD note, exposes
`adVersion` + `raiseGateBypassed` on /status, and appends an upgrade advisory to open-response
hints. Boxes on current AD see nothing.
## 1.8.77 — 2026-07-18 — pup owns its own provisioning: warmup ALWAYS ensures CfT
John: "isn't that your decision? why would you need AD to change anything?" Correct — the
skip-CfT-when-Chrome-exists policy was the dead installed-browser-first doctrine living on in
pup's OWN warmup(). Now warmup (runs at every bridge spawn = AD install/update + machine start)
background-fetches CfT whenever it isn't cached, regardless of installed browsers — so a fresh
box provisions pup's browser at INSTALL time and the first "open in pup" never waits. Low-disk
guard unchanged. AD's job ends at "pup is installed"; no AD policy involved (issue #204
withdrawn — resolved pup-side; AD's v1.9.76 prewarmer is redundant for pup and the install
singleton dedupes if both fire).
## 1.8.76 — 2026-07-18 — Icon glyph tucked into the corner (clear gap from the leaves)
1.8.75's browser glyph touched the dark leaf and blended into it. The glyph is now smaller and
tucked into the lower-right corner with clear teal space on all sides. Ships as
`adom-pup-browser3.ico` (new filename per the icon-cache rule); v2 art removed.
## 1.8.75 — 2026-07-18 — Identity icon simplified per John: line-art browser glyph, no white
1.8.74's plated badge was overdone. The icon is now the favicon with a MINIMAL line-art browser
window in the leaf dark-teal (#003d40) inlaid lower-right — teal fill, dark outline + title-bar
line, zero white. Ships as `adom-pup-browser2.ico` (new filename per the icon-cache rule); the
v1 badge art is removed. Master SVG `adom-pup-browser2.svg`; future home remains the brand-icons
wiki repo.
## 1.8.74 — 2026-07-18 — New pup identity icon: favicon + browser-window inlay
The Adom Pup icon is now the Adom favicon with a browser-window glyph inlaid in the lower-right
(dark plate, white window frame, teal screen) — the icon itself says "this is an Adom browser
window." Ships as `icons/adom-pup-browser.ico` (NEW filename per the icon-cache rule; the old
adom-pup.ico is removed). Master art `adom-pup-browser.svg` lives in src/ for now; future home is
the Adom brand-icons wiki repo so the mark is globally reusable beyond pup.
## 1.8.73 — 2026-07-18 — Adom Pup window identity: full icon/name takeover (AD ≥1.9.134)
pup windows now read "Adom Pup" with the Adom icon in the taskbar, alt-tab, and hover — no Chrome
logo, no CfT "Test" tile. Built on AD 1.9.134's verbs (shipped to pup's spec after the 2026-07-18
prototype):
- **Self-registration at bridge start** (`desktop_register_app_identity`, loopback direct-API):
HKCU AUMID `Adom.Pup` + a Start Menu "Adom Pup" shortcut whose PIN launches a REAL managed pup
window via adom-desktop-cli (the pinning trap, solved). The bridge owns its branding: the icon
ships in this release zip (`icons/adom-pup.ico`; art changes require a NEW filename — Windows
caches decoded icons by path).
- **Per-window stamp at CREATE** (`desktop_set_window_identity` with `cacheKey`), not just at
park — identity is per-HWND, and stamping late would leave the window reading "Chrome for
Testing" through its whole load. Re-stamped at park/heal/tab hot paths.
- **Overlay badge demoted to fallback**: on AD <1.9.134 (unknown verbs → self-gating) the teal
overlay badge continues exactly as before; on capable AD the full identity replaces it.
`desktop_taskbar` remains in use for flash/progress.
- Mechanical root (from the prototype): icon HICONs are process-owned USER objects — AD holds
them in a process-lifetime cache, which is why AD-core is the only correct owner.
## 1.8.72 — 2026-07-18 — THE DEMARCATION GATE: no silent branded fallback, ever
1.8.71's "converge by the second session" still ran the FIRST session on branded Chrome — and the
first session is exactly the Aditya session. Now an unpinned new-window open REQUIRES cached CfT:
if absent, pup kicks the ~150 MB install in the background and returns the pollable
chrome_for_testing_installing (poll browser_readiness, retry) instead of silently driving the
user's browser. The installed Chrome/Edge is reachable ONLY by explicit browser_use pin (hints
carry the identity-bleed warning). Existing/live sessions exempt. Companion AD ask filed
(issue #204): flip prewarm_cft auto→always so install-time prewarm erases the first-use wait.
## 1.8.71 — 2026-07-18 — CfT-first hybrid pick order (#202) + auth-URL steering (the Aditya incident)
- **Pick order flipped to: cached CfT → installed Chrome → Edge → CfT-download-last-resort.**
Chrome for Testing is the automation-first binary with no profile/sync/sign-in machinery — the
branded browsers' identity features bleed into pup's "anonymous" windows (the documented
sign-in → profile-creation → consolidation → lost-user cascade). No first-open stall anywhere:
a branded-fallback launch kicks a ONE-TIME background CfT install, so every box converges to
deterministic CfT by its second session. `browser_use` still pins explicitly.
- **Codec caveat retired with evidence:** CfT (real Chrome build) decodes H.264/AAC/MP3/VP9
("probably" via canPlayType on a live box) — the gap was legacy Chromium's, not CfT's. Only
Widevine DRM playback is out of scope.
- **Auth-URL steering:** opening/navigating to a known auth/login URL (Google, Microsoft, Okta,
Auth0, GitHub, AWS, Apple, Salesforce, Atlassian, OpenAI, generic /oauth2/authorize) returns a
🔑 hint: pup is the anonymous sandbox — sign the USER in via abe (their real browser) or AD-core
desktop_open_url (plain native launch) instead; continue in pup only for throwaway accounts.
## 1.8.70 — 2026-07-15 — THE FOREGROUND GATE (abe parity): reason required, recorded, auditable
Real incident: a thread background-opened a window then raised it 2 minutes later; the raise verb
required no reason, recorded nothing, and didn't even log — forensics needed the focus-latch
timestamp. pup now gates foregrounding the way abe does (dual key, one call):
- `browser_open_window {foreground:true}` WITHOUT `foregroundReason` → the open succeeds but the
window stays BACKGROUND, with `foregroundDenied:'foreground_reason_required'` + a hint (abe
semantics: withhold the foregrounding, not the work). With a reason (min 10 chars): granted.
Also accepts abe's arg name `userRequestedForeground`.
- `browser_raise_os_window`/`browser_focus_window` WITHOUT `foregroundReason` → REFUSED with
`errorCode: foreground_reason_required` and the contract in the hint.
- Every granted foreground is logged (`[foreground] ... GRANTED — reason: ...`), recorded in
`lastAgentUpdate` (`FOREGROUND: <reason>`), and surfaced as `lastForeground {ts, verb, reason}`
in session status — "who put this window on my screen and why" is now one query.
- browser_describe teaches the whole contract, including return-to-background after the show-me.
## 1.8.69 — 2026-07-15 — Sized opens keep their x/y (multi-monitor placement works); window-management granted
- The sized-open background re-assert was still the LEGACY PS park, which moves the window to 0,0
sized-to-monitor — silently clobbering the caller's x/y/w/h (every sized open snapped back to
the primary monitor's origin, making second-monitor placement impossible). It is now AD
`state:'bottom' force:false` — z-order only, geometry untouched, user-held windows respected.
- `window-management` added to the permissive default grants so `getScreenDetails()` works inside
pup pages (it used to hang forever on a CDP-suppressed permission prompt) — pup can now
enumerate the REAL monitor layout from Chrome itself. Chrome sees monitors AD's
`desktop_list_monitors` misses (ADOMGPU reports 1 monitor via AD but isExtended:true in Chrome;
AD gap to file).
## 1.8.68 — 2026-07-15 — Park AFTER the page settles: the paint-raise happens invisibly off-screen
Even with 1.8.67's re-asserts, the raise-then-re-bottom left a visible blip: another thread's
window (adom/wiki) briefly popped over John's work between its first paint and the re-assert.
Root: the park placed the window on-screen BEFORE the navigation settled, so the paint-raise was
visible. Now launchSession waits for the page to settle (load event, capped 2.5s, skipped if
already complete) while the window is still OFF-SCREEN, and only then parks (bottom → place →
z-confirm). The first paint's raise hits an invisible off-screen window; the window's first
visible frame is full-size, z-bottomed, z-confirmed. One late re-assert (3s) remains for
late paints. The open response still awaits the full verdict.
## 1.8.67 — 2026-07-15 — Re-assert bottom after the open's navigation (the post-park raise)
Even with 1.8.66's z-confirmed park, the w3 batch sat on top with honest 'backgrounded' verdicts:
the open's FIRST cross-origin nav (file:// welcome → https) swaps renderer processes and Chromium
re-shows the window at first paint — raising it — and heavy sites paint AFTER the park's z-confirm
passed (the real goto runs concurrently with the park). A later same-window navigate does NOT
raise (tested), and neither does the badge (tested). Fix: re-assert z-bottom right after the
open's goto AND once more at the page 'load' event, plus after open_tab's new-tab first paint.
All re-asserts use force:false so a window the user is actively holding foreground is never
yanked. Verified: bottoming sticks once the page settles (8s watch, no spontaneous re-raise).
## 1.8.66 — 2026-07-15 — PLACEMENT DOCTRINE: verify the z-order, revert if unconfirmed
1.8.65's "fix" was worse: AD desktop_set_window_bounds ALSO raises the window (despite
restore:false), and desktop_list_monitors reports logical px while set_window_bounds takes
physical — five windows on top of the user's work AND undersized. Placement is rebuilt around
distrust:
- Geometry via CDP plain bounds only (left/top/width/height, normal state), sized from the page's
own screen.availWidth/Height with retries. No CDP maximize (raises), no AD bounds (raises).
- After placement: RE-BOTTOM via AD state:bottom force, then CONFIRM THE Z-ORDER via AD
desktop_list_windows — pup's window must be strictly below at least one non-pup window and not
topmost. Retried once; if still unconfirmed, the window is moved BACK OFF-SCREEN and the verdict
is park-failed. A window that might be covering the user never stays on-screen.
## 1.8.65 — 2026-07-15 — NEVER use CDP maximize: it RAISES the window (the covering-windows breach)
John caught five "backgrounded" windows stacked at z-1..3 directly under his editor — covering his
work. Root cause, latent since 1.8.55: CDP `Browser.setWindowBounds {windowState:'maximized'}` is
ShowWindow(SW_MAXIMIZE) in Chromium, which RAISES the window to the top of the z-order (no focus
steal, so every focus-based check passed while the windows sat on top). The earlier
"small-then-grow" sighting was the same raise making the staging visible.
- Placement is now AD `desktop_set_window_bounds {restore:false}` (MoveWindow only: no activation,
NO z-order change) sized to the primary monitor from AD `desktop_list_monitors` (cached 10 min).
The window ends visually full-screen, still at the z-bottom where force-bottom put it.
- The park threads the hwnd from the bottoming step into placement (no re-lookup in the common path).
- Dev-skill dead-end added: never use CDP windowState:'maximized' for background placement.
## 1.8.64 — 2026-07-15 — Background windows appear already-maximized (no more small-then-grow)
John: "why when you open a new window is it smaller than my screen and then i see it grow larger?"
Because the park staged the window ON-screen at 1200×800 before maximizing, and a z-bottom window
is visible wherever the user's windows don't cover — so the small stage showed, then the grow.
Now the park stages the window JUST past the LEFT EDGE of the primary monitor (fully invisible but
adjacent, so the OS maximize targets that monitor — maximizing from deep off-screen produces
garbage bounds, tested) and only then maximizes: the window's first visible frame is full-size.
Position-verify and self-heal thresholds tightened from -5000 to -1000 so a window stuck at the
-1310 staging spot reads as failed/heal-worthy instead of healthy.
## 1.8.63 — 2026-07-15 — force:true adopted (AD 1.9.115): the launch park is one AD call
AD 1.9.115 shipped the follow-up: `desktop_set_window_state {state:'bottom', force:true}` bottoms
the window EVEN IF it is currently foreground (verified on ADOMGPU: sentToBack:true despite
wasForeground:true). pup now always passes force:true — the launch park is a single AD call in
the common case. The legacy PS fallback is RETAINED but auto-obsoletes: it only fires on AD
1.9.114 (force ignored, skip-on-foreground) or older (no state:'bottom' at all); delete it once
the fleet is ≥1.9.115. It was kept deliberately — removing it while AdomLapper was still on
1.9.114 would have regressed the post-restart foreground-reassignment case on John's primary box.
## 1.8.61 — 2026-07-14 — 100% direct-API window management (AD shipped state:'bottom')
AD 1.9.114 shipped pup's feature request: `desktop_set_window_state {state:'bottom'}` — native
z-order send-to-back, background drive, title-resolved, returns `{sentToBack, wasForeground}`.
The park now uses it, deleting pup's LAST PowerShell from the open path (one AD call replaces
find_window + the P/Invoke SetWindowPos script). Legacy PS fallback kept for desktops on older AD.
## 1.8.60 — 2026-07-14 — Launch park bottoms UNCONDITIONALLY (escape hatch removed)
"Pup window is foreground at park time" is NOT a user signal: Windows reassigns the foreground to
a fresh window when the previous foreground window was just destroyed (e.g. a bridge-update
restart), which force-foregrounded windows the user never clicked (nxp.com, another thread's
cpconn). The user-click escape hatch is gone; a user who clicked during the ~2s launch gap clicks
once more and the window stays (nothing ever re-parks).
## 1.8.59 — 2026-07-14 — HARD RULE: never move a window on-screen until z-bottom is CONFIRMED
Under a 3-parallel-open load, one window's hwnd lookup lagged, the z-bottom step failed, and the
park moved the window on-screen anyway — an un-bottomed window lands on TOP of the user's work
(the nxp.com pop; no focus stolen, but visually covering, which is just as forbidden).
- The park now retries hwnd-resolve + z-bottom up to 8× (~2.5s); if 'bottomed' is never confirmed,
the window STAYS off-screen and the verdict is an honest `park-failed` — invisible beats popping.
- `park-failed` windows self-heal opportunistically: any browser_status / list query retries the
heal, on top of the existing reattach / open_tab / switch_tab touchpoints.
- 1.8.58: badge calls check AD's real result + log `[badge] applied/FAILED`; the tab-path debounce
arms only on success (one silent failure no longer means a bare icon for 10 minutes).
## 1.8.54 → 1.8.57 — 2026-07-14 — Honest click signal; OS maximize; focus-latch fix; self-heal stranded windows
- **1.8.54:** `GetForegroundWindow` replaces `document.hasFocus()` for user-click detection —
hasFocus LIES for CDP-created windows (false-fired `user-foreground` on every open).
- **1.8.55:** park places via CDP `windowState:'maximized'` (OS-true full size) instead of pixel
math from a screen probe that could fail mid-navigation (shipped 1600×900 windows once).
- **1.8.56:** flash `pending/cleared` focus latch fixed — the hook now also injects into the
CURRENT document (`evaluateOnNewDocument` alone only fires on the NEXT navigation, so clicked
windows kept reporting `pending`).
- **1.8.57 (the adom-shotlog fix):** a window created under a build whose park failed sat
stranded off-screen forever — taskbar click "showed" an invisible window, badge gone. pup now
**self-heals**: cheap CDP bounds check at reattach / `open_tab` / `switch_tab`; only if actually
off-screen, re-park + re-badge (never touches a healthy or user-foreground window). Badge also
re-applies (debounced 10 min) on the tab hot path so long-lived shared windows stay badged
across bridge restarts. Anonymous window CREATORS now get an owner-tip in the open response
(an ownerless long-lived window has zero cross-thread protection). Enriched `browser_describe`
entries for `open_tab`/`switch_tab` (return shape, updateNote, background/click contract).
## 1.8.53 — 2026-07-14 — Direct-API park: CDP + AD verbs replace the PowerShell window-hunt
John caught a parallel-open window (catch-2) still stuck OFF-SCREEN while the bridge claimed
"backgrounded" — the PS park moved the wrong window and its focus-only self-check passed anyway.
And he asked the right question: "why are you using powershell? that's heavy. why aren't you just
calling direct api's?" Rebuilt the park on direct APIs:
- **Geometry via CDP `Browser.setWindowBounds`** — addresses the EXACT window by CDP windowId, so
parking the wrong window is impossible. ~10ms, no process spawn.
- **Window lookup via AD `desktop_find_window`** (native, title-suffix match) — no EnumWindows PS,
no pid guessing.
- **The park runs where the window is BORN** (right after `newPage()` + welcome page in
launchSession) — no wait loop at all; the `await` IS the event. `tryDetachedLaunch` just marks
`_needsPark` (reused windows are never re-parked).
- **Verification checks POSITION** (CDP `getWindowBounds`), not just focus — the exact lie catch-2
told is now detected as `park-failed`.
- **One residual Win32 call**: z-bottom (`osSendHwndToBottom`, single P/Invoke) — CDP has no
z-order and AD has no send-to-back verb yet (AD issue filed). Runs while the window is still
off-screen so its latency is invisible.
- **Badge fires the instant the park lands** (title suffix already set) instead of blind
1.5s/3.5s timers — the teal Adom favicon appears with the window, not seconds later.
## 1.8.52 — 2026-07-14 — Close the launch gap: a click during the first ~2s now shows the window
With 1.8.51, a taskbar click during the launch gap (window still at its off-screen coords, park PS
still starting) activated an invisible off-screen window — the user saw nothing for ~2s. Now the park
checks the foreground first, and the check is UNAMBIGUOUS because `--no-startup-window` means Chrome
cannot raise itself: pup-window-foreground can only be the user. If the user clicked during the gap,
the park places the window on-screen at the TOP full-size and leaves them focused on it
(`user-foreground`); otherwise it parks to z-bottom as usual. Either way it exits — still no loops.
## 1.8.51 — 2026-07-14 — `--no-startup-window`: eliminate the focus steal at the SOURCE (no more war)
John proved a taskbar click within the first couple seconds still got yanked (the 1.8.50 stability
watch cannot tell an early user click from a Chrome self-raise — no watch can). Fix: make the war
impossible instead of fighting it better.
- **Background launches use `--no-startup-window`.** Windows only grants foreground rights to a
process at launch; with this flag the browser starts with NO window, and when pup creates the
window via CDP moments later the browser is a background process — the OS itself DENIES it the
foreground. No steal can happen.
- **Therefore: no stability watch, no timers, no re-parks — deleted.** The park is now: wait for
the window to exist → hand focus back if somehow taken (normally a no-op) → one SetWindowPos to
z-bottom → verify → EXIT. A user click at ANY moment — 200ms in or 2 minutes in — has nothing
running against it.
- Removed the duplicate post-open background re-assert (now only after an explicit sized open,
where CDP setWindowBounds can re-activate; guarded by a foreground check).
## 1.8.50 — 2026-07-14 — One event-driven watchdog replaces the blind-timer park (John: "waiting 1.2s is for the birds")
The launch park no longer fires at guessed delays. `osBackgroundWatchdog` = ONE hidden PS process:
1. WAIT for the real window to EXIST (EnumWindows @50ms, up to 4s) — react to the event, not a timer.
2. The moment it exists: hand focus back (only if Chrome actually took it), THEN park on-screen at
z-bottom (deactivate-first = no pop). Once.
3. Stability watch: if Chrome re-grabs the foreground during startup, hand back + re-park within 50ms;
exit after 500ms of calm (typically <1s total).
4. Exit. Afterwards NOTHING re-parks — a user's taskbar click brings the window up full-size, forever.
The open response awaits the watchdog's SELF-VERIFIED verdict, so `background` is the real outcome,
never a guess mid-park.
## 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
`Page.captureScreenshot` hangs (~27s then errors) even with occlusion disabled. Reverted to the
on-screen-occluded park (which keeps rendering → screenshots work), now correct on all the pieces:
- **Real window handle** via `EnumWindows` (1.8.46) — no more phantom-helper-window misses.
- **Deactivate-then-park order** (1.8.46) — hand focus back while off-screen, THEN move on-screen at
z-bottom, so it never flashes on top (no pop).
- **Short re-park window** (~1.2s: 300/700/1200ms) — just long enough to counter Chrome's startup
foreground-grab. After that pup NEVER re-parks, so a user click is not fought. Within the window,
`GetLastInputInfo` distinguishes a real click (→ bring on-screen + latch off further passes) from
Chrome self-raising (→ re-park).
## 1.8.47 — 2026-07-14 — Background = MINIMIZE (no more re-park loop fighting the user's clicks)
The on-screen-z-bottom park needed a re-park retry loop to counter Chrome's startup foreground-grab,
and that loop fought the user: click a window to view it and pup yanked it back. Rebuilt the background
primitive around **minimize**, which removes the whole conflict:
- **Minimizing hands the foreground back to the user automatically** (no foreground-lock fight, no
AttachThreadInput) — no focus steal, and no visible pop (the window launched off-screen).
- **The restore rectangle is set on-screen** (`SetWindowPlacement`), so a taskbar click restores the
window centered + visible — **and it STAYS**, because there is NO on-screen re-park loop to yank it.
- **Screenshots still work** because Chrome already launches with occlusion detection disabled
(`--disable-features=CalculateNativeWinOcclusion` + `--disable-backgrounding-occluded-windows`), so a
minimized window keeps compositing frames.
- The only late passes are safety re-minimizes that RESPECT a user restore: the instant the window is
un-minimized by a real click (physical input), pup latches `_userForeground` and never touches it again.
- `browser_lower_os_window` / post-resize re-park also minimize now. `raise` restores via CDP as before.
## 1.8.46 — 2026-07-14 — Resolve the REAL browser window (the background park was targeting a phantom)
**The bug behind "I click a pup window and it never shows."** The background park resolved the window via
`Get-Process(pid).MainWindowHandle`, which on some boxes (John's laptop) returns a **hidden Chrome helper
window**, not the real browser window. So `SetWindowPos` moved a phantom, the real window stayed at its
off-screen launch coords (`screenX -21333`), and the "backgrounded" self-check passed against the phantom —
a **false positive**. Clicking the taskbar activated an off-screen window = nothing visible. Proven by
contrast: CDP `Browser.setWindowBounds` (which `raise` uses) put the same window at `0,0 1708×1068` first try.
- **Resolve the real window by ENUMERATION** (`EnumWindows`): the largest VISIBLE top-level
`Chrome_WidgetWin_1` owned by the browser's pid (or its children). Replaces the flaky `MainWindowHandle`
in `osBackgroundWindowByPid` AND `osClearFlashByActivate`. The park now actually moves the real window
on-screen at z-bottom, so a taskbar click shows it — and the self-verify is honest.
## 1.8.45 — 2026-07-14 — User↔AI window balance + real flash clear + programmatic flash state
Three related fixes, all from John's live testing. Governing rule now written into `pup-bridge-dev`
("The user↔AI balance"): pup windows are background AI tasks that must not disturb the user, AND real
windows the human must be able to click, SEE, and use normally — both at once.
- **A user click now actually SHOWS the window.** v1.8.44 made the re-park respect a deliberate user
raise, but it *bailed without moving the window on-screen* — so clicking a still-off-screen window
activated an invisible window (user saw nothing). Now the user-raise path brings the window ON-SCREEN
(centered ~80%, `SWP_SHOWWINDOW`, HWND_TOP) and leaves it foreground. Background↔foreground handoff.
- **`browser_alert_window {stop}` truly clears the flash.** `FLASHW_STOP` only stops the *pulse*; the
Win11 attention tint persists until the window is ACTIVATED. New `osClearFlashByActivate` clears it by
activating the window OFF-SCREEN (invisible → tint clears) then handing focus back — no pop, no focus
steal. Reports a real `cleared` / `already-foreground`, never a fire-and-forget guess.
- **Programmatic flash state.** `getSessionInfo.flash` = `none|pending|cleared` (from `_flashState` vs an
injected `window.__pupFocusTs` focus latch) — answers "which windows are still flashing?" without a
screenshot.
## 1.8.44 — 2026-07-14 — Respect a deliberate user raise (click-to-watch no longer forced back)
A user clicking a background pup window's taskbar button to WATCH a page load saw it snap
back to the background. Cause: the open-time re-park passes (600ms/1.4s/2.5s) that counter
Chrome's startup self-raise ran `SetWindowPos HWND_BOTTOM` **unconditionally**, so a click
landing inside that 2.5s window got yanked back.
- **`osBackgroundWindowByPid(..., respectUser)`** now distinguishes a real user raise from
Chrome self-raising: if the pup window is foreground AND `GetLastInputInfo` shows a physical
input within the last ~1.5s (Chrome's self-raise is programmatic — no input), it returns
`"user-foreground"` and skips the park.
- The retry driver **latches** on that (`_userForeground`) and **cancels the remaining passes**
— once you take the window, pup never fights you for it. The post-resize re-park honors the
same latch. The initial off-screen park is unaffected (runs with `respectUser=0`; you can't
have clicked an off-screen window).
## 1.8.35 → 1.8.43 — 2026-07-13/14 — Flash routes through AD; taskbar badge; snappy flash; cleanup nudge
- **Taskbar flash + clear route through AD-core** (`desktop_flash_window`) instead of pup's own
`FlashWindowEx` — one mechanism, native, reliable. `browser_alert_window {stop}` now returns
AD's real `applied` result (no more fire-and-forget `cleared:true` lies). Snappy: flashes on
open before the page finishes loading.
- **Adom favicon badge** overlaid on the pup taskbar button via AD's `desktop_taskbar` overlay.
- **`🧹 CLEANUP` hint** in the open response + `browser_status` when windows are ≥30m old or ≥5 open.
## 1.8.24 → 1.8.34 — 2026-07-13 — Background-by-default, actually correct (no pop / no cover / no focus steal / no cmd flash)
The long-standing "pup window pops up, covers my work, steals my typing" problem, solved properly and
made deterministic + self-verifying. **The full technique + the dead-ends are documented in the
`pup-bridge-dev` skill ("Background-by-default without stealing focus") — read that before touching window
placement; do NOT re-derive it.** Summary:
- **Launch OFF-SCREEN** (`--window-position=-32000,-32000`, not `--start-maximized`) so it never visually
pops; CDP still renders it (screenshots work).
- **Park on-screen at z-bottom** sized to the monitor (`SetWindowPos HWND_BOTTOM SWP_NOACTIVATE`) → behind
the user's windows (invisible) but taskbar-clickable.
- **Hand keyboard focus back with `AttachThreadInput`** (snapshot the user's window pre-launch; attach
input threads to bypass the foreground lock; `SetForegroundWindow` back to it; re-run over ~2.5s as the
browser re-grabs during startup). Deterministic `backgrounded` instead of a flaky focus steal.
- **All window-management PowerShell runs HIDDEN** (`execFileSync` + `windowsHide` + `-WindowStyle Hidden`
via `runPsHidden`), so no cmd/terminal window ever flashes.
- **Self-verifying:** every open re-reads `GetForegroundWindow()` and reports a `background` field
(`backgrounded`/`still-foreground`). navigate/reload/switch_tab no longer re-raise a background window.
- Dead-ends rejected (all worse): start-maximized+lower (visible pop), bare `SetForegroundWindow` (lock),
minimize-and-leave (breaks screenshots), `WS_EX_NOACTIVATE` (breaks taskbar-click + Chrome self-activates),
minimize-loop (flicker), pup-side localhost probe (AD already reprimands localhost on failure).
## 1.8.23 — 2026-07-10 — Agent-update signalling (auto-flash), keystroke-safe raise, render_html
Field report from an agent that staged a 4-tab task and lost track of which window it was updating.
All five items shipped (AD relays verb args verbatim → no AD-side change):
- **Auto-flash on agent updates.** Mutating verbs (`open_window`/`open_tab`/`navigate`/`reload`/`back`/
`forward`/`switch_tab`/`click`/`type`/`press_key`/`input_dispatch`/`eval`/`evaluate`) flash the
window's taskbar via real `FlashWindowEx` (`FLASHW_TRAY|FLASHW_TIMERNOFG`) so the user notices the
agent touched it — **never steals focus**, **self-suppresses while foreground**, **clears on click**.
Debounced: one flash per window per ~5s burst. Suppress per-call with `{silent:true}`, treat a read as
read-only with `browser_eval {readOnly:true}`, or silence a whole session with
`browser_open_window {autoFlash:false}`.
- **`updateNote` + `lastAgentUpdate`.** Pass `updateNote` on any mutating verb; pup records
`{tabId,verb,note,ts}` and surfaces it top-level **and per-tab** in `browser_list_tabs` /
`list_windows` / status — answers "which tab did the agent just change?".
- **Raise no longer leaks keystrokes.** `browser_raise_os_window`/`focus_window` blur the page's active
element before `bringToFront()`. Docs steer agents to `browser_alert_window` (or the auto-flash) for
"I updated this", reserving raise for an explicit show-me.
- **`browser_open_tab` returns `tabId` + `tabCount`** explicitly (no more diffing `list_tabs`).
- **New `browser_render_html {sessionId,html,waitUntil?,updateNote?,silent?}`** — renders a local HTML
string via `page.setContent` (UTF-8-correct; a `data:` URL mojibakes accented chars without a charset).
- Note: `desktop_screenshot_window titleContains/sessionId` is an **AD-core** verb — routed to AD, not
pup. The `fullPage` nested-scroll `nestedScroll:true` warning shipped in 1.8.21.
## 1.6.0 → 1.8.17 — 2026-07 — Generic browser, background-by-default, cross-thread ownership, bridge→AD notify
Consolidated summary of the arc since 1.1.0 (per-release detail lives in the wiki Releases tab):
- **Became a generic, non-profile browser.** Removed all native-real-browser drive code — driving the
user's real signed-in Chrome/Edge is now the Adom browser **extension's** `nbrowser_*` job. Every pup
session is a fresh isolated Chrome/Edge process. Installed-browser-first: Chrome → Edge → cached CfT,
spawn-verified with fallthrough; Chrome for Testing only as a last resort.
- **Loopback bind** (`ADOM_BIND_HOST`, 127.0.0.1) so no Windows Firewall prompt.
- **Background by default → hard guarantee (1.8.17):** windows never steal focus; sizing/positioning no
longer foregrounds either. `foreground:true` is the only way to show a window.
- **Window ownership (1.8.16):** sessions carry an advisory `owner`; declared cross-thread navigate/close
is refused (`session_owned_by_another_thread`), anonymous grabs are reprimanded — stops AI threads
stealing each other's windows. `browser_status`/`list_windows` expose `owner` + `ageMinutes`.
- **Chrome install drives the user through the UAC (1.8.12–1.8.15):** on a locked-down box, escalates to
an elevated install and — via AD ≥1.9.84's direct-API — fires a native "click YES" toast from the
bridge itself, pinging the attended device (cross-AD `target`) and re-arming if it expires. Degrades
cleanly on older AD (raise UAC once + relay hint, never spam).
- **Missing verbs + catalog completeness (1.8.8–1.8.11):** added browser_click/type/press_key/back/
forward/evaluate and documented 13 working-but-unadvertised verbs in `bridge.json` + `browser_describe`.
- **Title-suffix resilience (1.8.17):** persistent `evaluateOnNewDocument` injector so AD's title-based
raise/lower window lookup stops flaking right after a navigation.
- **Skillpack restructure:** public `pup` + `pup-bridge-dev` + `pup-bridge-publish` as
`skills/<name>/SKILL.md` (`user-invocable:false` on the dev/publish pair).
## 1.1.0 — 2026-06-29 — Cold-start self-heal + ownership extraction
**Why:** On a fresh PC (Hydrogen Desktop bundles Adom Desktop; the installer pre-installs neither
Node nor Chrome for Testing), the first "open … in pup" failed with a generic
"Failed to launch Chrome after 3 attempts" + a developer-only string ("run npx puppeteer browsers
install chrome") — no `errorCode`, no auto-install, no actionable hint. The AI couldn't recover.
**Fixed — the bridge now self-heals Chrome for Testing (CfT):**
- **`src/chrome.js`** (new) — detect (validates the *actual* executable, not just "some dir exists"),
auto-install programmatically via the bundled `@puppeteer/browsers` (no npx), pinned to the exact
buildId this puppeteer expects, with download-progress + install-state dedupe + a readiness snapshot.
- **`browser_open_window`** — a cold-start gate (CfT path only; native-drive skips it) that detects a
missing CfT, kicks off a NON-BLOCKING background install, and returns a structured
`{ errorCode:"chrome_for_testing_installing", installing:true, statusVerb:"browser_readiness",
_hint:"poll browser_readiness then retry" }` — instead of blindly spawning a missing binary 3× then
throwing. On install failure → `chrome_install_failed`/`chrome_download_failed` with a real hint
(no more silent continue-to-doomed-launch).
- **`browser_readiness`** (new verb) — `{ ready, chromeForTestingInstalled, installing,
installProgressPct, ... }`. Poll until `ready:true`.
- **`browser_prewarm`** (new verb) — install CfT WITHOUT opening a window (warm before first use);
`{wait:true}` blocks.
- **Status chip** — `/status` now emits `led`/`summary`/`tooltip` and tells the TRUTH: yellow
"Installing Chrome N%" / "Chrome not installed", red on install failure, green when ready (was always
green, even with no Chrome). `/status` + `/health` both carry `version` + `chrome` readiness.
- **Deep launch throw** is now Chrome-aware (structured `chrome_for_testing_not_installed` when CfT is
genuinely missing vs a memory/locked-profile failure when it's present).
**Hygiene:**
- `BRIDGE_VERSION` ↔ `bridge.json` version lockstep fixed (was 1.0.14 vs 1.0.16).
- `bridge.json`: `statusVerb:"browser_readiness"` + `updateManifestUrl` so AD auto-updates from this
page; new verbs registered in `verbs[]` + `browser_describe`.
**Ownership:** extracted from `adom-desktop/plugins/puppeteer` into this standalone wiki repo
(`wiki.adom.inc/adom/adom-desktop-puppeteer-bridge`), mirroring the cloud-owned kicad/fusion model.
Adom Desktop keeps a bundled SEED for first-run/offline and auto-pulls newer versions from here via
`updateManifestUrl`.