Skip to content

[Feature] shadcn-audit: check component PRs against shadcn Base UI + vega - #578

Merged
cirdes merged 31 commits into
mainfrom
feature/shadcn-audit
Oct 27, 2026
Merged

cirdes merged 31 commits into
mainfrom
feature/shadcn-audit

Conversation

@cirdes

@cirdes cirdes commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

Related issue

No issue. Started from reviewing #572, where checking a PR against shadcn by hand was too slow to actually happen.

Description

RubyUI mirrors shadcn/ui, but nothing checked that on PRs. This adds /shadcn-audit <PR | branch | component>, a Claude Code skill backed by a small Ruby CLI, that compares the components a PR touches with shadcn and leaves a checklist on the PR.

Which shadcn

The reasoning is in design/2026-10-26-shadcn-audit-design.md.

  • Behavior, HTML, ARIA and props: the Base UI base (apps/v4/registry/bases/base/ui/), shadcn's default since July 2026. React Aria is only a second opinion for accessibility.
  • Look: the vega style (styles/style-vega.css), which shadcn calls "the classic shadcn/ui look" and is closest to RubyUI today. Not nova (the create default) and not new-york-v4 (legacy).
  • Theme: neutral. RubyUI's tokens already match it except chart-1..5.

What a run does

  1. Pins the PR's base and head SHAs and the shadcn SHA, and works from a git archive checkout of the head. The main working tree is never touched.
  2. Per touched component, compares classes (Base UI .tsx + vega cn-* rules), HTML, props, ARIA, behavior and animation against shadcn. Both sides are driven in a browser with identical fixture content: a local shadcn sandbox (Base UI + vega + neutral) and RubyUI's docs app at the PR head.
  3. Checks shadcn against HTML, WAI-ARIA and ARIA in HTML (APG and MDN carry less weight), and flags JS that a native feature such as <dialog> or popover could replace.
  4. Prints a checklist (✅ ⚠️ ❌ ⏸, each line with values from both sides and a source) and writes an HTML page with screenshots side by side.
  5. Posts the checklist as one comment on the PR, only after an explicit ok, only on an open PR whose head hasn't moved, and only over the runner's own earlier comment.

What's in the PR

  • audit/: new subproject (CLI exe/shadcn-audit, Minitest + standardrb, CI job on Ruby 3.3 and 3.4). The CI job tests the tool itself. It doesn't audit PRs.
  • .claude/skills/shadcn-audit/: the procedure, plus references for grading, interactions per component kind, spec sources, and native HTML.
  • audit/component_map.yml and fixtures for Dialog, AlertDialog, Sheet and Command. Other components get ⏸ lines until mapped.
  • audit/divergences.yml: deliberate differences, registered in the PR that introduces them.
  • gem/AGENTS.md and CLAUDE.md: agents writing components start from Base UI + vega and prefer native HTML.

Acceptance on #572

Recorded in audit/test/fixtures/acceptance_572.md. The run found everything we found by hand. The overlay matches; the Dialog content still has the new-york look; exit animations are missing; AlertDialog, Sheet and Command are <div>s that could be native <dialog>; and there's no aria-labelledby/aria-describedby. It also found a dark-mode bug: the Dialog has no text-foreground, so its text renders black on near-black.

Each run tracks shadcn main and records the SHA it used. We can pin the reference later if drift between runs becomes a problem.

Testing instructions

  1. cd audit && bundle exec rake (118 runs, 0 failures; standardrb clean).
  2. bundle exec exe/shadcn-audit workspace then bundle exec exe/shadcn-audit reference dialog: the parts list shows vega classes such as rounded-xl and ring-1 on dialog-content.
  3. In Claude Code, run /shadcn-audit 572. It needs gh authenticated, Node/pnpm, and the chrome-devtools MCP. It asks before posting anything.

🤖 Generated with Claude Code

cirdes and others added 25 commits October 26, 2026 18:28
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…argets

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ures

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ted SHA

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re bundling

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also: repo in the markdown header, source pointer on pass lines, drop render --out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… own comment

The upsert PATCHed the first marked comment from any author; a maintainer
token can edit other people's comments, so it now matches only comments by
the authenticated gh user. It also refuses when the PR state isn't OPEN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- exe/shadcn-audit reports report, argument, missing-file and JSON errors
  as one {"error"} object with exit 1 (CLI.run).
- workspace re-applies the sparse paths on every sync; reference raises
  ConfigError when the registry or style file is missing, so a broken
  workspace no longer reads as "no shadcn equivalent".
- theme raises when the shadcn light or dark block is missing or empty.
- sandbox skips null shadcn entries when adding components.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sandbox stamps the workspace shadcn SHA and build time, returns them as
  built_from_sha/built_at, and re-adds the mapped components with
  --overwrite when the workspace SHA moves (refreshed: true).
