| name | browser-verify-ui-changes |
| description | Use whenever a task has a user-visible outcome — chart, overlay, UI state, route change. Before marking the task complete, take a chrome-devtools MCP screenshot (or evaluate_script against actual render state) and visually diff against the operator's reference. Type-check, lint, unit tests, and e2e all passing is NECESSARY but NOT SUFFICIENT for canvas/UI work — paint-time bugs hide from synthetic tests. |
| metadata | {"category":"agentic-discipline"} |
Browser-verify UI changes before marking done
This is the single most consequential discipline lesson from canvas-heavy UI work with an agent. It was learned the hard way after an agent shipped a broken chart indicator three times in a row with all gates green:
- Type-check passed
- Biome / ESLint passed
- Unit tests passed
- e2e (Playwright) passed
- The chart was still wrong
The bugs only manifested at paint time:
- A chart lib's
isWhitespace=true stripping rows from data without warning
- NaN sentinels for off-screen positions silently dropping draws
- Array-index misalignment between primary and overlay series
Synthetic tests literally cannot catch these. The fix has to be at the same layer the bug manifests: read the actual rendered pixels.
When to use
Reach for this skill for every task that:
- Changes a chart, overlay, indicator, or any visible chart element
- Changes a layout, toolbar button, popover, menu, page
- Changes a route or URL behavior the operator interacts with
- Adds a visual state (active, hover, disabled, loading, empty)
Reach for it ESPECIALLY when:
- The task seems "simple enough" to skip verification (this is the trap)
- You've already burned an iteration on a similar bug
- The reference is a screenshot or an existing tool you're matching
The pattern
Treat the edit and the visual verification as one atomic unit. The moment you think "I'm done" is the moment you MUST screenshot and diff before saying so.
await mcp__chrome-devtools__navigate_page({ type: "reload" })
await mcp__chrome-devtools__take_screenshot()
await mcp__chrome-devtools__evaluate_script({
function: `() => {
const hook = window.__myDebugHook;
const series = hook.chart.panes()[0].getSeries()[0];
return { data: series.data(), options: series.options() };
}`
})
If browser access is unavailable in the session, the task is in_progress and you say so explicitly — NEVER completed on assumed-correctness.
Why synthetic tests fall short
| Test class | Catches | Misses |
|---|
| TypeScript | type errors, missing fields | render-time data shape issues |
| Lint | code-style, banned patterns | runtime behavior |
| Unit | pure-function math, hook reducers | canvas pixel correctness, time-axis layout |
| e2e (Playwright) | "the canvas exists with N candles" | "the candles are drawn at the right Y position" |
| Visual screenshot diff (chrome-devtools MCP) | pixel-level correctness ✓ | (this is the floor) |
The agentic-loop rule
Codify in your project's CLAUDE.md (or equivalent) so the agent reads it before every UI task:
**No browser-affecting task is "done" until verified in the browser via
chrome-devtools MCP.** For any change with a user-visible outcome (chart,
UI, route, overlay, popover, etc.), reload the page, exercise the
specific behavior end-to-end through the live app (click the button,
drag the chart, toggle the setting), and screenshot or programmatically
confirm the expected state. tsc + biome + unit + e2e passing is
necessary but not sufficient — synthetic tests don't catch what only
manifests at paint time. Acceptable verification = a chrome-devtools
take_screenshot, take_snapshot, or evaluate_script inspecting actual
chart state. If browser access is unavailable in the session, mark the
task in_progress and say so explicitly rather than completing on
assumed-correctness.
Gotchas
- The rule loaded in context is not a checklist. When this rule first lands, the agent often ignores it on the NEXT iteration because "context I've read" ≠ "checklist I run before declaring done." Make verification a forcing-function step in the task workflow, not just a documented rule.
- "It looks right" isn't verification. Compare against the explicit reference. The operator's screenshot, the Pine source render, the previous-version screenshot — there's always a reference.
- Screenshots aren't always enough. Some bugs (off-by-one OHLC, wrong volume, hidden state) show up in
series.data() but not in pixels. Use evaluate_script for those.
- Don't claim "verified" if the dev server might be stale. Force a navigate-reload before screenshotting.
- Browser-verification doesn't replace tests. It catches paint-time bugs the tests can't. Keep the tests; add the verification.
Reference implementation
The rule above was added to a project's CLAUDE.md mid-session after an indicator port shipped wrong three times in a row despite green CI. The exact incident — a "trend fill" implementation that recolored the wrong layer, then drew the fill at the wrong y-range, then connected lines across session gaps — was each time invisible to type-check + unit + e2e, and each time obvious in a screenshot.
The lesson was written up as a long-form memory note feedback_browser_verify_before_done.md in the project's memory store, then codified as a numbered hard rule (#9) in CLAUDE.md so future agent sessions load it as context.
Related skills
lightweight-charts-integration — the chart-specific paint-time bugs that escape synthetic tests
react-canvas-race-conditions — another class of bug invisible to unit tests
read-reference-source-first — its companion discipline ("don't guess; read the source")