# 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.
