← Commit history

NEW PLACEMENT RULES (John, 2026-08-20): windows are born on-screen at their final rect, background is a preference not a precondition, a taskbar click always works, identity is captured at birth, taskbar branding is async and after. Single source of truth in pup-bridge-dev R1-R5; the other skills now defer to it instead of contradicting it.

John Lauer ·edcdf3dee3 ·2mo ago ·parent 3f1ca1c
3 files changed +90−69
dev-skills/pup-bridge-dev/SKILL.md+60−62
@@ -340,74 +340,72 @@ the user's keystrokes land in the invisible pup window. Requirements that must h R1 never visually pop / cover the user's work · R2 the window is reachable (taskbar-clickable) · R3 CDP `pup_screenshot` still works · R4 never steal keyboard focus · R5 no console/terminal flash. -**The working design (v1.8.51+). The KEY insight, found after a week of warring with re-park loops:-`--no-startup-window` eliminates the focus steal at the SOURCE, so there is NO war to fight.**+**THE RULES (John, 2026-08-20). These SUPERSEDE every earlier placement text in this repo.** They+were set after a pup window opened off-screen behind a live taskbar button for the second time in one+day: *"you need to allow a user to open a pup window immediately if they want to."* -Windows only grants a process the right to take the foreground AT LAUNCH. With `--no-startup-window`-the browser process starts with NO window at all; pup creates the window via CDP (`newPage()`) moments-later, by which time the browser is a background process and **the OS itself denies it the foreground**.-No steal is possible → no watchdog, no timers, no re-park passes exist → **a user's taskbar click at ANY-moment (200ms in or 2 minutes in) has nothing running against it and always sticks.** Do NOT add any-loop that re-parks after the initial park — every such loop, no matter how clever (input-timing, short-windows, stability watches), eventually yanks a real user's click. John proved each variant failed.+**R1 BIRTH IT ON SCREEN, IN ONE CALL.** `Target.createTarget` accepts `left/top/width/height`, so the+window is CREATED at its final geometry: the PRIMARY MONITOR's work area inset **10px on all four+sides**. There is no staging position, no migration, no "place it once we are sure". The window is+reachable from its first millisecond because it was never anywhere else. -**THE FINAL ALGORITHM (v1.8.55, John-verified "those seemed to open nicely") — DIRECT APIs, one-tiny Win32 call, no PS window-hunting, no timers, no loops.** Lives in `tryDetachedLaunch` (flag) +-`parkSessionWindowDirect` + `osBottomUnlessForeground`:+**R2 BACKGROUND IS THE PREFERENCE, NOT A PRECONDITION.** `--no-startup-window` is what actually keeps+the foreground (the browser process holds no foreground rights by the time CDP creates the window), and+it works with an on-screen rect exactly as well as an off-screen one. If it fails anyway and the window+comes up FOREGROUND: **let the flaw exist.** Log it, move on. Do not fight it, do not move the window,+do not re-park it. John's words: *"just allow that flaw to exist."* Fighting this is what produced every+re-park loop, and every one of those eventually yanked a real click. -1. Spawn with `--no-startup-window --window-position=-32000,-32000` (no `--start-maximized`).-   `tryDetachedLaunch` only sets `_needsPark` — there is NO window yet to manage.-2. Connect CDP; in launchSession `newPage()` creates the window (off-screen, unfocused — the OS-   denied it the foreground). **The park runs right here — the `await` on newPage IS the-   "window exists" event. No wait loop, no polling.**-3. `parkSessionWindowDirect(page, sessionId)`:-   - hwnd via **AD `desktop_find_window`** (native, matches the "(session: <id>" title suffix the-     welcome page just set), retried together with the bottoming below (~8 × 300ms).-   - **Z-bottom UNCONDITIONALLY via AD-core** (v1.8.63): ONE call —-     `desktop_set_window_state {titleContains, state:'bottom', force:true}` (AD ≥1.9.115; both-     verbs shipped from pup's feature requests). `force:true` bottoms even a foreground window.-     There is deliberately **NO user-click escape hatch at park time** — see the dead ends:-     "foreground at park" has non-user causes and honoring it forced never-clicked windows onto-     John's screen. A legacy PS fallback (`osBottomUnlessForeground`) remains ONLY for desktops on-     AD ≤1.9.114 (no state:'bottom', or force ignored + skip-on-foreground) — delete it once the-     fleet is ≥1.9.115.-   - **HARD RULE: never move the window on-screen until 'bottomed' is CONFIRMED.** If bottoming-     can't be confirmed after the retries, return an honest `park-failed` and leave the window-     invisible off-screen — the self-heal (reattach / open_tab / switch_tab / any status query)-     retries it. An un-bottomed window moved on-screen lands ON TOP of the user's work (the-     nxp.com pop, v1.8.59).-   - **Geometry via CDP plain bounds ONLY** (`Browser.setWindowBounds {left,top,width,height}`,-     normal state), addressed by windowId — parking the WRONG window is structurally impossible.-     Size comes from the page's own `screen.availWidth/Height` WITH RETRIES (a single probe can-     fail mid-navigation; retry, don't fall back). NEVER `windowState:'maximized'` and NEVER AD-     `desktop_set_window_bounds` — BOTH RAISE the window (see dead ends).-   - **TRUST NOTHING — verify the Z-ORDER after placement** (v1.8.66): re-bottom after the move,-     then confirm via AD `desktop_list_windows` that pup's hwnd is NOT topmost and sits below at-     least one non-pup window. Unconfirmable after a retry → move the window BACK OFF-SCREEN and-     report `park-failed`. A window that might be covering the user never stays on-screen. This-     check is what caught ten versions of placement bugs the focus-based checks all missed.-   - **Self-verify POSITION** via CDP `getWindowBounds` (left/top > -5000), NOT focus — a-     focus-only verify passed while the window sat off-screen ("backgrounded" lie).-   - **The launch-gap contract:** a user who clicks the taskbar button within the ~2s before the-     park lands may see nothing (window still off-screen) or get parked once — they click ONCE-     more and the window comes up and STAYS (nothing re-parks after the park exits). That is the-     accepted trade; do not "improve" it with foreground detection.-4. The open response awaits the verdict (`_bgPromise`); badge (`badgePupTaskbar`) + flash fire the-   INSTANT the park lands — no blind 1.5s/3.5s badge timers.-5. Nothing ever re-parks. `foreground:true` opens skip all of this and launch maximized on-screen.-6. Remaining AD gap (issue filed): a `send-to-back` window state; when AD ships it, delete-   `osBottomUnlessForeground` and pup is 100% PS-free.+**R3 A TASKBAR CLICK MUST WORK, ALWAYS, WITH NO HELP FROM PUP.** The moment the user clicks the button,+the window comes to the foreground and they see it. This is a REQUIREMENT, not a trade. Any state that+breaks it — off-screen, hidden, cloaked, `WS_EX_NOACTIVATE` — is forbidden as a resting state and as a+transient one. There is no "launch-gap contract" any more; the old text promised ~2s and the shipped+code could hold a window unreachable for **42 seconds** (8 bottom attempts x a 5s ab timeout + sleeps). -**Supporting principles (subordinate to the algorithm above — where older text conflicts, the-algorithm wins):**+**R4 IDENTITY IS CAPTURED AT BIRTH, EXACTLY, AND NEVER LOST.** The hwnd is resolved in the same locked+moment the window is created and stashed on the page. See `pup-window-targeting`; the mechanism is a+set-diff under `withBirthLock` filtered to this profile's Chrome PIDs, which is exact WITHOUT depending+on a unique birth coordinate (that dependency is what forced the off-screen birth in the first place). -1. **Launch OFF-SCREEN** → satisfies R1. Background windows launch with-   `--window-position=-32000,-32000` (NOT `--start-maximized`; strip it when `!foreground`). The window is-   created far past any monitor, so nothing ever appears on screen during startup. CDP renders regardless-   of position, so screenshots/eval/record all still work.-2. **The resting state is ON-SCREEN at the bottom of the z-order** → satisfies R2 + R3.+**R5 THE TASKBAR IDENTITY IS ASYNC AND COMES AFTER.** AUMID stamping, tile art, jump lists, overlay+badges and the button nudge are ALL slower than R1 and must never gate it. The window is on screen and+clickable long before it is correctly branded, and that is the intended order. A user who clicks an+unbranded button still gets their window.++### What this replaced, and why the old text was dangerous++The v1.8.55 playbook had it backwards: it made placement CONDITIONAL on proving z-bottom+(*"never move the window on-screen until 'bottomed' is CONFIRMED"*, *"Unconfirmable → move the window+BACK OFF-SCREEN"*). It listed **R2 the window is reachable (taskbar-clickable)** as a requirement that+must hold simultaneously with the others, then traded it away one paragraph later without saying so.+And the proof it waited for fed its own failure: `zConfirmedBelow` looks the hwnd up in ab's window+enumeration and returns false when absent, while an off-screen window may not be enumerated at all.+Once a window was parked off-screen it could never confirm its way back out.++It also contradicted `pup-user-foreground` rule 1 (*"never off-screen"*, v1.9.133/134), which John set+AFTER his "click the flash, see nothing" bug specifically to reverse this. Two skills disagreed, the+code followed the older one, and either could be cited to justify whatever the code did. That is why+this section is now the single source for placement and the others defer to it.++**Still true, still load-bearing (do not re-derive):**+- `--no-startup-window` is the focus fix, at the source. Keep it.+- Geometry via **CDP bounds only**, addressed by windowId. NEVER `windowState:'maximized'`, NEVER ab+  `desktop_set_window_bounds` — both RAISE the window.+- **Nothing re-parks a window the user has raised.** `userIsUsingWindow()` gates every demotion.+- Bottoming still happens, but only as a best-effort ASSERTION on an already-correctly-placed window.+  It may never move, hide, or off-screen a window as a consequence of failing.+- `foreground:true` opens skip the background dance entirely and come up on-screen, as before.+++**Supporting principles (subordinate to THE RULES above — where older text conflicts, the rules win):**++1. **~~Launch OFF-SCREEN~~ — STRUCK 2026-08-20.** This was the root of every "I clicked the taskbar+   button and nothing came up" report. Windows are born at their FINAL on-screen rect (R1). The+   `--window-position=-32000,-32000` launch arg is also gone (removed in v1.9.135 for a separate+   reason: it poisoned the restore rect of every Chrome-created window, including popups).+2. **The resting state is ON-SCREEN at the bottom of the z-order** → and now the BIRTH state too.    On-screen-occluded (behind the user's windows, taskbar-clickable, still compositing) is the ONLY-   resting state that keeps screenshots working — see the minimize dead-end.+   state that satisfies R3 while keeping screenshots working — see the minimize dead-end. 3. **DIRECT APIs, not PowerShell** (John: "why are you using powershell? that's heavy. why aren't you    just calling direct api's?"). Geometry = CDP `Browser.setWindowBounds` by windowId (exact window,    ~10ms). Window lookup = AD `desktop_find_window`. Foregrounding = AD `desktop_bring_to_front`.
dev-skills/pup-user-foreground/SKILL.md+16−4
@@ -32,9 +32,17 @@ loaded gun pointed at the user's click.  ## The doctrine (current, settled) -1. **Background means BEHIND, never hidden.** Park = on-screen, full work-area size, z-BOTTOM-   (never off-screen, never minimized). A taskbar click then reveals a correctly-placed window-   with no help from pup (v1.9.133/134, after the "click the flash, see nothing" bug).+1. **Background means BEHIND, never hidden — and now, never ANYWHERE ELSE FIRST.** As of+   2026-08-20 this is no longer just the resting state, it is the BIRTH state: a window is created+   at its final on-screen rect (primary work area, 10px inset) in the `Target.createTarget` call+   itself, at z-bottom, never off-screen and never minimized. A taskbar click reveals it with no+   help from pup, from the first millisecond of its life.+   You were right in v1.9.133/134 (after the "click the flash, see nothing" bug) and this rule was+   correct the whole time; what it lacked was force over the older `pup-bridge-dev` playbook, which+   said the opposite ("leave the window invisible off-screen") and which the CODE was following.+   **THE PLACEMENT RULES NOW LIVE IN ONE PLACE: `pup-bridge-dev` → THE RULES (R1-R5).** This file+   governs the user-vs-pup FIGHT (who may raise, who may demote); it defers to those for geometry.+   If you find any text anywhere that permits an off-screen window, it is stale — delete it. 2. **User intent is read from the OS, atomically, inside the demotion primitive.**    `GetForegroundWindow() == our hwnd` AND `GetLastInputInfo` idle < 5s means a HUMAN raised it    (a keyboard/mouse was active). A programmatic self-raise happens with idle hands. This check@@ -69,8 +77,12 @@ loaded gun pointed at the user's click.    - **Any hide puts its re-show in a `finally`,** so a throw cannot strand the window;    - **the caller verifies the window came back,** because a KILLED process runs no finally —      verification has to live where the kill cannot reach;-   - **a 120s sweep asserts the invariant for the whole population** (`sweepVisibility`), so any+   - **a 30s sweep asserts the invariant for the whole population** (`sweepVisibility`), so any      future path that hides a window is repaired whether or not its author remembered this page.+     It is a BACKSTOP, not the fix. John, when told a stranded window was recovered in 50s:+     *"50 seconds is a fucking lifetime. the user clicks a pup icon within 500ms if they really+     want to see what's going on in that window."* If you ever find yourself sizing this interval,+     you are working on the wrong end of the problem — go make the bad state unreachable instead.    The re-show restores the state we FOUND: `SW_SHOWMINNOACTIVE` for a window the user had    minimized, `SW_SHOWNOACTIVATE` otherwise. The old code always used the latter, so every nudge    silently un-minimized windows John had deliberately put away.
dev-skills/pup-window-targeting/SKILL.md+14−3
@@ -59,9 +59,20 @@ Resolution is READ-ONLY. It never resizes or moves a window (see the wiggle grav  1. **Cached `session._hwnd`**, validated by `hwndBelongsToPup` (rejects a stale/recycled/foreign    handle - e.g. one now owned by Edge or another profile's process).-2. **Birth-time capture** - `createPageInNewWindow` births each window at a UNIQUE off-screen position-   and reads its handle THERE, while it is off-screen and small, before park maximizes it. That is the-   only moment a same-profile window is unambiguous. Stashed on `page.__pupHwnd`, trusted first.+2. **Birth-time capture** - `createPageInNewWindow` resolves the handle in the SAME LOCKED MOMENT it+   creates the window, and stashes it on `page.__pupHwnd`. Trusted first, and it must be EXACT: rule+   R4 of the placement rules in `pup-bridge-dev` makes never losing track of a window a requirement,+   not a best effort.+   **The mechanism changed on 2026-08-20 and the reason matters.** It used to birth each window at a+   UNIQUE OFF-SCREEN coordinate and match on that coordinate, because same-profile windows otherwise+   share an identical rect. That worked, but it meant IDENTITY was the thing forcing every window to+   be created somewhere the user could not click - and that is exactly how windows ended up stranded+   off-screen behind live taskbar buttons. Identity must never again dictate placement.+   The replacement needs no unique coordinate: hold `withBirthLock`, snapshot the top-level windows+   owned by THIS profile's Chrome PIDs, create the window, snapshot again, and the single new handle+   is ours. The lock guarantees exactly one pup birth at a time; the PID filter excludes anything the+   user opens in the same instant. That is exact even though every window is now born at the same+   on-screen rect. 3. **Persisted handle** - `_hwnd` is written to the session file and restored on EVERY adoption path    (`recoverSessions` AND `rescanProfile`). A Chrome window keeps the SAME hwnd across a bridge    respawn, so a recovered window restores its handle instead of re-resolving.