name: pup-window-targeting description: THE LAW for how pup finds a session's OS window for any Bridge window verb - ALWAYS by its resolved HANDLE (hwnd), NEVER by title. Title lookup is unreliable (titleTag is off by default and pages rewrite their own ) and it has independently broken the overlay paint, the park, the taskbar flash, the AUMID stamp, AND the jump list - the same bug, over and over. READ THIS before adding or touching ANY chrome.adCommand('desktop_*') call that acts on a window, or before adding a desktop_find_window resolver.</h2> <h1>pup window targeting: hwnd, never title (the recurring bug, killed)</h1> <p>John, across ONE session, caught this same class of bug break FOUR separate features, each of which I "fixed" one at a time before he snapped: "how many times did you say you couldn't look up a window by title and it messed up your algorithm... you keep duct-taping it instead of a permanent solution." He was right. This file is the permanent solution so it never regresses.</p> <h2>The disease</h2> <p>Every one of these subsystems, independently, tried to find a session's OS window by matching <code>(ai-thread: <sid>)</code> in its title (via <code>desktop_find_window</code>/<code>titleContains</code>), and every one FAILED the moment the title wasn't there:</p> <ul> <li><strong>Overlay paint</strong> - windows came back with no badge / <code>hwnd=None</code>.</li> <li><strong>Park (z-bottom + place)</strong> - windows stranded off-screen or never bottomed.</li> <li><strong>Taskbar flash</strong> - "did not match the window (title changed?)".</li> <li><strong>AUMID stamp</strong> - <code>"No visible window with title containing (ai-thread: pup-dashboard..."</code> → the dashboard window never stamped, so only SOME windows went teal (the mismatch John kept seeing).</li> <li><strong>Jump list</strong> - <code>updateWikiJumplist</code> resolved hwnd by title, got null, so <code>payload.hwnd</code> was never set and the list never committed ("i turned on jump lists and none of them work").</li> </ul> <p>Why title is fundamentally unreliable, and will NEVER be the mechanism:</p> <ol> <li><strong><code>titleTag</code> is OFF by default</strong> - John turned it off on purpose; he does not want <code>(ai-thread: X)</code> decorating his window titles. So most windows carry NO tag to match.</li> <li><strong>Pages rewrite <code>document.title</code></strong> as they load (SPAs constantly). Even a transient tag gets wiped before a find runs. The old re-stamping MutationObserver that fought this caused its own freezes.</li> </ol> <h2>THE LAW</h2> <p><strong>Every AD window verb targets the window by its resolved HWND, via <code>windowTarget()</code>. Never by title.</strong> <code>titleContains</code> survives ONLY as the cold fallback inside <code>windowTarget()</code> itself.</p> <pre><code class="language-js">// returns { hwnd } (validated, read-only) or, only if we truly have no handle, { titleContains } const tgt = await windowTarget(session, sessionId); await chrome.adCommand('desktop_taskbar', { ...tgt, overlay: {...} }); await chrome.adCommand('desktop_set_window_state', { ...tgt, state: 'bottom', force: true }); await chrome.adCommand('desktop_set_window_identity',{ ...tgt, appId, ... }); // STAMP await chrome.adCommand('desktop_set_window_jumplist',{ hwnd: session._hwnd, appId, tasks }); </code></pre> <p><code>windowTarget()</code> resolves + validates <code>session._hwnd</code> (via <code>hwndBelongsToPup</code>), read-only, and NEVER touches window geometry. Spread it into the verb args. That is the whole rule.</p> <h3>When you add a new window verb</h3> <p>Spread <code>...(await windowTarget(session, sessionId))</code>. Do NOT write a fresh <code>desktop_find_window({ titleContains })</code> to "get the hwnd" - that is the exact anti-pattern that keeps coming back. If you catch yourself typing <code>titleContains: \</code>(${TITLE_LABEL}<code>in anything other than the</code>windowTarget` fallback, STOP.</p> <h2>Where the handle comes from (so the fallback stays cold)</h2> <p>Resolution is READ-ONLY. It never resizes or moves a window (see the wiggle grave below). Priority:</p> <ol> <li><strong>Cached <code>session._hwnd</code></strong>, validated by <code>hwndBelongsToPup</code> (rejects a stale/recycled/foreign handle - e.g. one now owned by Edge or another profile's process).</li> <li><strong>Birth-time capture</strong> - <code>createPageInNewWindow</code> 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 <code>page.__pupHwnd</code>, trusted first.</li> <li><strong>Persisted handle</strong> - <code>_hwnd</code> is written to the session file and restored on EVERY adoption path (<code>recoverSessions</code> AND <code>rescanProfile</code>). A Chrome window keeps the SAME hwnd across a bridge respawn, so a recovered window restores its handle instead of re-resolving.</li> <li><strong>Sole-window-of-a-profile</strong> - if a profile's process owns exactly one top-level Chrome frame, that IS the window (works even maximized).</li> <li>If none of the above: <strong>leave it BARE</strong> (no overlay). Bare > wrong, forever (John's Edge-overlay rule). NEVER guess.</li> </ol> <h2>⚰️ The width-wiggle is DEAD. Do not resurrect it.</h2> <p>For months, same-profile windows (which share the identical parked rect) were disambiguated by temporarily SETTING A UNIQUE WIDTH via CDP, enumerating, and restoring. It is GONE (v1.9.308) because:</p> <ul> <li>It never worked on <strong>maximized</strong> windows (CDP can't resize a maximized window) - it was matching by luck/collision the whole time.</li> <li>Its DPI-scale guessing was promiscuous and <strong>collapsed multiple windows onto one handle</strong>.</li> <li>When a restore failed it left a window <strong>stuck narrow</strong> - John: "why did you resize this window so weird? what the fuck?" Deforming the user's window is never acceptable.</li> </ul> <p>There is no invisible way to geometry-probe an on-screen window. Birth-time capture + persistence is the answer. If you think you need the wiggle, you need birth-time capture instead.</p> <h2>One-line summary</h2> <p>Target windows by handle through <code>windowTarget()</code>, resolve the handle read-only (birth-time + persist, never resize), leave bare when unsure, and never, ever look a window up by its title again.</p>