[Feature] shadcn-audit: deterministic browser collection (collect)

Merged
CI
2d ago
+2026-16

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 for form_control and static). Per part it reads computed styles, the Playwright accessibility snapshot, a few ARIA attributes and a computed modal fact. 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.yml gets selectors (RubyUI part → CSS selector; null = shadcn renders no element), filled in for dialog, alert_dialog, sheet, command and form.
  • divergences.yml entries can carry a match key (part, property or attribute, both values) so collect applies them. The four existing entries are HTML/Behavior differences, so they stay prose-only.
  • The skill, grading.md, interactions.md and the README describe the new flow. --shadcn-url/--rubyui-url cover 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 dialog runs produced identical collect.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).
  • collect grades 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.yml gains fixed part selectors (null = shadcn renders no element); divergences.yml entries can carry a structured match that collect applies 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-url cover non-default ports.