# v0.5.0 — Screenshots that wait, scrolls that work, responses on a diet

## TL;DR
Auto-screenshots now wait for the page to actually look ready before capturing — no more blank PNGs from pre-hydration app shells. `scroll_page` drives the real scroller even when it's an inner container (the app-shell layout that silently no-opped before). Every JSON response is compact instead of pretty-printed, and a new `page_info` tool answers "what state is this page in?" for a handful of tokens instead of a screenshot.

## Why
A four-page review session produced two blank screenshots (Next.js hides `<body>` until hydration; images stream in late) — each blank capture cost tokens AND forced a manual re-screenshot. On another site, `scroll_page` reported `[at bottom] [at top]` while moving nothing: the page kept `<html>` at viewport height and scrolled an inner `overflow-y-auto` div with 7,700px of content the tool never touched. And across every tool, pretty-printed + fenced JSON roughly doubled the token cost of structurally identical output.

## Highlights
| What | Before | After |
|---|---|---|
| Auto-screenshot timing | Fired immediately after the action — blank shots on hydrating pages | Waits (default 8s cap) for `readyState=complete`, visible `<body>`, and decoded viewport images; captures anyway on timeout with a note of what was pending |
| `scroll_page` on app shells | Silently no-opped; claimed `[at bottom] [at top]` | Falls back to the largest inner scrollable container and names the scroller it drove (`scroller=div.relative.isolate.h-full`) |
| `query_dom` output | `classes` array duplicated `attrs.class`; server re-parsed and pretty-printed into a fence | Compact passthrough of the in-page result; dup dropped; new `omitAttrs` param cuts Tailwind noise on request |
| `scroll_page` output | One-liner + fenced pretty JSON of the same 4 numbers | One-liner only |
| JSON responses (console, network) | `JSON.stringify(data, null, 2)` | Compact `JSON.stringify(data)` |
| Page diagnostics | Screenshot or nothing | `page_info`: URL, title, readyState, viewport, pending images, body visibility, frames, scrollable containers |

## How it works
**Readiness gate.** A middleware-level probe polls the page every 250ms: document loaded, `<body>` actually visible (computed `display`/`visibility`/`opacity` — the FOUC guard), and every `<img>` intersecting the viewport decoded. Below-fold lazy-load images are deliberately excluded so infinite-lazy pages can't stall the wait. On success it settles two animation frames and captures; on timeout it captures anyway and prepends a text note like `[waited 8004ms — readyState=complete, 3/7 viewport images pending]`. One insertion point covers all 13 auto-screenshot tools.

**Scroll fallback.** Before scrolling, the injected script checks whether the root scrolling element has anything to scroll. If not, it scans for elements with `overflow-y: auto|scroll` and `scrollHeight > clientHeight`, picks the one with the most hidden content, and drives it with `Element.scrollTo/scrollBy`. Metrics (`scrollY`, `atBottom`, `atTop`) are computed against whichever element was actually scrolled, and the response names it. If nothing on the page can scroll, it says so instead of claiming success.

**Verified live** against getminds.ai (the page that exposed the bug): fallback selected the inner `div.relative.isolate.h-full` (7,734px content in a 799px window), scrolled 2 → 6,935px, honest `atBottom=true`.

## Config
| Flag | Env | Default | Meaning |
|---|---|---|---|
| `--screenshot-wait-ms` | `FIREFOX_SCREENSHOT_WAIT_MS` | `8000` | Max ms to wait for visual readiness before the auto-screenshot. `0` disables the wait entirely. |

No breaking changes. Bridge package untouched.

## Files changed
- `src/utils/visual-ready.ts` — new: readiness probe + `waitForVisualReady`
- `src/index.ts` — gate wired into the auto-screenshot middleware; `page_info` registered
- `src/tools/page-info.ts` — new micro-tool
- `src/tools/scroll.ts` — inner-scroller fallback, honest flags, one-liner output
- `src/tools/query-dom.ts` — compact passthrough, `classes` dup dropped, `omitAttrs`
- `src/utils/response-helpers.ts` — `jsonResponse` compact
- `src/cli.ts` — `--screenshot-wait-ms`
