Skip to content

Keep the headful window maximized while the session runs - #506

Draft
robertjamesprior wants to merge 2 commits into
mainfrom
hypeship/window-reconcile
Draft

robertjamesprior wants to merge 2 commits into
mainfrom
hypeship/window-reconcile

Conversation

@robertjamesprior

@robertjamesprior robertjamesprior commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Keeps a headful window covering the display after the promotion that runs on browser start, by watching the window state rather than polling it, and bounds how long it will argue with a client that wants a smaller window.

Why

Promotion restores the maximized contract once per chromium start. It does not cover what happens next: a CDP Browser.setWindowBounds over the proxy, the same command inside the playwright executor, a window manager restore, or a geometry a saved profile carries into the restart and applies after the promotion has already run. None of those are distinguishable from the window state itself, so the guard acts on the state.

How it watches

xprop -spy on the window's _NET_WM_STATE. Openbox rewrites that property the moment the window leaves or re-enters maximized, so a correction happens as the demotion does instead of up to an interval later, and a session whose window never moves costs nothing — no timer, no devtools connection, no round trips. The earlier revision of this PR polled the state every 5s on a held connection for the life of every headful session.

The X atoms are the trigger; CDP is still what decides. A reported change dials the browser, reads the bounds, and acts only on a window that is neither maximized nor fullscreen, so kiosk sessions are untouched and a change that was already corrected costs nothing. A window manager that does not maintain the EWMH atoms simply never triggers the guard; promotion on start is what guarantees the window starts covered.

The budget

Three corrections per browser start. One covers the case this exists for: a window that leaves maximized once and stays there for the rest of the session. Repeated demotions are a client that wants a window that size, and past the budget it keeps it, with a log line saying so. The previous revision fought such a client for the life of the session, with no error, no event and no way to turn it off — which is the mirror image of the bug this fixes.

Logging

Unchanged: a line per correction, carrying the bounds it replaced, so one line means one demotion and its timestamp can be matched against the client's own call timeline — including for a command through the playwright executor, which the devtools proxy does not see.

Testing

  • go test -race across the non-e2e packages: pass
  • go build ./..., go vet ./cmd/api/api/ ./lib/x11/: clean
  • New unit tests cover the budget (corrects, stands down, stays down), a covered window costing nothing, and a change that CDP already reports as maximized not spending budget; the existing reconcileWindow tests are unchanged
  • New parse tests for _NET_ACTIVE_WINDOW and _NET_WM_STATE, including maximized-in-one-direction and the kiosk fullscreen case
  • The watcher was run against a live headful session on the current release image, as a probe binary built from this branch: it reported the maximized window at startup and covered=false in the same second as a CDP demotion, twice, once for the state change and once for the explicit bounds that followed

Not yet exercised in a deployed image: the guard itself needs a release to run for real.

Follow-up worth having, not in this PR

A correction should be a telemetry event, not only a VM log line. Adding an event type means a schema change here plus the matching type on the API side, so it belongs in its own change — but without it, a window geometry change leaves no trace a customer can see, which is exactly why the bug behind this PR took two days to pin down.

Replaces the five-second reconcile poll with a watch on the window's
_NET_WM_STATE property, which the window manager rewrites the moment the
window leaves or re-enters the maximized state. A correction happens as
the demotion does rather than up to an interval later, and a session
whose window never moves costs nothing: no timer, no devtools
connection, no round trips.

Caps corrections at three per browser start. One correction covers what
this guard is for, a window that leaves maximized once and stays there.
Repeated demotions are a client that wants a window that size, and past
the budget it keeps it, rather than being fought for the life of the
session by something it cannot see or turn off.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant