Recaptures every docs screenshot against `v2026.1106.0` (`d69631b0`). 196 of 278 were stale, and some contradicted the text, such as the removed **Run with provider trace** button and the old roster tabs. ## What changed - **Images:** 276 of 276 captured in both themes; 271 PNGs changed. Every registry entry is stamped `v2026.1106.0`. - **Pipeline:** `seed.mjs`, `capture.mjs`, `routes.mjs`, `config.mjs` and `github-review-fixture.mjs` now match this release's UI. No targets were dropped. - **Caption:** `experimental/file-viewer.md` now describes what its image shows. ## Check these - **Staged state:** the budget banner is removed in the browser on `dashboard/dashboard-overview`, `experimental/server-info` and `onboarding/sidebar-new-company-button`. The budget pages keep their real incidents. The seed also turns off hard stops outside the Budget Demo agents, and marks agents stuck in `verifying` as ready, so demo pages aren't paused. - **`experimental/file-viewer-browse`:** opening the task **Files** tab crashed the app at the tag on the seeded instance (`Maximum update depth exceeded` in `SidePanelTabs`), so the image shows a file open in its side panel instead. `file-viewer.md` still lists `g f` as an entry point. ## Review coverage All 140 light images were checked at contact-sheet size, so fine text wasn't readable that way. 44 images from the final run, 24 light and 20 dark, were opened at full size. The remaining dark images weren't viewed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <[email protected]>
Screenshot pipeline
Automated capture of light + dark UI screenshots at 1440×900 @2x for use in the docs. Output PNGs land under docs/user-guides/screenshots/{light,dark}/ and are reviewed in a PR — they are never auto-pushed.
Prerequisites
- Parent repo present. The pipeline spins up a real Paperclip instance from source. Set
PAPERCLIP_REPOto its absolute path, or ensure it lives at the default location~/Documents/PaperclipAI/paperclip(the default resolved byconfig.mjs). See paperclip-localdev-runbook.md conceptually — the runner follows the samelocal_trusted/private/loopbackisolation pattern. - Playwright Chromium installed. Run once after
npm install:npx playwright install chromium pnpmavailable. The runner starts the parent's onboarding entrypoint viapnpm paperclipai onboard --yes --run.
One-command flow
npm run screenshots:refresh
What happens under the hood:
- Isolated instance.
run.mjsspawnspnpm paperclipai onboard --yes --runinside the parent repo with a throw-awayHOME/PAPERCLIP_HOMEunderos.tmpdir(),PORT=3197,PAPERCLIP_BIND=loopback,PAPERCLIP_DEPLOYMENT_MODE=local_trusted, andPAPERCLIP_DEPLOYMENT_EXPOSURE=private. The child process receives only a small allowlist of runtime environment variables, so real database URLs, provider keys, and GitHub tokens are not forwarded.- Parent
.envneutralized. The server also loads a.envfrom its working directory (the parent repo) via dotenv withoverride: false, which would otherwise re-inject the developer's real settings and defeat the allowlist.instanceEnv()therefore explicitly pins the keys that matter:DATABASE_URL=""(andDATABASE_MIGRATION_URL="") so the instance uses its own embedded Postgres instead of the parent's external database, andSERVE_UI="true"so the UI is served even if the parent's.envsetSERVE_UI=false. Because dotenv runs withoverride: false, these explicit values win over the parent.env. - Free embedded-Postgres port. Before that,
run.mjsruns a short config-generation pass (onboard --yespointed at an unreachable externalDATABASE_URL, so it writes a config without initializing the embedded cluster) and rewritesdatabase.embeddedPostgresPortto a guaranteed-free port discovered by scanning upward from 54329. This matters because onboard hard-codes embedded Postgres to 54329 with no env/flag override, and that's the same default a developer's real local Paperclip uses. Pinning a free port means a screenshot run started while your real instance is up can never collide with — or, worse, connect into and seed — your real database. (The generation pass must stay in external-DB mode: if it ever raninitdbon the scratch data dir, a half-finished cluster would make the real run skip init and fail withrole "paperclip" does not exist.) The direct-DB workspace seed (seed-execution-workspace.mjs) reads the live port back from the instance'spostmaster.pid/config.jsonand refuses to guess 54329.
- Parent
- Health check. Polls
http://127.0.0.1:3197/api/healthuntil 200 (up to 120 s). - Seed.
seed.mjscreates a minimal demo company ("Acme Robotics") plus a manager agent, a regular agent, a project, a goal, a routine, an issue, and a workspace. The company'sissuePrefixis auto-derived server-side (first three uppercase letters of the name →ACM) and cannot be set via REST, so the real prefix is captured and written to.seed-ids.jsonalongside the entity IDs (gitignored).routes.mjsreadscompanyPrefixfrom there, so routes always resolve to whatever the server assigned. Idempotent — re-running looks up the existing company first. - Sync registry.
sync-registry.mjsmerges route anddepends_onmetadata fromCAPTURE_TARGETSintoregistry.jsonwithout overwriting other fields. - Capture.
capture.mjsopens Chromium via@playwright/test, navigates each route in both light and dark themes (1440×900, deviceScaleFactor 2), and saves PNGs todocs/user-guides/screenshots/{light,dark}/<name>.png. The board theme is seeded via bothcolorSchemeemulation and alocalStorageinit script so the UI picks it up consistently. - Stamp. After capture,
registry.jsonentries are updated withcaptured_sha(parent git HEAD) andcaptured_against(branch/tag name). - Teardown. The server child process is killed and the scratch home is removed (
--keepskips the cleanup for debugging).
Overhaul mode
After a large UI/UX redesign — when every screen has changed — recapture everything in one pass:
npm run screenshots:refresh:all
This passes --all to capture.mjs, which ignores staleness and re-shoots every entry in CAPTURE_TARGETS.
Individual scripts
| Script | Command | What it does |
|---|---|---|
seed.mjs |
npm run screenshots:seed |
Seeds demo data against a running instance at BASE_URL. Writes .seed-ids.json. |
capture.mjs |
npm run screenshots:capture |
Captures screenshots against a running instance. Flags: --all, --only <substr[,substr…]> (comma-separated substrings are OR-ed), --theme <light|dark|both>, --stale <file,...>, --base-url <url>, --keep. |
sync-registry.mjs |
npm run screenshots:sync-registry |
Syncs route + depends_on from routes.mjs CAPTURE_TARGETS into registry.json. Safe to run standalone. |
run.mjs |
npm run screenshots:refresh |
Full orchestrator: starts isolated instance → seeds → syncs registry → captures → tears down. Forwards --all, --only, --theme to capture.mjs. |
Route map
The full list of screenshots to capture, their routes, and their depends_on mappings is maintained in docs/user-guides/screenshots/SCREENSHOTS_TODO.md. routes.mjs is the machine-readable version of that same list — keep them in sync when adding new pages.
Rules
- Never run against live data. The pipeline always talks to the seeded demo instance on loopback port 3197.
PAPERCLIP_BIND=loopbackensures no external traffic. - Safe to run alongside your real local Paperclip. The screenshot instance pins its embedded Postgres to a free port (see "Isolated instance" above), so you do not need to stop a real instance first. It will not touch the real database on 54329.
- Output goes to a PR, never auto-pushed. After
screenshots:refresh, open a PR with the changed PNGs for human review. The pipeline does not push. - Always capture both themes. The renderer swaps variants at runtime — providing only one breaks dark- or light-mode users.
GitHub review-bot guides
Set PAPERCLIP_REPO to a source revision containing the GitHub review setup, then
run npm run screenshots:refresh -- --only github-review. Set
PAPERCLIP_SCREENSHOT_PORT to a free port if 3197 is occupied. The four captures
use the real setup components and browser-only demo API responses from
github-review-fixture.mjs; they do not contact GitHub, store credentials, or
execute an agent. Both themes are captured and the parent revision is recorded
in the registry. These images illustrate configuration, not live acceptance.