The window-placement invariant (next-session spec, from John's 2026-07-26 stranding)

The incident

agenda-monday was alive and healthy but parked at -32768,-32768 (the background-by-default park). John clicked its taskbar button; Windows activated a window 32,000px outside every monitor; it looked dead. Every backgrounded pup window carries this trap today.

The invariant (one sentence)

A pup window is EITHER background-and-parked OR foreground-and-on-screen — never foreground/user-activated while off-screen, and never on-screen while claiming to be parked.

PRIMARY FIX (John's design, 2026-07-26): settle on-screen at open — don't stay parked

The off-screen park is only needed for the LAUNCH INSTANT (Chrome's foreground grab; already neutralized by --no-startup-window). Once the open settles, MOVE the window to its real on-screen rect (work-area of the target monitor) with SWP_NOACTIVATE + HWND_BOTTOM — visible position, bottom of z-order, zero focus steal, still covers nothing the user is working on. Then a later taskbar click is just Windows foregrounding a correctly-placed window: no pup code in the path, nothing to catch. Known tradeoff, accepted: the window shows in desktop-peek / when everything else is minimized. The enforcement layers below become the SAFETY NET for drift (recovery, monitor changes, DPI moves), not the primary mechanism.

The safety net: ONE enforcement function, MANY triggers

Write a single enforcePlacement(session, sessionId, cause) that reads the real OS rect + foreground state and repairs violations (slide on-screen if user-activated; re-park if a background window drifted on-screen half-visible). Then call it from every layer:

  1. User-activation watcher (the big missing catch): poll osGetForegroundWindow() every ~2s against parked sessions' HWNDs. A parked window that BECAME foreground = the user clicked its taskbar button = auto-slide on-screen. This makes plain left-click Just Work.
  2. Every reveal path: raise_os_window, foreground:true opens, wiki_set_view's bringToFront on the unchanged-view branch (AUDIT: today it foregrounds without un-parking — same trap), focus_window from the jump-list header.
  3. Recovery: after bridge restart, re-assert placement for every recovered session (windows survive restarts since 1.9.122, so stale park state now persists too).
  4. The periodic sweep: the same maintenance loop that reaps orphans asserts the invariant and repairs violations.
  5. Visibility: every auto-repair goes through recordCleanup() (the report-once queue) so the next AI caller and eventually John SEE that a window was rescued, per his design.
  6. Verb responses: open/list responses should carry parked: true|false so AIs stop guessing where a window physically is.

Also queued next session

  • One flash per logical operation (John, 2026-07-26): auto-flash fires per mutating verb (5s debounce), so open → settle → verify flashes TWICE — once at open, once as the progress bar finishes. Hold the flash through the whole open/settle sequence and fire ONCE when the window is ready; keep per-verb flashes only for genuinely separate later touches.
  • #16 per-session serialization: a slow page.goto queues eval/screenshot/list behind it → everything times out → "pup looks frozen". Navigation must not block reads.
  • Console-free reparent (parked): 1.9.131 measurements — manual Start-Process launches fine, bridge-composed arg list does not. Diff the two command lines, re-land.