roa-vision
v0.1.3
Published
Zero-config visual code review: screenshot every commit, diff the UI, explain the change.
Readme
Röa Vision
Visual code review that actually shows you what changed.
Röa Vision automatically captures full-page screenshots of your web app at every Git commit, highlights exactly which regions of the UI changed, and explains each change in plain English — down to the file and line of code responsible. The whole team reviews from a shared dashboard. Nobody checks out a branch. Nobody runs the app locally.
Setup is one GitHub Actions workflow file, with no secrets to configure. From that moment on, every commit from every developer gets a screenshot, a pixel diff, and an AI explanation.
The Problem
Frontend code review is blind. A reviewer sees:
- padding: 16px;
+ padding: 24px;…and has no idea whether that broke the mobile nav or looks perfectly fine. Finding out means checking out the branch, installing dependencies, and starting a dev server — 15 to 30 minutes per review. Product managers, designers, and QA usually can't do it at all.
Existing tools cover adjacent problems: Chromatic needs Storybook and starts at $149/month, Vercel previews are per-PR with no diff highlighting, Percy and Applitools need written test suites. Röa Vision fills the gap: zero-config, per-commit, full-page, with an interactive history anyone with the link can browse.
How It Works
- Capture. On every push, a GitHub Actions job starts your dev server, screenshots each page you configured with Playwright, and uploads the PNGs to Supabase Storage.
- Diff. Each screenshot is pixel-compared to the previous commit's screenshot of the same page. Changed pixels are clustered into a few meaningful bounding boxes rather than thousands of raw coordinates. Full-page reflows (a font-size change shifting everything below it) are recognised and reported as a single "global layout shift".
- Explain. For each changed region, the before/after crops and the visually relevant part of the git diff (CSS, components, design tokens) are sent to Claude, which returns a 2–3 sentence explanation naming the file and line that caused it.
- Review. A shared dashboard lets anyone browse the commit graph, see pulsing highlights on changed regions, read the explanation, open the responsible source side-by-side with the screenshot, and leave comments anchored to the region.
Setup
Connect a repo from the dashboard, add one file to it, and you're done. There are no secrets to configure and nothing to self-host.
1. Connect the repo
Sign in to the dashboard with GitHub, click New Project, and enter the repo. Röa Vision checks that your GitHub account can see it, then registers it. If a teammate already connected that repo, you're added to their project instead of creating a duplicate.
2. Add the workflow file
The dashboard shows you the exact file to add at
.github/workflows/roa-vision.yml. You can create it entirely in GitHub's web
UI — no local clone needed. It contains no secrets and nothing specific to
your project, so it's the same file for every repo:
name: Röa Vision Capture
on:
push:
branches: ['**']
# One capture at a time per branch; different branches still run in parallel.
concurrency:
group: roa-vision-${{ github.ref }}
cancel-in-progress: false
# Lets the runner prove which repo this run belongs to.
permissions:
contents: read
id-token: write
jobs:
capture:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
with:
# Deep enough to diff against the branch's tip before this push,
# so a batched push is described in full rather than only its
# last commit.
fetch-depth: 20
- uses: actions/setup-node@v4
with:
node-version: '18'
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run dev &
- run: npx wait-on "$(node -p "require('./roa-vision.config.json').baseUrl")" --timeout 60000
continue-on-error: true
- name: Capture screenshots
run: |
npm install --prefix "$RUNNER_TEMP/roa-vision" \
--no-save --no-fund --no-audit --prefer-online \
roa-vision@^0.1.0
"$RUNNER_TEMP/roa-vision/node_modules/.bin/roa-vision" capture
env:
ROA_INGEST_URL: https://your-deployment.supabase.co/functions/v1/capture-ingest
BEFORE_SHA: ${{ github.event.before }}
COMMIT_HASH: ${{ github.sha }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
AUTHOR_NAME: ${{ github.event.head_commit.author.name }}
AUTHOR_EMAIL: ${{ github.event.head_commit.author.email }}Adjust npm run dev if your app starts differently. The wait step reads your
config, so there's no second URL to keep in sync.
Why there are no secrets. id-token: write lets the runner ask GitHub for
a short-lived token proving which repo the run is for. The capture step sends
that to the ingest endpoint, which verifies GitHub's signature and does the
privileged work on its side — it holds the database and Anthropic credentials,
and identifies your project from the token itself. Nothing durable is stored
in your repo, so there is nothing to leak, rotate, or revoke.
3. Add roa-vision.config.json to your repo root
See the configuration reference below. Then push a commit. Within a few minutes the dashboard shows your first capture.
The capture job is designed to never block a push. If the dev server won't start, a page 404s, or the Claude API is slow, the job logs a warning and exits successfully.
Using Röa Vision across several repos
Access is per repository, by design. A workflow only runs in the repo it's committed to, and the token it mints names only that repo. Nothing here can reach a repo that hasn't opted in.
Scaling is just repeating step 1 and step 2: connect each repo from the dashboard and add the same workflow file. Each repo's history stays isolated, and teammates only see the projects they belong to. There is no way to enroll a repository remotely — someone with write access has to add the workflow file to it.
Configuration Reference
roa-vision.config.json in the root of your repo:
{
"baseUrl": "http://localhost:3000",
"pages": [
{ "path": "/", "name": "Homepage" },
{ "path": "/about", "name": "About" },
{ "path": "/pricing", "name": "Pricing" },
{ "path": "/dashboard", "name": "Dashboard" },
{ "path": "/product/:id", "name": "Product Page", "example": "/product/demo-123" }
],
"viewport": { "width": 1280, "height": 800 },
"waitFor": 2000,
"mask": [".timestamp", ".live-feed", ".ad-banner"]
}| Field | Type | Default | Description |
|---|---|---|---|
| baseUrl | string | http://localhost:3000 | Where the dev server is reachable during capture |
| pages | array | [{ "path": "/", "name": "Home" }] | Pages to screenshot on every commit |
| pages[].path | string | required | Route path. May contain :param placeholders |
| pages[].name | string | path | Display name in the dashboard |
| pages[].example | string | — | Concrete URL to visit for a dynamic route (e.g. /product/demo-123). Required when path contains :param |
| viewport | object | { width: 1280, height: 800 } | Browser viewport. Screenshots are full-page, so height only affects above-the-fold layout |
| waitFor | number | 2000 | Milliseconds to wait after page load before screenshotting (lets fonts, animations, and data settle) |
| mask | string[] | [] | CSS selectors for dynamic content to black out before diffing (timestamps, ads, live feeds) |
Notes
- Only pages with detected pixel changes appear in a commit's review. Unchanged pages are skipped silently.
- Dynamic routes are captured once, via the
exampleURL. One representative instance per route pattern, not every possible page — this is a deliberate tradeoff. - If a page fails to load it is skipped with a warning; the rest of the pages still capture.
The Dashboard
Deployed to Vercel (or run locally with cd dashboard && npm run dev).
- Commit graph — every captured commit as a node, branches as lanes. Group by day, week, month, or year and expand a period to see what's inside it. A red badge marks unresolved comments, rolled up across everything beneath it.
- Page tabs — switch between the pages you configured. Only pages that changed in the selected commit are highlighted.
- Screenshot view — the current screenshot with pulsing red boxes over each changed region, and a toggle to the previous commit's version.
- Explanation panel — click a region to see Claude's explanation of what changed and why, plus the file and line responsible.
- View in code — opens a split screen: the screenshot on one side, the actual source on the other, with the changed line highlighted. Toggling to the previous commit swaps both sides at once, so you can compare the picture and the code together. Nothing to install, and it works whether or not you have the repo checked out.
- Comments — leave a comment on any region, and drag it anywhere on the screenshot as a sticky note pointing at what you mean. Only the author can resolve or delete their own.
- Live updates — new commits appear as soon as the capture job finishes, via Supabase Realtime.
Team access
Sign in with GitHub, or with an email and password. Connecting a repo requires GitHub, since access is verified against your GitHub permissions — if you can see the repo there, you can see its captures here, and joining a repo a teammate already connected is automatic.
For anyone without GitHub access to the repo (a designer or PM, say), project owners can copy an invite link that grants read and comment access. Row Level Security guarantees members only see projects they belong to.
Dashboard environment
Create dashboard/.env:
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-keyUse the anon key here, never the service role key. Set the same two variables in Vercel when deploying.
Local Watch Mode
For live feedback while you code, separate from the commit history:
npx roa-vision --watchThis watches src/ with chokidar. On every file save it waits 500ms for hot reload to settle, screenshots the configured base URL, diffs against the last screenshot, and opens a local panel showing the changed regions. Nothing is uploaded — this is a private feedback loop for the developer at the keyboard. The commit capture remains the permanent record.
Options:
--watch Start watch mode
--dir <path> Directory to watch (default: src)
--url <url> Override baseUrl from config
--page <path> Only watch a single page pathDevelopment
npm install # install capture + watch dependencies
npm test # run the clustering algorithm unit tests
npx roa-vision --watch # local watch mode
cd dashboard
npm install
npm run dev # dashboard at http://localhost:5173Troubleshooting: UNABLE_TO_GET_ISSUER_CERT_LOCALLY
If npm install hangs or fails with this error, check whether the problem is
Node rather than your network:
curl -sI https://example.com | head -1 # system trust
node -e "fetch('https://example.com').then(r=>console.log(r.status))" # Node trustIf curl succeeds and Node fails, Node's bundled certificate authority store
is incomplete. This happens on some Homebrew Node builds. Point Node at the
CA bundle Homebrew already installs:
export NODE_EXTRA_CA_CERTS=/opt/homebrew/share/ca-certificates/cacert.pemAdd that line to ~/.zshrc to make it permanent. Do not disable strict-ssl
or set NODE_TLS_REJECT_UNAUTHORIZED=0; both turn off certificate
verification entirely rather than fixing the missing root.
Scope Boundaries
Works well for any web app where visiting a URL produces consistent output: marketing sites, SaaS products, dashboards, e-commerce, admin interfaces, documentation sites.
Not designed for
- Games or anything with non-deterministic rendering
- Native mobile apps
- WebGL / Canvas-heavy rendering
- Real-time collaborative tools whose state depends on live user actions
- Pages behind authentication (v1 captures as an anonymous visitor)
- Multiple viewports per commit (v1 captures one viewport)
The constraint is deliberate. Deterministic UI covers the vast majority of web teams, and it's what makes zero-config, reliable diffing possible.
Project Structure
.github/workflows/roa-vision.yml The workflow file users copy into their repo
capture/ Playwright capture, diff, Claude explanation, Supabase upload
watch/ Local chokidar watch mode
dashboard/ React review interface (Vite, deployed to Vercel)
supabase/migrations/ Database schema and RLS policies
roa-vision.config.json Example configurationLicense
MIT
