Interactive UI
cappa review serves more than a report. The same UI can drive the browser: pick what to
capture, start a run, and watch it happen — without leaving the page to run cappa capture
in another terminal.
cappa reviewOpen the URL it prints and choose Capture in the sidebar.
Capturing
Section titled “Capturing”The capture panel lists every task your plugins discovered. From there you can:
- Capture everything — the same work
cappa capturedoes. - Capture one plugin — tick a plugin to run only its tasks.
- Capture specific screenshots — filter by name and tick the ones you want.
A selection of specific screenshots leaves the rest of your results alone, so you can
iterate on one component without re-capturing the whole suite. A full run still clears
actual/ and diff/ first, exactly as the CLI does.
While the run is going you get live progress, a row per task moving from pending through running to its outcome, a count by status, and the run’s log output. Cancel stops it: tasks already in flight finish, but nothing new starts.
When the run finishes, the screenshot lists refresh themselves — the Changed, New and Passed tabs reflect what was just captured with no reload.
Re-capturing a single screenshot
Section titled “Re-capturing a single screenshot”Every screenshot’s detail page has a Re-capture button. It runs that one task and leaves every other result untouched, which makes the tighten-a-diff loop short: change your component, re-capture, look at the diff, approve.
The button needs to know which capture task produced the screenshot, and that is not
something a filename can tell it — @cappa/plugin-storybook writes
example/button/primary.png for the task example-button--primary. Cappa records the
link as it captures, in a .cappa-manifest.json beside actual/ and expected/. So the
button appears once a screenshot has been captured at least once by this version of
cappa, and is hidden for anything older until the next run. The file is small, safe to
commit and safe to delete — deleting it only hides the button until the next capture.
Picking up a run you did not start
Section titled “Picking up a run you did not start”The capture page reads the run in flight from the server rather than remembering its own,
so reloading mid-capture reattaches to the same run — progress, task table and log intact
— and a cappa capture running in another terminal shows up here too, with the capture
controls disabled until it finishes.
One run at a time
Section titled “One run at a time”The server captures one run at a time — the browser pool is the real constraint, and a second concurrent run would only fight it. Starting a run while one is active is rejected rather than queued, and the UI disables the button while a run is in flight.
The browser stays warm
Section titled “The browser stays warm”Between captures the browser is kept alive so an interactive run does not pay browser startup every time. It shuts down once it has been idle for a while, so a forgotten review session does not hold a browser process indefinitely.
Contexts are recycled before every run, so a capture started from the UI is taken against the same clean state the CLI would use.
Tune the idle window with review.browserIdleTimeout.
An active watch session suspends that timer entirely: a session left idle over a coffee break would otherwise pay a full browser start-up on the next save, which is the cost watch mode exists to avoid. The timer resumes when watching stops.
Watch mode
Section titled “Watch mode”Turn on Watch files in the capture panel and every save re-captures what it affects. The run streams into the same run view a capture you started by hand does, and the run header says which file triggered it.
The session lives on the server, not in the tab, which is what makes it survive a reload —
and what makes a second tab, or a cappa capture --watch in a terminal, show the same
state rather than disagreeing with it. The status line under the toggle says what the last
change re-captured:
src/components/Button.stories.tsx changed — re-capturing 2 tasksHow much gets re-captured depends on what the plugins can tell Cappa about the file you
saved — precise for a story file with @cappa/plugin-storybook, a whole-plugin re-run for
a component it cannot attribute. Watch mode covers the rules, and
Custom Plugins covers teaching your own plugin the
mapping.
Two things watch mode deliberately does not do: it never clears actual/ between
iterations, so the diffs you are looking at for screenshots you did not touch stay where
they are; and it never approves anything. Watch captures — a human still approves.
The toggle is hidden when the server cannot watch: a read-only server refuses every mutation, and an engine with no local filesystem has nothing to watch.
Serving a report without capture
Section titled “Serving a report without capture”Capture drives a real browser and writes to disk. When you only want to share a report — CI artifacts, for instance — start the server read-only:
cappa review --read-onlyThe capture page, the sidebar entry and the re-capture buttons all disappear, and the server refuses capture, approval and every other mutation.
Exposing the UI beyond your machine
Section titled “Exposing the UI beyond your machine”By default the server binds to 127.0.0.1, reachable only from your own machine. Binding
anywhere else exposes control of a browser that reads local files, so a token is required:
cappa review --host 0.0.0.0Cappa generates one and prints it as part of the URL. Pass your own with --token <value>
if you would rather choose it. Every /api/* request needs it — the whole UI, not just the
capture page.
The URL is printed with localhost as its host even when you bound to 0.0.0.0, because
a wildcard is an address to listen on and not one a browser can open. Replace it with the
machine’s own hostname when opening the UI from somewhere else.
Open the URL the server printed and the UI takes care of the token from there. It keeps it for the tab, so reloading or navigating still works once the token has left the address bar — and it removes it from the address bar as soon as it has been read, to keep it out of bookmarks, screenshots and the referer header. A new tab needs the printed URL again: the token is not stored beyond the tab it was opened in — open one without the token and the UI says so rather than sitting on an empty screen.
A request can only capture tasks that discovery produced — the UI selects from that list and never supplies a URL of its own — so a client cannot point the browser somewhere arbitrary.
| Flag | Description |
|---|---|
--port <port> |
Port to listen on. Defaults to review.port. |
--host <host> |
Host to bind. Defaults to 127.0.0.1; anything else requires a token. |
--read-only |
Serve the report without capture, approval or any other mutation. |
--token <token> |
Require this token on every API request. |