# browser-tools

Headless-browser screenshot tooling so Claude can *see* rendered Evolution pages
(reports, forms, layouts) instead of reasoning from PHP alone.

Stack: **Playwright + headless Chromium**, driven by a single script ([shot.js](shot.js)).
Chromium launches on demand and is torn down on exit — no persistent process
(the container is RAM-tight).

## One-time setup (root, at deploy)

Chromium's system libs and the browser binary are installed by
[../codeserver-setup/install.sh](../codeserver-setup/install.sh) — run it from the
Docker host after any redeploy:

```
docker exec -u root <container> bash /config/workspace/codeserver-setup/install.sh
```

That does `npm install` here, then `playwright install --with-deps chromium`
into the shared cache `/ms-playwright`.

## Config

```
cp .env.example .env      # then fill in EVO_DEV_USER / EVO_DEV_PASS
```

`.env` is git-ignored. Dev-only for now; live creds stay blank until the prod
`claude` user exists.

## Usage

```
node shot.js /reporting/invmonthlysales.php        # dev app path, auto-login
node shot.js /jobedit.php?id=42 --selector "#bomTable"   # element-only
node shot.js /invoiceadd.php --full                 # full-page
node shot.js https://example.com --no-login         # any URL, no login
node shot.js /somepage.php --env live               # live (once configured)
```

Images land in `shots/` (git-ignored). Login is two-stage (credentials → company
select); shot.js handles both and caches the session in `.auth/<env>.json` so
repeat shots skip the login form (`--fresh` to re-auth).

## Image size (docs vs AI tokens)

Two independent knobs — both native, no separate reduce-step, no per-image AI cost:

- **File size** (for the docs site): use JPEG. `--jpeg [quality]` / `--quality N`
  (default 80). Turns a ~1.5 MB PNG into ~150–280 KB with no visible loss.
- **AI-token cost** (when Claude reads the image): driven by *pixel dimensions*,
  not bytes. Use `--scale <n>` (<1) to render fewer pixels — e.g. `--scale 0.66`.
  File size drops too.

```
node shot.js /reporting/sales.php --jpeg 82                 # docs-quality JPEG
node shot.js /reporting/sales.php --jpeg 82 --scale 0.66    # smaller file + fewer tokens
node shot.js /jobedit.php --out docs/img/jobedit.jpg        # ext infers jpeg
```

Default remains lossless PNG (best when I need to read fine table detail). PNG
compression is lossless, so PNG file size can't be tuned; for crisp *and* small
UI screenshots, JPEG is the native answer. (Lossy-PNG via `pngquant` is possible
but needs a binary — not wired up unless you want it.)

See the header of [shot.js](shot.js) for all options.

## Editing an image after capture (ImageMagick)

Installed by [../codeserver-setup/install.sh](../codeserver-setup/install.sh).
Ubuntu 24.04 ships ImageMagick 6, so the commands are `convert` / `mogrify` /
`identify` — there is no unified `magick` entrypoint.

**Don't reach for it to crop.** Playwright crops natively at capture time and
does it better, because the bounds come from the DOM instead of guessed pixel
coordinates. Pass a `clip` box built from `getBoundingClientRect()`:

```js
const box = await page.evaluate(() => {
  const r = document.querySelector('#someSection').getBoundingClientRect();
  return { x: r.left, y: r.top + scrollY, width: r.width, height: r.height };
});
await page.screenshot({ path: out, clip: box });
```

Use ImageMagick for the things that can only happen *after* an image exists:

```
# annotate — arrow + callout on a docs screenshot
convert in.png -stroke red -strokewidth 4 -fill none \
        -draw "roundrectangle 40,300 900,360 6,6" out.png

# redact — pixelate a region holding a customer name/email
convert in.png -region 420x28+560+180 -scale 8% -scale 1250% +region out.png

# read a customer-supplied screenshot: crop a region and upscale it
convert ticket.png -crop 600x200+120+400 +repage -resize 250% region.png

identify -format '%wx%h %b\n' *.png     # dimensions + byte size
```

Note `-region` must come *before* the operators it scopes, and be closed with
`+region`. Ordering is positional throughout — IM applies operators left to
right, not as a set of flags.
