[Feature] shadcn-audit: deterministic browser collection (collect)
Merged
CI
2d ago+2026+2026+0123456789012345678901234567890123456789-16-16-01234567890123456789
Why
The shadcn-audit skill had the agent collect every browser fact by hand through the chrome-devtools MCP: which selector to use for each RubyUI part, which properties to compare, which states to screenshot, so two runs on the same commit could disagree. This PR moves the objective part of the audit into the CLI, and the same SHAs now give the same facts and starting grades.
What
collect <folder> --run <dir> [--preview <path>]: drives both fixtures in headless Chromium (playwright-ruby-client) at 1280×800, light and dark, through closed, open, focus and after-esc (fewer states forform_controlandstatic). Per part it reads computed styles, the Playwright accessibility snapshot, a few ARIA attributes and a computedmodalfact. It also takes a screenshot and a pixel diff per state; the pixel diff is evidence and never sets a grade. Everything goes to<dir>/collect.json.- It grades the lines that don't need judgment: Look ❌ when a computed style differs, ARIA ⚠️ when the snapshot or an attribute differs, ✅ intentional when a structured divergence matches, ⏸ when a state wasn't reached, a selector matched a different count, a part was never visible, or a theme token or the environment (font) explains the difference. Spec, Native, animation and the per-kind behavior sequences stay with the agent, which can override a line only with a reason in its note.
component_map.ymlgetsselectors(RubyUI part → CSS selector;null= shadcn renders no element), filled in for dialog, alert_dialog, sheet, command and form.divergences.ymlentries can carry amatchkey (part,propertyorattribute, both values) socollectapplies them. The four existing entries are HTML/Behavior differences, so they stay prose-only.- The skill,
grading.md,interactions.mdand the README describe the new flow.--shadcn-url/--rubyui-urlcover the non-default ports from #593.
Design and plan: audit/docs/2026-11-01-collect-design.md, audit/docs/2026-11-01-collect-plan.md.
Verification
cd audit && bundle exec rake: 201 runs, 0 failures, standardrb clean. The driver smoke test runs real Chromium against local HTML and skips when Playwright isn't installed, so CI keeps running only the browser-free tests.- Against the live sandbox and preview: two
collect dialogruns produced identicalcollect.json. All five mapped components run. - Dialog no longer shows the #572 Look ❌ on the content or the ARIA ⚠️ on labelledby/describedby. That comes from the Dialog fixes after #572 (#580), not from the tool.
- A Codex review found seven issues (a 0/0 part graded pass, ARIA snapshots normalized like CSS, JS errors after load ignored, among others). All are fixed with regression tests.
Test steps
cd audit
bundle install
pnpm install && pnpm exec playwright install chromium
bundle exec rake
# with the sandbox (5179) and preview (3179) running, as in the skill's step 2:
bundle exec exe/shadcn-audit collect dialog --run /tmp/run-a --preview <preview path>
bundle exec exe/shadcn-audit collect dialog --run /tmp/run-b --preview <preview path>
diff <(jq 'walk(if type=="string" then sub("/run-(a|b)/"; "/run/") else . end)' /tmp/run-a/collect.json) \
<(jq 'walk(if type=="string" then sub("/run-(a|b)/"; "/run/") else . end)' /tmp/run-b/collect.json)
🤖 Generated with Claude Code
Summary by cubic
Moves the objective part of the shadcn-audit browser audit into the CLI, so the same commit now always yields the same collected facts and starting grades instead of depending on how the agent happens to collect them.
New Features
- Adds
collect <folder> --run <dir>: drives both fixtures headlessly in Chromium (light and dark, 1280×800) through closed, open, focus, and after-esc states, recording computed styles, accessibility snapshots, ARIA facts, and screenshots with pixel diffs (evidence only, never a grade). collectgrades the lines that need no judgment: Look on computed style differences, ARIA on snapshot or attribute differences, ⏸ when a state wasn't reached or a theme token or environment explains the difference.component_map.ymlgains fixed part selectors (null = shadcn renders no element);divergences.ymlentries can carry a structuredmatchthatcollectapplies automatically.- Waits for client-mounted fixtures and fonts, voids observations on JS errors, and pauses running animations before reading styles; a failed mount is reported as a mount error rather than a missing fixture, a never-reached part is inconclusive, and ARIA IDs are trimmed with dangling targets reported.
- Skill docs,
grading.md,interactions.md, and the README describe the new flow;--shadcn-url/--rubyui-urlcover non-default ports.