verify
Browser-check what a change could have broken in timepicker-ui (library demo and docs-app, before/after). Use at the end of a task that changed library behavior or styles (app/src) or docs-app pages,
SKILL.md.claude/skills/verify/SKILL.md
GitHub/verify — before/after browser verification
Runs entirely in the main agent (HARD RULES in CLAUDE.md: subagents never run servers,
browsers or checks). Report only: never fixes code, never runs git writes, never publishes.
Files in this folder: impact-map.md (path → tags → routes), servers.md (exact commands),
scenarios.md (steps per tag), helpers.js (served on 127.0.0.1:8199, injected as window.__tp).
Arguments
lib/docs— force a target (default: auto from the diff).--base <ref>— compare against<ref>(defaultHEAD; e.g.HEAD~1when the change is committed).--no-docs— skip the docs target.--no-baseline— after phase only (expectations + screenshots, no comparison); a SHOT is the screenshot only, no fingerprint.--files <paths>— select tags from these files instead of the diff. Use it when the working tree holds unrelated uncommitted work (a release in progress); pair with--no-baselinewhen the base predates that work.--full— run every scenario of every selected tag, every viewport and every mapped docs route. Use it before a release or when the user asks; without it the run uses the quick profile (Step 2.5).--mcp chrome-devtools|playwright— force the MCP.--tags <@a,@b>— add these tags to whatever the diff selects; with an empty diff the run still has a baseline (before = after → a determinism check).--clean— servers.md §6, then stop.
Step 1 — Preflight
servers.md §0, then §4 (run id). Any failure → stop with the exact fix for the user.
Step 2 — Tags, targets, routes
- Changed files: the
--fileslist when given, else (Bash)git diff --name-only <base>andgit ls-files --others --exclude-standard. - impact-map.md §1 / §3 → tags (union) +
@smoke; unmatched → UNCOVERED (read its diff, pick tags yourself, propose a map row in the report). - Targets and docs routes: impact-map.md §2 and §4; apply
lib/docs/--no-docs. - Add
--tags. Scenarios to run = everylib/*scenario under the heading of each selected tag in scenarios.md; style-rule tags also addlib/variants-<tag>;@theme:<X>runslib/theme-<X>; plus onedocs/<route>scenario per selected route. Read only what runs: Grep^##in scenarios.md for line numbers, then Read the common headers and each selected section with offset/limit, not the whole file. - Quick profile (default;
--fullskips this step):- lib: desktop only - drop scenarios and SHOTs with
vp: morvp: lunless a changed file isMobileViewHandler.tsor underapp/src/styles/, or the task is about mobile; lib/themesandlib/seconds-themes: onlybasic,darkand the themes the task changed;- docs: impact-map §3 routes as mapped; from §2 only the first route of each tag; desktop only unless a docs-app page or component changed.
- lib: desktop only - drop scenarios and SHOTs with
- Tell the user in 2-4 lines: base, targets, profile (quick: what
--fullwould add), tags (with the triggering file), routes, uncovered. Don't wait.
Step 3 — Servers
servers.md §1a (helpers server, always first), then §1 (lib) and/or §2 (docs — includes
NEXT_TURBOPACK_EXPERIMENTAL_USE_SYSTEM_TLS_CERTS=1 for next/font/google behind the proxy);
skip before servers with --no-baseline. Every server in the background, keep task ids; §3 for
each. A failing server → log tail into the report, drop that target (a failing before server →
that target runs without baseline).
Step 4 — Browser
- MCP (unless
--mcp):mcp__chrome-devtools__list_pages; on any error → Playwright MCPbrowser_tabs(action: list); both fail → SUMMARY.md "No browser MCP available" with both errors and NO verdict, go to Step 6. Before and after MUST use the same MCP; if it dies mid-run, delete.verify/shotsand.verify/fp, switch, redo Step 4. - Pages — chrome-devtools:
new_pageper target+phase (lib-before,lib-after,docs-before,docs-after), note eachpageId; on eachemulateviewport: "1280x800x1",colorScheme: "light". Playwright MCP (one page): run phases one after another;browser_resize1280×800,browser_emulate_mediacolorScheme: "light". chrome-devtools can restart its browser between calls ("No page found", page ids change) →list_pages,new_page+emulateagain, and redo the current scenario from its navigation. - Run every selected scenario (below): all before-phase runs first, then after. Before a
phase starts,
select_pageits page withbringToFront: true— a background tab throttlesrequestAnimationFrame(used bysettle()) and keyboard focus. - Close what you opened (
close_page, keep one blank page /browser_close).
Running a scenario
Header: page (lib → 8105/8106, docs → 3105/3106), vp (d = 1280x800x1, m = 390x844x1,mobile,touch),
path, ready call, optional hide/ignore. M = body > .tp-ui-modal.show.
- Set the viewport if
vpdiffers from the page's current one (emulate/browser_resize390×844). - Navigate to server +
path(never pastehelpers.jsitself — the loader fetches it from 8199):- chrome-devtools:
navigate_pagetype: "url",url,initScript=(()=>{try{const x=new XMLHttpRequest();x.open('GET','http://127.0.0.1:8199/helpers.js',false);x.send();(0,eval)(x.responseText);}catch(e){window.__tpLoadError=String(e);}})() - playwright:
browser_navigate, thenbrowser_evaluateasync () => { const r = await fetch('http://127.0.0.1:8199/helpers.js'); (0, eval)(await r.text()); return !!window.__tp; }. window.__tpmissing → readwindow.__tpLoadError; check §1a is listening; redo the navigation.
- chrome-devtools:
- Run the ready call, then the hide call if any.
- Steps in order:
DO <call>→evaluate_script/browser_evaluatewithasync () => await window.__tp.<call>. Both phases; compare→ EXPECTonly in after. Consecutive DO/CHECK steps without a KEY or SHOT between them may go in one evaluate call.CHECK <call> → EXPECT …→ after phase only.KEY <key>→press_key/browser_press_key. Both phases.SHOT <name> root=<sel> ignore=<sel>→ both phases:DO settle(); screenshot (viewport) to.verify/shots/<phase>/<target>/<scenario-id-with-/-replaced-by-_>-<name>-<vp>.png(take_screenshotfilePath/browser_take_screenshotfilename,scale: "css"); fingerprint (skipped with--no-baseline)() => window.__tp.fingerprint('<root>', '<ignore>')to the same path under.verify/fp/…with.json(evaluate_scriptfilePath/browser_evaluatefilename). The screenshot and the fingerprint may share one message; the next step that changes the page goes in a later message. A screenshot moved to the background (> 120 s) → wait for its completion notice, Read the PNG to confirm it shows the SHOT's state, else redo the scenario.
- End:
CHECK errors()in both phases (record both; "new" = in after, not in before). Playwright: alsobrowser_console_messages(errors). Afavicon.ico404 is known noise — ignore it. - A step failing in before → note it, continue. In after → record the failed expectation; skip the rest of that scenario except its SHOT steps.
- After-phase failure with a baseline → rerun that scenario on the before page WITH its
CHECK/EXPECT lines: fails there too =
broken-on-base, passes =regression. - A scenario marked "Known library issue" in scenarios.md that fails →
broken-on-base (known)even without a baseline; it becomes aregressiononly if before passes and after fails.
Step 5 — Compare and classify
- Normalize all fingerprints (Bash):
find .verify/fp -name '*.json' -print0 | xargs -0 -r node -e "const fs=require('fs');for(const f of process.argv.slice(1)){const s=fs.readFileSync(f,'utf8');if(s.startsWith('{\"root\"')&&s.trim().split('\n').length>1)continue;let d;try{d=JSON.parse(s)}catch{d=JSON.parse(s.slice(s.indexOf('{'),s.lastIndexOf('}')+1))}if(typeof d==='string')d=JSON.parse(d);fs.writeFileSync(f,[JSON.stringify({root:d.root,viewport:d.viewport,count:d.count,missing:d.missing||false}),...(d.nodes||[]).map(n=>JSON.stringify(n))].join('\n')+'\n')}" git diff --no-index --stat -- .verify/fp/before .verify/fp/after 2>/dev/null→ changed files (stderr carries LF→CRLF warnings only; exit code 1 just means "differs"). For each:git diff --no-index -U0 -- <before> <after> 2>/dev/null(which nodes/styles changed), then Read both PNGs. Identical fingerprints → don't open the PNGs.- Verdicts:
- intended — the difference is in the area the task changed (task context +
git diff --stat <base>) and looks like that change. - regression — difference outside the intended change; visual break (overlap, clipping, misalignment, missing element, unreadable contrast); failed expectation; new error.
- broken-on-base — expectation failing on before too.
- unclear — cannot tell; say why.
/verifyrun without task context → visual differences areunclearwith a description, neverintended.
- intended — the difference is in the area the task changed (task context +
- Owners: styles →
styling-themes; clock →clock-engine-dev; wheel/range/timezone/PluginRegistry →plugin-dev; modal/events/config/validation/core/templates →core-architect; ARIA/keyboard semantics →accessibility; docs pages →docs-site. - New
broken-on-baselibrary bugs (not already listed) → append a row to the Confirmed table ofdocs/superpowers/bugs/2026-10-11-verify-findings.md(area, what happens vs expected, file:line if known, owner, repro). Log only — fixing is a separate task the user starts. - Write
.verify/SUMMARY.md:# /verify — <RUN_ID> MCP: <chrome-devtools|playwright> · Base: <ref> · Baseline: <yes|no> · Targets: <lib, docs> Tags: <@smoke, @wheel (← app/src/managers/plugins/wheel/WheelDragHandler.ts), …> Docs routes: <…> ## Verdicts | Target | Scenario | Shot / step | VP | Verdict | Evidence | Owner | |---|---|---|---|---|---|---| ## Expectations (after) | Target | Scenario | Step | Expected | Observed | Result | |---|---|---|---|---|---| ## New errors (after) ## Uncovered changes → proposed impact-map rows ## Notes (blocked steps, MCP switch, skipped targets, server log tails)
Step 6 — Stop servers (always, also after any failure in Steps 3-5)
servers.md §5 (TaskStop for every task id, then the port kill — TaskStop alone leaves node
listening on Windows); the count must be 0. Then unlink the junction (servers.md §0 "unlink
junction" row) so a later manual delete of .verify/ cannot reach app/node_modules.
Step 7 — Report
Relay SUMMARY.md briefly: MCP, counts per verdict, each regression (PNG paths + owner),
broken-on-base (known / newly logged), uncovered + proposed rows, new errors. Offer to route
regressions to their owners (they only edit code; you run /verify again afterwards). Fix
nothing inside /verify.
Guardrails
- Main agent only — never dispatch a subagent for any part of
/verify. - Local only — never
npm publish, deploy,git pushor any remote-changing command. - Never install or remove packages (
npm install/ci/uninstall/update/audit fix), not even inside.verify/sandboxes — missingnode_modules→ ask the user to run the install and wait. - Git read-only — never
add/commit/stash/checkout/restore/reset/ … (HARD RULES §6). - Never edit
app/,docs-app/,dist/by hand; writes go to.verify/**and the bug backlog (builds also writedist/). - Never touch
docs-app/package.jsonordocs-app/node_modules. - Junction safety — servers.md §0 / §6.
- Never a false PASS — every abort ends with an abort report, not a verdict.
- Maintenance — new demo section / option / uncovered file: propose scenario + map rows; add them after the user agrees.