- target fetches refs/pull/N/head and main from
  https://github.com/ruby-ui/ruby_ui.git into refs/shadcn-audit/main
  instead of trusting origin, which is the fork in a fork clone.
- target surfaces cross_repository; the skill shows the diff of executable
  files and asks before booting a fork's preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…out mise

- The checklist says "(not in component_map.yml)" for an unmapped folder
  and keeps "(no shadcn equivalent)" for a mapped shadcn: null; non-Look
  lines label the reference value "shadcn:" instead of "vega:".
- preview boot prefixes Ruby commands with `mise exec --` only when mise is
  on PATH.
- report_572.json cites the right anchors: overlay style-vega.css:464,
  content style-vega.css:468, ARIA bases/base/ui/dialog.tsx:122-138.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- SKILL.md, audit/README.md and gem/AGENTS.md list the prerequisites (Ruby
  3.4 via mise or equivalent, gh, Node/pnpm, chrome-devtools MCP) and run
  the servers in Herdr panes only when HERDR_ENV=1, else as background
  processes, stopped at the end.
- A newly mapped component also needs audit/fixtures/<folder>/.
- grading.md cites style-vega.css:464/468 and dialog.tsx:122-138.
- interactions.md: same viewport on both sides with the actual size
  recorded; focus checks need a real click, not .click().
- sandbox.yml quotes its version pins; CLAUDE.md says lockfiles where
  present; .dockerignore skips .shadcn-audit/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e design approved

The plan was an execution script for the build and is now superseded by
the code, tests and history. The design stays as the record of why the
audit mirrors Base UI + vega.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 82 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread .claude/skills/shadcn-audit/SKILL.md Resolved
Comment thread .claude/skills/shadcn-audit/SKILL.md Resolved
Comment thread audit/divergences.yml Resolved
Comment thread audit/lib/shadcn_audit/reference.rb Resolved
Comment thread audit/lib/shadcn_audit/report.rb Resolved
Comment thread audit/lib/shadcn_audit/report.rb Resolved
Comment thread audit/lib/shadcn_audit/theme.rb Resolved
Comment thread CLAUDE.md Resolved
Comment thread audit/lib/shadcn_audit/workspace.rb Resolved
cirdes and others added 4 commits October 26, 2026 21:42
…rules

Show the whole three-dot PR diff before booting a cross-repository head,
and record in the design that the PR head is untrusted code. Grading:
a shared normative miss is a warning, native basis no longer covers
labelling, a registered divergence covers only what it describes. Fix
the native-html table cell, list every CI job in CLAUDE.md, and state
that shared-code candidates are intentionally the full mapped set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…R comment

Report.load now requires a source on every pass/warn/fail line, an
http(s) source on a spec-basis pass, sandbox_lock as an object, repo and
both SHAs on target, object theme diffs and a non-empty string folder.
The PR comment header shows full 40-char SHAs; the terminal keeps 8.
Covers the markdown fence escalation with a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`shadcn add` pulls components from the published registry, not from the
workspace clone, so `sandbox` stops reporting built_from_sha and
refreshed_from and outputs registry: "live" with built_at. The workspace
SHA stays only as a refresh trigger, and the SKILL drops the "differs"
environment line. Drop StrictMode from the sandbox entry (it double-runs
effects Base UI doesn't expect in production) and pin tw-animate-css
1.4.0 like the docs app; fix the mangled sandbox.yml header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Malformed YAML comes back as one {"error"} object; component_map rejects
non-mapping entries; divergences require component, rubyui, shadcn and a
reason that isn't TODO. Interpolated template literals no longer leak
fragments into effective classes. Shell errors name the command and fall
back to the exit status. Theme raises the intended ArgumentError when a
theme has no cssVars. The HTML renderer requires fileutils and reads its
template as UTF-8; the preview fixture glob is safe for odd audit paths;
Workspace#clone becomes clone_repo; the Rakefile requires standard/rake
outright.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
component_map.yml maps cmdk's CommandItem value and CommandGroup heading
(RubyUI title), with CommandItem text/href as RubyUI-only kwargs. Tests:
divergences no longer depend on the live registry being empty for sheet,
the workspace-clone name check skips visibly when there is no clone,
target_test uses a real ComponentMap, theme_test's with_css fails on a
no-op edit, and the pinned workspace sync asserts the detached
FETCH_HEAD checkout and the rev-parse result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 34 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread audit/test/workspace_test.rb Resolved
Comment thread audit/lib/shadcn_audit/divergences.rb Resolved
…d non-list divergences

Subprocess tests no longer assume an empty stderr: in CI any process that
loads bundler prints constant-redefinition warnings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cirdes
cirdes merged commit f115108 into main Oct 27, 2026
10 checks passed
@cirdes
cirdes deleted the feature/shadcn-audit branch October 27, 2026 01:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant