@AGENTS.md ## Verifying in the browser Some of what follows names setup rather than repo content. The `opendiving-web-login` and `opendiving-web-home-screenshot` skills are committed under `.claude/skills/`, so a clone has those; the `playwright` MCP server is user scope in `~/.claude.json`, and a clone does not. Nothing below needs it — the failure modes are properties of the tools themselves, not of that setup — so read the `playwright` mentions as _if you have it_. `computer{action:"screenshot"}` only returns real pixels while the Browser pane is **visible**. A hidden pane stops compositing, and the capture falls back to the page background — `rgb(22, 22, 24)` for the web app — which arrives as a plausible blank dark image rather than an error. Every DOM-side tool (`read_page`, `get_page_text`, `javascript_tool`) keeps working meanwhile, so "DOM is fine, pixels are blank" reads as a broken capture stage and invites a pointless detour into Playwright. Check `document.visibilityState` before concluding anything. Two tells for the same cause: successive screenshots come back byte-identical even after the page scrolls, and navigating appears to fix it intermittently, because each navigation forces one fresh paint that then goes stale again. A hidden pane sometimes also reports a **0×0 viewport**, and then **input** is gone too, not just capture. With no layout box on anything, `computer{action:"left_click", ref}` resolves the ref to `(0, 0)`, reports `left_click at (0, 0) [ref_6]` as if it worked, and the element never receives the click. The trap is that you still look equipped: `read_page` with the default `interactive` filter returns a bare `(empty page)` — nothing has a box to be interactive in — but `read_page{filter:"all"}` hands back the full accessibility tree with `ref_N` handles, so the obvious next move is to click one, and that silently does nothing. `javascript_tool` reading `innerWidth`/`innerHeight` alongside `document.visibilityState` is the check; neither `resize_window` (which answers "Viewport reset to native size" and changes nothing) nor `tabs_select` fixes it. Only the user can reveal the pane, so ask rather than working around it — and say what is left unverified. A **visible** pane with a healthy viewport can drop clicks too, and the cause here is not established — so treat the symptom as the signal. `computer{action:"left_click"}` reports plausible coordinates, `document.elementFromPoint()` at those coordinates returns the intended element, the form has no validation errors, and nothing happens; after a few attempts `computer` starts timing out. Diagnose it in one step rather than re-clicking: attach a capture-phase listener to the target (`el.addEventListener('click', …, true)`) and click again. If the listener records nothing, the event is not reaching the DOM at all, and no amount of better targeting will help — the app is not the problem. Fall back to the page's own event path (`form.requestSubmit()` fires a real submit that React and react-hook-form handle exactly as a click would) or verify off the browser entirely, and say in the write-up which layer you skipped. That interacts with the `opendiving-web-login` skill in one useful way: a click that never lands also never fires the verifying POST, so the magic-link token is **not** consumed and step 2b's bearer path is still available with the same link. Falling back to the API is often the better verification anyway — asserting against a real response body beats clicking Save, and it is the only half of the job a hidden pane doesn't block. Scroll with `computer{action:"scroll"}` rather than `window.scrollTo` through `javascript_tool` — the tool action is tracked by the capture path and fails loudly when the pane is hidden instead of silently returning the previous frame. `scripts/screenshots.mjs` drives its own Playwright browser and is immune to all of this, but it exists to regenerate this repo's `docs/screenshots/` (see its `opendiving-web-home-screenshot` skill), not to answer ad-hoc "does this look right?" questions. The `playwright` MCP server is the ad-hoc answer — user scope in `~/.claude.json`, so it loads in every repo — and it is worth reaching for the moment the pane misbehaves, rather than after a detour through it. It drives its own Chrome and shares nothing with the pane. Both it and `screenshots.mjs` are immune for the same reason: Playwright captures through CDP `Page.captureScreenshot`, which renders off the page rather than off the screen, so nothing needs to be visible — `screenshots.mjs` produces the README images headless, with no window at all. An occluded or minimized browser is therefore a non-issue, and window visibility is never the thing to debug on this path. It runs **headed on purpose** — leave `--headless` off. A visible window is what lets you watch a click that isn't landing, the failure two paragraphs up that DOM inspection alone doesn't explain, and by the mechanism above being headed costs nothing in reliability. Three traps. Writes are restricted to the workspace roots, so a screenshot cannot go to the scratchpad directory — the path comes back rejected as "outside allowed roots"; pass a _relative_ filename instead and it lands in `.playwright-mcp/`, which is already gitignored. The browser profile outlives each call, so an account signed in once stays signed in: run `opendiving-web-login` once per session, not once per navigation. And a session that ends without closing its browser leaves a Chrome holding that profile's lock, so the next session's first navigation fails with "Browser is already in use". Do not read that message as proof of an orphan. A _live_ sibling session on the same profile produces it identically, and concurrent sessions in this checkout are the norm rather than the exception. `about:blank` is no evidence either: it is the initial-navigation argument baked into the launch command line at spawn, and CDP navigation never rewrites argv, so the string is still sitting there while Playwright drives fully loaded pages. Either tell alone will have you killing another run's browser out from under it. The owner is the part that is actually observable. `ms-playwright-mcp` appears in the `--user-data-dir` of every helper process too, so isolate the browser itself — the one line carrying no `--type=` — and read its parent: ```bash ps -Ao pid,ppid,command | grep ms-playwright-mcp | grep -v -- --type= | grep -v grep ``` A parent that is still a live `playwright-mcp` node process means another session holds it: leave it alone. A parent of `1` means that server exited and launchd adopted the browser — that is the orphan, and killing that pid is safe.