Feat/phase 11 schema and motion#65
Conversation
…ps, cssVariables to registry Co-Authored-By: Prashant Kumar Singh <singh.prashantking@gmail.com>
…yItem, tighten cssVariable regex Co-Authored-By: Prashant Kumar Singh <singh.prashantking@gmail.com>
…, axe, perf budgets
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
More reviews will be available in 54 minutes and 14 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. 📝 WalkthroughWalkthroughThis PR adds registry metadata and integrity support, a bulk-install CLI command with integrity checks, Playwright accessibility and performance tests wired into CI, registry build/validation improvements (including integrity hash generation and CSS variable extraction), reduced-motion consistency checks and tests, and small dark-mode UI class fixes. ChangesRegistry Metadata and Integrity System
CLI Integrity Verification and Add-All Command
E2E Testing Infrastructure (Accessibility and Performance)
Reduced Motion Consistency Checking
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (2)
packages/registry-schema/src/index.test.ts (1)
108-199: ⚡ Quick winAdd explicit validator tests for
files[].integrity(and optionallyassets) to cover the new security path.This suite covers most new metadata fields, but it doesn’t validate the newly introduced per-file integrity contract. Adding one valid + one invalid integrity case here will harden regression detection around the SRI gate.
🧪 Suggested test additions
+ it("accepts a valid file integrity hash — CLI verification depends on this format", () => { + const ok = { + ...goodEntry, + files: [ + { + ...goodEntry.files[0], + integrity: "sha384-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + }, + ], + }; + expect(validate(RegistryEntry, ok).ok).toBe(true); + }); + + it("rejects malformed file integrity hashes — prevents silently invalid SRI metadata", () => { + const bad = { + ...goodEntry, + files: [{ ...goodEntry.files[0], integrity: "sha384-not_base64url??" }], + }; + expect(validate(RegistryEntry, bad).ok).toBe(false); + });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/registry-schema/src/index.test.ts` around lines 108 - 199, Add two unit tests that validate the new per-file integrity contract: create one "accepts files[].integrity with valid SRI" test that builds on goodEntry and sets files: [{ path: "foo.js", integrity: "sha384-<valid-base64>" }] (expect validate(RegistryEntry, ok).ok toBe(true)), and one "rejects files[].integrity with invalid SRI" test that uses files: [{ path: "foo.js", integrity: "not-an-sri" }] (expect validate(RegistryEntry, bad).ok toBe(false)); place both next to the other field tests that reference validate, RegistryEntry, and goodEntry and optionally mirror the same pattern for assets[] if assets support integrity.scripts/build-registry.ts (1)
50-55: ⚡ Quick winAlign
RegistryEntry.filestype with emittedintegrityfield.Line 51-55 omits
integrity, but Line 826 and Line 838 always emit it. This weakens compile-time guarantees for a now-required registry contract.Suggested type fix
type RegistryEntry = (typeof registry)[number] & { files: Array<{ path: string; content: string; type: string; + integrity: string; }>;Also applies to: 826-838
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/build-registry.ts` around lines 50 - 55, The files object in the RegistryEntry type is missing the emitted integrity property; update the RegistryEntry definition so each file in files includes an integrity: string field (i.e., change the files element type to { path: string; content: string; type: string; integrity: string }), and then adjust any code that constructs or consumes RegistryEntry.files to satisfy the new required integrity property (references: RegistryEntry and the files array used when emitting integrity at the spots mentioned).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/www/e2e/a11y.spec.ts`:
- Around line 8-14: The a11y job is failing because the baseline routes in the
PAGES array cause repeat serious/critical violations; update
apps/www/e2e/a11y.spec.ts by either (A) narrowing the rollout scope: remove or
replace the problematic routes from the PAGES constant (replace routes causing
color-contrast failures) so CI only tests stable pages, or (B) relax the
assertion logic in the a11y test that currently hard-fails on
'serious'/'critical' findings—change the test harness to treat those severities
as warnings or add an allowedViolations/threshold configuration instead of
failing the build; locate and update the PAGES array and the assertion block in
the same file to implement one of these fixes.
In `@apps/www/e2e/perf.spec.ts`:
- Around line 27-45: The test initializes window.__lcp to 0 which masks missing
LCP measurements; change the initialization in the page.addInitScript (where
window.__lcp and PerformanceObserver are set) to a sentinel (e.g., null or -1)
and then, after awaiting page.waitForTimeout and evaluating window.__lcp into
the lcp variable, explicitly assert that lcp is present (not the sentinel)
before comparing to LCP_BUDGET_MS; fail the test with a clear message if no LCP
entry was captured so a missing measurement cannot pass the budget check.
In `@apps/www/public/registry/mini-mac-keyboard.json`:
- Around line 44-48: The manifest declares "tailwind": "3+|4+" but the component
uses the Tailwind v4-only class pattern (e.g., the indicatorRing class string
contains "bg-linear-to-b"), so update the registry manifest entry to accurately
require Tailwind v4 by changing the "tailwind" field to "4+" (regenerate via the
build command) OR alter the component to use the v3-compatible class
("bg-gradient-to-b") if you intend to keep v3 support; specifically modify the
tailwind field in mini-mac-keyboard.json and then run pnpm build:registry, or
update the component's indicatorRing class to the v3 class and rebuild.
In `@apps/www/public/registry/particle-field.json`:
- Around line 50-53: The metadata says the decorative background "should be
aria-hidden" (accessibility.screenReaderNotes) but the component rendering the
decorative surface doesn't set that attribute; either update the component that
renders the decorative surface to include aria-hidden="true" (and optionally
role="presentation") on the outer decorative element, or change
accessibility.screenReaderNotes to remove the directive and reflect actual
behavior—locate the code that consumes the "particle-field" descriptor and add
aria-hidden to the decorative container element or update the JSON note
accordingly.
In `@apps/www/public/registry/radial-menu.json`:
- Line 12: The trigger button in RadialMenu uses aria-label={triggerAriaLabel}
which can be undefined; update the trigger to always provide an accessible name
(e.g., fallback to a sensible default like "Open radial menu" / "Close radial
menu" based on isOpen) so the motion.button that renders the trigger never has a
missing aria-label; reference RadialMenu, the triggerAriaLabel prop, the trigger
motion.button and the DefaultTrigger when implementing the fallback and ensure
aria-label reflects open state.
In `@packages/cli-core/src/commands/add-all.test.ts`:
- Line 55: The test declares stdoutSpy but never uses it, causing a lint error;
either remove the unused binding or keep the mock without assigning it. Replace
"const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() =>
true);" with either "vi.spyOn(process.stdout, 'write').mockImplementation(() =>
true);" to remove the unused variable, or rename it to "_stdoutSpy" if you
prefer to keep the reference; ensure the symbol stdoutSpy is not left unused and
the mock on process.stdout.write remains intact.
In `@packages/cli-core/src/commands/add.ts`:
- Around line 77-83: The code currently converts a malformed non-empty
fo.integrity into undefined (variables integrityRaw/integrity) and silently
skips integrity checks when pushing into files via files.push; instead, detect
when fo.integrity is a non-empty string that fails the SRI regex and surface an
error (throw or return a validation failure) so the entry is rejected rather
than silently dropping integrity. Update the validation around
integrityRaw/integrity to explicitly reject malformed integrity values with a
clear error message referencing the offending fo.path (or similar context)
before calling files.push.
In `@packages/registry-schema/src/index.ts`:
- Line 254: PeerDependenciesMap currently allows empty arrays but
RegistryEntry.peerDependencies requires non-empty arrays; update the record
value schema so each entry is a non-empty array (e.g., replace z.array(NpmDep)
with a non-empty array variant such as z.array(NpmDep).nonempty()) so the map
rejects empty peer-dependency lists up front and stays consistent with
RegistryEntry.peerDependencies.
In `@scripts/check-reduced-motion.mjs`:
- Line 45: The current GUARD_RE (/useReducedMotion\b|prefers-reduced-motion/) is
too permissive and matches tokens in comments/strings; replace it with a
stricter pattern that only matches actual guard usage (e.g. function call or
media-query/descriptor usage). Update the GUARD_RE definition to something like
/useReducedMotion\s*\(|prefers-reduced-motion\s*(?:[:(])/i so it requires a
following "(" for useReducedMotion or ":"/"(" for prefers-reduced-motion, and
apply this change to both occurrences (the GUARD_RE declaration and the other
check around lines 98-100) so only real guard usages pass the `"full"` gate.
---
Nitpick comments:
In `@packages/registry-schema/src/index.test.ts`:
- Around line 108-199: Add two unit tests that validate the new per-file
integrity contract: create one "accepts files[].integrity with valid SRI" test
that builds on goodEntry and sets files: [{ path: "foo.js", integrity:
"sha384-<valid-base64>" }] (expect validate(RegistryEntry, ok).ok toBe(true)),
and one "rejects files[].integrity with invalid SRI" test that uses files: [{
path: "foo.js", integrity: "not-an-sri" }] (expect validate(RegistryEntry,
bad).ok toBe(false)); place both next to the other field tests that reference
validate, RegistryEntry, and goodEntry and optionally mirror the same pattern
for assets[] if assets support integrity.
In `@scripts/build-registry.ts`:
- Around line 50-55: The files object in the RegistryEntry type is missing the
emitted integrity property; update the RegistryEntry definition so each file in
files includes an integrity: string field (i.e., change the files element type
to { path: string; content: string; type: string; integrity: string }), and then
adjust any code that constructs or consumes RegistryEntry.files to satisfy the
new required integrity property (references: RegistryEntry and the files array
used when emitting integrity at the spots mentioned).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 72375a4d-aa2f-40eb-a213-d5e6f93fb524
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (94)
.github/workflows/ci.ymlapps/www/e2e/a11y.spec.tsapps/www/e2e/perf.spec.tsapps/www/package.jsonapps/www/playwright.config.tsapps/www/public/registry.jsonapps/www/public/registry/3d-tilt-card.jsonapps/www/public/registry/ambient-glow-card.jsonapps/www/public/registry/animated-glowing-text-outline.jsonapps/www/public/registry/animated-tabs.jsonapps/www/public/registry/animated-timeline.jsonapps/www/public/registry/aurora-background.jsonapps/www/public/registry/bento-grid.jsonapps/www/public/registry/blur-reveal.jsonapps/www/public/registry/border-beam.jsonapps/www/public/registry/caustic-light-card.jsonapps/www/public/registry/chromatic-aberration-reveal.jsonapps/www/public/registry/confetti-burst.jsonapps/www/public/registry/count-up.jsonapps/www/public/registry/cursor-trail.jsonapps/www/public/registry/data-table.jsonapps/www/public/registry/dot-grid-background.jsonapps/www/public/registry/drawer-slide.jsonapps/www/public/registry/dynamic-info.jsonapps/www/public/registry/flip-card.jsonapps/www/public/registry/floating-dock.jsonapps/www/public/registry/glow-hero-section.jsonapps/www/public/registry/gradient-text-reveal.jsonapps/www/public/registry/hero-iridescent-sweep.jsonapps/www/public/registry/hero-liquid-aurora-mesh.jsonapps/www/public/registry/hero-logo-marquee.jsonapps/www/public/registry/hero-magnetic-letters.jsonapps/www/public/registry/hero-noise-dot-field.jsonapps/www/public/registry/hero-reference-pulse.jsonapps/www/public/registry/horizontal-scroll-gallery.jsonapps/www/public/registry/hover-reveal-card.jsonapps/www/public/registry/infinite-marquee.jsonapps/www/public/registry/interactive-cursor.jsonapps/www/public/registry/iridescent-foil-button.jsonapps/www/public/registry/kinetic-variable-headline.jsonapps/www/public/registry/limelight-nav.jsonapps/www/public/registry/liquid-glass-panel.jsonapps/www/public/registry/macbook-mock.jsonapps/www/public/registry/magnetic-button.jsonapps/www/public/registry/magnetic-text.jsonapps/www/public/registry/meteors-card.jsonapps/www/public/registry/mini-mac-keyboard.jsonapps/www/public/registry/morphing-card-stack.jsonapps/www/public/registry/morphing-modal.jsonapps/www/public/registry/moving-border.jsonapps/www/public/registry/multi-step-auth-card.jsonapps/www/public/registry/nested-comments.jsonapps/www/public/registry/notification-stack.jsonapps/www/public/registry/outlined-mega-mark.jsonapps/www/public/registry/particle-field.jsonapps/www/public/registry/pen-cursor.jsonapps/www/public/registry/radial-menu.jsonapps/www/public/registry/refractive-cursor-lens.jsonapps/www/public/registry/ripple.jsonapps/www/public/registry/scramble-text.jsonapps/www/public/registry/scroll-reveal.jsonapps/www/public/registry/shader-mesh-gradient.jsonapps/www/public/registry/shiny-text.jsonapps/www/public/registry/shooting-stars-grid.jsonapps/www/public/registry/skeleton-shimmer.jsonapps/www/public/registry/spotlight-card.jsonapps/www/public/registry/typewriter-text.jsonapps/www/public/registry/word-rotate.jsonpackage.jsonpackages/cli-core/src/commands/add-all.test.tspackages/cli-core/src/commands/add-all.tspackages/cli-core/src/commands/add.tspackages/cli-core/src/commands/info.tspackages/cli-core/src/commands/list.tspackages/cli-core/src/commands/registry-build.tspackages/cli-core/src/commands/search.test.tspackages/cli-core/src/commands/search.tspackages/cli-core/src/index.tspackages/cli-core/src/validators/registry-schema.tspackages/cli/src/index.tspackages/registry-schema/src/index.test.tspackages/registry-schema/src/index.tsregistry.jsonregistry/accessibility.jsonregistry/compatibility.jsonregistry/motion.jsonregistry/peer-dependencies.jsonregistry/related-slugs.jsonregistry/tags.jsonregistry/used-by-blocks.jsonscripts/build-registry.tsscripts/check-reduced-motion.mjsscripts/check-reduced-motion.test.tsscripts/validate-registry.mjs
| const PAGES = [ | ||
| { route: "/", label: "home" }, | ||
| { route: "/components", label: "components index" }, | ||
| { route: "/components/moving-border", label: "component slug" }, | ||
| { route: "/blocks", label: "blocks" }, | ||
| { route: "/docs/theming", label: "docs/theming" }, | ||
| ]; |
There was a problem hiding this comment.
A11y gate is currently non-mergeable on the chosen baseline routes.
This spec hard-fails serious/critical findings, and CI already reports repeat failures for routes in PAGES (notably color-contrast). As-is, this PR keeps the a11y job red until those route styles are remediated (or the rollout scope is adjusted).
Also applies to: 42-48
🧰 Tools
🪛 GitHub Actions: CI / Docs site a11y (axe-core)
[error] Command failed: pnpm --dir apps/www test:e2e:a11y (playwright test --project=a11y). 2 tests failed due to axe critical/serious violations.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/www/e2e/a11y.spec.ts` around lines 8 - 14, The a11y job is failing
because the baseline routes in the PAGES array cause repeat serious/critical
violations; update apps/www/e2e/a11y.spec.ts by either (A) narrowing the rollout
scope: remove or replace the problematic routes from the PAGES constant (replace
routes causing color-contrast failures) so CI only tests stable pages, or (B)
relax the assertion logic in the a11y test that currently hard-fails on
'serious'/'critical' findings—change the test harness to treat those severities
as warnings or add an allowedViolations/threshold configuration instead of
failing the build; locate and update the PAGES array and the assertion block in
the same file to implement one of these fixes.
| await page.addInitScript(() => { | ||
| window.__lcp = 0; | ||
| const observer = new PerformanceObserver((list) => { | ||
| const entries = list.getEntries(); | ||
| const last = entries[entries.length - 1] as PerformanceEntry & { startTime: number }; | ||
| if (last) window.__lcp = last.startTime; | ||
| }); | ||
| observer.observe({ type: "largest-contentful-paint", buffered: true }); | ||
| }); | ||
|
|
||
| await page.goto("/components", { waitUntil: "load" }); | ||
| // Allow a short drain for any deferred LCP candidates. | ||
| await page.waitForTimeout(500); | ||
|
|
||
| const lcp = await page.evaluate(() => window.__lcp); | ||
| expect( | ||
| lcp, | ||
| `/components LCP ${lcp.toFixed(0)}ms exceeds budget of ${LCP_BUDGET_MS}ms`, | ||
| ).toBeLessThan(LCP_BUDGET_MS); |
There was a problem hiding this comment.
LCP budget can pass even when no LCP was recorded.
Initializing window.__lcp to 0 makes a missing measurement look like a perfect score. Fail when no LCP entry is captured before checking the threshold.
Suggested patch
declare global {
interface Window {
- __lcp: number;
+ __lcp: number | null;
__cls: number;
}
}
@@
await page.addInitScript(() => {
- window.__lcp = 0;
+ window.__lcp = null;
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const last = entries[entries.length - 1] as PerformanceEntry & { startTime: number };
if (last) window.__lcp = last.startTime;
});
@@
const lcp = await page.evaluate(() => window.__lcp);
+ expect(lcp, "No LCP entry captured for /components").not.toBeNull();
expect(
- lcp,
+ lcp!,
`/components LCP ${lcp.toFixed(0)}ms exceeds budget of ${LCP_BUDGET_MS}ms`,
).toBeLessThan(LCP_BUDGET_MS);🧰 Tools
🪛 GitHub Actions: CI / Docs site a11y (axe-core)
[error] Command failed: pnpm --dir apps/www test:e2e:a11y (playwright test --project=a11y). 2 tests failed due to axe critical/serious violations.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/www/e2e/perf.spec.ts` around lines 27 - 45, The test initializes
window.__lcp to 0 which masks missing LCP measurements; change the
initialization in the page.addInitScript (where window.__lcp and
PerformanceObserver are set) to a sentinel (e.g., null or -1) and then, after
awaiting page.waitForTimeout and evaluating window.__lcp into the lcp variable,
explicitly assert that lcp is present (not the sentinel) before comparing to
LCP_BUDGET_MS; fail the test with a clear message if no LCP entry was captured
so a missing measurement cannot pass the budget check.
| "compatibility": { | ||
| "react": "18+", | ||
| "next": "14+", | ||
| "tailwind": "3+|4+", | ||
| "rsc": false, |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does Tailwind CSS v3 support the bg-linear-to-butility class, or isbg-gradient-to-b required in v3?
💡 Result:
Tailwind CSS v3 does not support the bg-linear-to-b utility class; you must use bg-gradient-to-b [1][2]. In Tailwind CSS v3, the gradient utility naming convention is bg-gradient-to-{direction} [1][3]. The bg-linear-to-* syntax was introduced in Tailwind CSS v4, where the utilities were renamed to align with expanded gradient capabilities, such as support for arbitrary angles and new color interpolation methods [4]. If your project is using Tailwind CSS v3, you should continue using bg-gradient-to-b [1][5].
Citations:
- 1: https://v3.tailwindcss.com/docs/background-image
- 2: https://windframe.dev/tailwind/classes/tailwind-gradient
- 3: https://wp-p.info/tpl_dictionary_rep.php?cat=dictionary-tailwind&fl=gradient&lang=en
- 4: https://tailwindcss.com/blog/tailwindcss-v4
- 5: https://www.reddit.com/r/tailwindcss/comments/1ms50f6/tailwind_css_bggradienttobr_not_working_in_my/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="apps/www/public/registry/mini-mac-keyboard.json"
echo "== Tailwind compatibility in file =="
nl -ba "$FILE" | sed -n '35,70p'
echo
echo "== Search for bg-linear-to-b / bg-gradient-to-b in file =="
rg -n "bg-(linear|gradient)-to-b" "$FILE" || true
echo
echo "== Also show any surrounding embedded source context if bg-linear-to-b exists =="
if rg -n "bg-linear-to-b" "$FILE" >/dev/null 2>&1; then
line=$(rg -n -o "bg-linear-to-b" "$FILE" | head -n1 | cut -d: -f1)
# print a small window around the first match
nl -ba "$FILE" | awk -v start="$line" 'NR>=start-20 && NR<=start+20 {print}'
else
echo "No bg-linear-to-b found."
fiRepository: pras75299/uniqueui
Length of output: 160
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="apps/www/public/registry/mini-mac-keyboard.json"
echo "== Tailwind compatibility in file =="
python3 - <<'PY'
import itertools
path = "apps/www/public/registry/mini-mac-keyboard.json"
start, end = 35, 70
with open(path, "r", encoding="utf-8") as f:
for i, line in enumerate(f, start=1):
if i < start:
continue
if i > end:
break
print(f"{i:5d}\t{line.rstrip()}")
PY
echo
echo "== Search for bg-linear-to-b / bg-gradient-to-b in file =="
rg -n "bg-(linear|gradient)-to-b" "$FILE" || true
echo
echo "== Show surrounding embedded source context if bg-linear-to-b exists =="
if rg -n "bg-linear-to-b" "$FILE" >/dev/null 2>&1; then
line=$(rg -n -o "bg-linear-to-b" "$FILE" | head -n1 | cut -d: -f1)
python3 - <<PY
path = "apps/www/public/registry/mini-mac-keyboard.json"
line = $line
start = max(1, line-20)
end = line+20
with open(path, "r", encoding="utf-8") as f:
for i, l in enumerate(f, start=1):
if i < start:
continue
if i > end:
break
print(f"{i:5d}\t{l.rstrip()}")
PY
else
echo "No bg-linear-to-b found."
fiRepository: pras75299/uniqueui
Length of output: 21949
Fix overstated Tailwind compatibility in mini-mac-keyboard.json
apps/www/public/registry/mini-mac-keyboard.json declares "tailwind": "3+|4+", but the embedded component uses the Tailwind v4-only class pattern bg-linear-to-b (within the indicatorRing class string). This makes the v3 compatibility claim unreliable for consumers using Tailwind v3—update the manifest (via pnpm build:registry) to "tailwind": "4+" (or change the component to v3-compatible bg-gradient-to-b if v3 support is intended).
Proposed regenerated diff
"compatibility": {
"react": "18+",
"next": "14+",
- "tailwind": "3+|4+",
+ "tailwind": "4+",
"rsc": false,
"ssr": true
},🧰 Tools
🪛 GitHub Actions: CI / Docs site a11y (axe-core)
[error] Command failed: pnpm --dir apps/www test:e2e:a11y (playwright test --project=a11y). 2 tests failed due to axe critical/serious violations.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/www/public/registry/mini-mac-keyboard.json` around lines 44 - 48, The
manifest declares "tailwind": "3+|4+" but the component uses the Tailwind
v4-only class pattern (e.g., the indicatorRing class string contains
"bg-linear-to-b"), so update the registry manifest entry to accurately require
Tailwind v4 by changing the "tailwind" field to "4+" (regenerate via the build
command) OR alter the component to use the v3-compatible class
("bg-gradient-to-b") if you intend to keep v3 support; specifically modify the
tailwind field in mini-mac-keyboard.json and then run pnpm build:registry, or
update the component's indicatorRing class to the v3 class and rebuild.
| "accessibility": { | ||
| "status": "n/a", | ||
| "screenReaderNotes": "Decorative background; should be aria-hidden." | ||
| }, |
There was a problem hiding this comment.
Accessibility metadata and implementation are inconsistent.
You declare on Line 52 that this decorative background “should be aria-hidden”, but the embedded component (Line 11 content) does not set aria-hidden on the decorative surface. Please either enforce it in the component or adjust the note to avoid misleading consumers.
As per coding guidelines, "Include standard HTML / ARIA attributes where applicable for accessibility."
🧰 Tools
🪛 GitHub Actions: CI / Docs site a11y (axe-core)
[error] Command failed: pnpm --dir apps/www test:e2e:a11y (playwright test --project=a11y). 2 tests failed due to axe critical/serious violations.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/www/public/registry/particle-field.json` around lines 50 - 53, The
metadata says the decorative background "should be aria-hidden"
(accessibility.screenReaderNotes) but the component rendering the decorative
surface doesn't set that attribute; either update the component that renders the
decorative surface to include aria-hidden="true" (and optionally
role="presentation") on the outer decorative element, or change
accessibility.screenReaderNotes to remove the directive and reflect actual
behavior—locate the code that consumes the "particle-field" descriptor and add
aria-hidden to the decorative container element or update the JSON note
accordingly.
| @@ -10,12 +10,14 @@ | |||
| { | |||
| "path": "radial-menu/component.tsx", | |||
| "content": "\"use client\";\n\nimport React, { useState, useRef, useEffect } from \"react\";\nimport { motion, AnimatePresence } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface RadialMenuItem {\n id: string;\n label: string;\n icon: React.ReactNode;\n onClick?: () => void;\n color?: string;\n}\n\nexport interface RadialMenuProps {\n /** Array of items to display in the menu */\n items: RadialMenuItem[];\n /** The radius of the circle the items burst outwards to */\n radius?: number;\n /** The starting angle in degrees (0 is right, 90 is bottom, 180 is left, 270 is top) */\n startAngle?: number;\n /** The ending angle in degrees */\n endAngle?: number;\n /** Stagger delay between each item animation */\n staggerDelay?: number;\n /** The central trigger icon (defaults to a plus icon if not provided) */\n triggerIcon?: React.ReactNode;\n /** Additional container classes */\n className?: string;\n /** Classes applied to the main trigger button */\n triggerClassName?: string;\n /** Classes applied to individual menu item buttons */\n itemClassName?: string;\n theme?: \"light\" | \"dark\";\n /** Accessible label for the trigger button */\n triggerAriaLabel?: string;\n}\n\nexport function RadialMenu({\n items,\n radius = 100,\n startAngle = -90,\n endAngle = 180,\n staggerDelay = 0.05,\n triggerIcon,\n className,\n triggerClassName,\n itemClassName,\n theme: _theme = \"dark\",\n triggerAriaLabel,\n}: RadialMenuProps) {\n const [isOpen, setIsOpen] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Close menu if clicking outside\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (\n containerRef.current &&\n !containerRef.current.contains(event.target as Node)\n ) {\n setIsOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", handleClickOutside);\n return () => document.removeEventListener(\"mousedown\", handleClickOutside);\n }, []);\n\n // Calculate positions for each item\n const calculatePosition = (index: number) => {\n // If there's only one item, place it in the middle of the available angle\n const totalItems = items.length;\n let angleRatio = 0;\n\n if (totalItems > 1) {\n // If we are drawing a full circle (difference is 360), divide by total items\n // Otherwise divide by totalItems - 1 to distribute from start to end inclusive\n const angleDiff = Math.abs(endAngle - startAngle);\n if (angleDiff >= 360) {\n angleRatio = index / totalItems;\n } else {\n angleRatio = index / (totalItems - 1);\n }\n } else {\n angleRatio = 0.5;\n }\n\n const currentAngle = startAngle + (endAngle - startAngle) * angleRatio;\n const currentAngleRad = (currentAngle * Math.PI) / 180;\n\n const x = radius * Math.cos(currentAngleRad);\n const y = radius * Math.sin(currentAngleRad);\n\n return { x, y };\n };\n\n const DefaultTrigger = (\n <div className=\"flex h-6 w-6 items-center justify-center\">\n <div className=\"absolute h-4 w-0.5 rounded-full bg-current transition-all\" />\n <div className=\"absolute h-0.5 w-4 rounded-full bg-current transition-all\" />\n </div>\n );\n\n return (\n <div ref={containerRef} className={cn(\"relative flex items-center justify-center\", className)}>\n <AnimatePresence>\n {isOpen &&\n items.map((item, index) => {\n const { x, y } = calculatePosition(index);\n\n return (\n <motion.button\n key={item.id}\n initial={{ opacity: 0, x: 0, y: 0, scale: 0 }}\n animate={{\n opacity: 1,\n x,\n y,\n scale: 1,\n transition: {\n type: \"spring\",\n stiffness: 300,\n damping: 20,\n delay: index * staggerDelay,\n },\n }}\n exit={{\n opacity: 0,\n x: 0,\n y: 0,\n scale: 0,\n transition: {\n type: \"spring\",\n stiffness: 300,\n damping: 25,\n // Stagger reverse on exit\n delay: (items.length - 1 - index) * staggerDelay * 0.5,\n },\n }}\n whileHover={{ scale: 1.1 }}\n whileTap={{ scale: 0.9 }}\n onClick={() => {\n item.onClick?.();\n setIsOpen(false);\n }}\n aria-label={item.label}\n title={item.label}\n className={cn(\n \"absolute flex h-12 w-12 items-center justify-center rounded-full shadow-lg border border-neutral-200 bg-white text-neutral-700 hover:text-neutral-900 hover:border-neutral-300 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-300 dark:hover:border-neutral-700 dark:hover:text-white transition-colors focus:outline-none focus:ring-2 focus:ring-neutral-400 dark:focus:ring-neutral-600 focus:ring-offset-2 dark:focus:ring-offset-neutral-950 z-0\",\n itemClassName\n )}\n style={item.color ? { color: item.color } : undefined}\n >\n {item.icon}\n </motion.button>\n );\n })}\n </AnimatePresence>\n\n <motion.button\n animate={{ rotate: isOpen ? 45 : 0 }}\n whileHover={{ scale: 1.05 }}\n whileTap={{ scale: 0.95 }}\n onClick={() => setIsOpen(!isOpen)}\n aria-expanded={isOpen}\n aria-label={triggerAriaLabel}\n className={cn(\n \"relative z-10 flex h-14 w-14 items-center justify-center rounded-full bg-neutral-900 text-white shadow-xl hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-neutral-400 focus:ring-offset-2 dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-100 dark:focus:ring-neutral-600 dark:focus:ring-offset-neutral-950 transition-colors\",\n triggerClassName\n )}\n >\n {triggerIcon || DefaultTrigger}\n </motion.button>\n </div>\n );\n}\n", | |||
There was a problem hiding this comment.
Trigger button can render without an accessible name.
On Line 12 (embedded component), aria-label={triggerAriaLabel} leaves the icon-only trigger unnamed when the prop is omitted, which is an accessibility blocker and can fail axe.
Suggested fix
- aria-label={triggerAriaLabel}
+ aria-label={triggerAriaLabel ?? "Toggle radial menu"}As per coding guidelines, "Include standard HTML / ARIA attributes where applicable for accessibility."
🧰 Tools
🪛 GitHub Actions: CI / Docs site a11y (axe-core)
[error] Command failed: pnpm --dir apps/www test:e2e:a11y (playwright test --project=a11y). 2 tests failed due to axe critical/serious violations.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/www/public/registry/radial-menu.json` at line 12, The trigger button in
RadialMenu uses aria-label={triggerAriaLabel} which can be undefined; update the
trigger to always provide an accessible name (e.g., fallback to a sensible
default like "Open radial menu" / "Close radial menu" based on isOpen) so the
motion.button that renders the trigger never has a missing aria-label; reference
RadialMenu, the triggerAriaLabel prop, the trigger motion.button and the
DefaultTrigger when implementing the fallback and ensure aria-label reflects
open state.
| warn: vi.spyOn(console, "warn").mockImplementation(() => {}), | ||
| }; | ||
| // Also silence process.stdout.write (used for the progress line) | ||
| const stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true); |
There was a problem hiding this comment.
Fix lint blocker: unused stdoutSpy at Line 55.
stdoutSpy is never used, so this fails @typescript-eslint/no-unused-vars and can block CI.
Suggested fix
-const stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
+const _stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);As per coding guidelines, "Always run lint before committing".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true); | |
| const _stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true); |
🧰 Tools
🪛 ESLint
[error] 55-55: 'stdoutSpy' is assigned a value but never used.
(@typescript-eslint/no-unused-vars)
🪛 GitHub Check: Test, Lint & Type-check
[warning] 55-55:
'stdoutSpy' is assigned a value but never used. Allowed unused vars must match /^_/u
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli-core/src/commands/add-all.test.ts` at line 55, The test declares
stdoutSpy but never uses it, causing a lint error; either remove the unused
binding or keep the mock without assigning it. Replace "const stdoutSpy =
vi.spyOn(process.stdout, 'write').mockImplementation(() => true);" with either
"vi.spyOn(process.stdout, 'write').mockImplementation(() => true);" to remove
the unused variable, or rename it to "_stdoutSpy" if you prefer to keep the
reference; ensure the symbol stdoutSpy is not left unused and the mock on
process.stdout.write remains intact.
| // Only accept well-formed SRI hashes — any other non-empty string (e.g. | ||
| // "md5-abc") would always fail verifyFileIntegrity and produce a | ||
| // misleading "tampered" error rather than a clear parse rejection. | ||
| const integrityRaw = typeof fo.integrity === "string" ? fo.integrity : undefined; | ||
| const integrity = integrityRaw && /^sha384-[A-Za-z0-9+/]+=*$/.test(integrityRaw) ? integrityRaw : undefined; | ||
| files.push({ path: fo.path, type: fo.type, content: fo.content, ...(integrity ? { integrity } : {}) }); | ||
| } |
There was a problem hiding this comment.
Reject malformed integrity instead of silently dropping it.
At Line 80–82, an invalid non-empty integrity value is converted to undefined, which skips verification later. This creates a downgrade path where tampered metadata avoids integrity checks.
Suggested fix
- const integrityRaw = typeof fo.integrity === "string" ? fo.integrity : undefined;
- const integrity = integrityRaw && /^sha384-[A-Za-z0-9+/]+=*$/.test(integrityRaw) ? integrityRaw : undefined;
+ const integrityRaw = typeof fo.integrity === "string" ? fo.integrity : undefined;
+ if (integrityRaw !== undefined && !/^sha384-[A-Za-z0-9+/]+=*$/.test(integrityRaw)) {
+ return null;
+ }
+ const integrity = integrityRaw;
files.push({ path: fo.path, type: fo.type, content: fo.content, ...(integrity ? { integrity } : {}) });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli-core/src/commands/add.ts` around lines 77 - 83, The code
currently converts a malformed non-empty fo.integrity into undefined (variables
integrityRaw/integrity) and silently skips integrity checks when pushing into
files via files.push; instead, detect when fo.integrity is a non-empty string
that fails the SRI regex and surface an error (throw or return a validation
failure) so the entry is rejected rather than silently dropping integrity.
Update the validation around integrityRaw/integrity to explicitly reject
malformed integrity values with a clear error message referencing the offending
fo.path (or similar context) before calling files.push.
| // Peer dependency list. Split from the existing `dependencies` array so the | ||
| // CLI can warn when a user's project is missing the right React/Next/ | ||
| // Tailwind versions without trying to install them automatically. | ||
| export const PeerDependenciesMap = z.record(Slug, z.array(NpmDep)); |
There was a problem hiding this comment.
Align PeerDependenciesMap with RegistryEntry.peerDependencies non-empty contract.
Line 254 currently permits empty arrays in registry/peer-dependencies.json, while Line 270 rejects them once merged into RegistryEntry. Make the map schema reject empties up front to fail earlier and keep contracts consistent.
🔧 Suggested fix
-export const PeerDependenciesMap = z.record(Slug, z.array(NpmDep));
+export const PeerDependenciesMap = z.record(Slug, z.array(NpmDep).min(1));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const PeerDependenciesMap = z.record(Slug, z.array(NpmDep)); | |
| export const PeerDependenciesMap = z.record(Slug, z.array(NpmDep).min(1)); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/registry-schema/src/index.ts` at line 254, PeerDependenciesMap
currently allows empty arrays but RegistryEntry.peerDependencies requires
non-empty arrays; update the record value schema so each entry is a non-empty
array (e.g., replace z.array(NpmDep) with a non-empty array variant such as
z.array(NpmDep).nonempty()) so the map rejects empty peer-dependency lists up
front and stays consistent with RegistryEntry.peerDependencies.
| // itself satisfies the invariant. The CSS path is allowed because some | ||
| // non-React-state animations (e.g. raw CSS keyframes) genuinely cannot use | ||
| // the hook. | ||
| const GUARD_RE = /useReducedMotion\b|prefers-reduced-motion/; |
There was a problem hiding this comment.
Guard detection is too permissive and can falsely pass "full" compliance.
At Line 45, matching bare useReducedMotion / prefers-reduced-motion tokens means comments or string literals can satisfy the check even when no real guard exists. That weakens the gate’s core invariant for "full".
Suggested fix
-const GUARD_RE = /useReducedMotion\b|prefers-reduced-motion/;
+const GUARD_RE =
+ /\buseReducedMotion\s*\(|`@media`[^{]*\(\s*prefers-reduced-motion\s*:\s*reduce\s*\)/; describe("detectReducedMotionGuard", () => {
@@
it("does not flag a component with no guard", () => {
expect(detectReducedMotionGuard("const x = useState(0);")).toBe(false);
});
+
+ it("does not flag comment-only mentions", () => {
+ expect(detectReducedMotionGuard("// prefers-reduced-motion")).toBe(false);
+ expect(detectReducedMotionGuard("/* useReducedMotion */")).toBe(false);
+ });
});Also applies to: 98-100
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/check-reduced-motion.mjs` at line 45, The current GUARD_RE
(/useReducedMotion\b|prefers-reduced-motion/) is too permissive and matches
tokens in comments/strings; replace it with a stricter pattern that only matches
actual guard usage (e.g. function call or media-query/descriptor usage). Update
the GUARD_RE definition to something like
/useReducedMotion\s*\(|prefers-reduced-motion\s*(?:[:(])/i so it requires a
following "(" for useReducedMotion or ":"/"(" for prefers-reduced-motion, and
apply this change to both occurrences (the GUARD_RE declaration and the other
check around lines 98-100) so only real guard usages pass the `"full"` gate.
…ral-400 across docs shell
…rast in docs and component grid
…Playwright 1.60 compat
…nal colors not mid-animation
…00ms→2.5s, networkidle)
Type
add --allcwd restore, LCP observer timingSummary
Phase 11 expands the registry contract with searchable taxonomy, compatibility, accessibility, motion, and peer-dependency metadata, wires it through
pnpm build:registry, and adds CLI integrity verification plus CI gates for reduced-motion honesty, axe a11y, and LCP/CLS perf budgets.Registry schema (
@uniqueui/registry-schema)RegistryEntry:tags,peerDependencies,cssVariables,compatibility,accessibility,motion,relatedSlugs,usedByBlocks,assets, per-fileintegrity(SHA-384 SRI).registry/:motion.json,tags.json,peer-dependencies.json,compatibility.json,accessibility.json,related-slugs.json,used-by-blocks.jsonIsoDatevalidation (real calendar dates, not just regex).resolvePathUnderDirretained for build-time path safety.Build (
scripts/build-registry.ts)integrityhashes for every emitted file.registry.jsonandapps/www/public/registry/*.jsonwith enriched payloads.CLI (
@uniqueui/cli-core/uniqueui-cli)uniqueui add --all— greenfield bulk install (skipshero-*blocks; refuses non-emptycomponents/uiunless--force; restoresprocess.cwd()infinally).addintegrity checks — verifies per-file SRI before write; optional registry-root pin incomponents.json.search— tag-aware ranking improvements.list/info— surface new metadata fields.Motion enforcement
scripts/check-reduced-motion.mjs— cross-checksregistry/motion.jsonagainst component source (motion API usage ↔ metadata;fullrequiresuseReducedMotionor CSSprefers-reduced-motion).pnpm test:repoviascripts/check-reduced-motion.test.ts.CI / e2e (
apps/www)e2e-a11yjob —@axe-core/playwrighton 5 canonical routes; fails on critical/serious violations (wcag2a,wcag2aa,wcag21aa,best-practice).e2e-perfjob — LCP < 2.5s on/components, CLS < 0.05 on/components/moving-border(observers injected viaaddInitScriptbefore navigation).a11y,perf(test:e2e:a11y,test:e2e:perf).Validation
scripts/validate-registry.mjsextended for new schema fields and cross-checks.Screenshots / video (UI changes only)
N/A — metadata, CLI, build, and CI changes only. No docs-site UI redesign in this PR.
Test plan
pnpm test— turbo:@uniqueui/cli-core(192),@uniqueui/registry-schema(50),www,uniqueui-clipnpm build:registry— regenerates enrichedregistry.json+apps/www/public/registry/*(committed)pnpm --filter uniqueui-cli build— tsup bundle succeedspnpm check:reduced-motion— 54 motion-using components coveredpnpm --filter @uniqueui/cli-core test— includesadd-all.test.ts(14 tests)pnpm --filter @uniqueui/registry-schema test— schema + cross-check tests (50 tests)pnpm test:repo— includescheck-reduced-motion.test.tspnpm --dir apps/www test:e2e:a11y— run locally or rely on CIe2e-a11yjobpnpm --dir apps/www test:e2e:perf— run locally or rely on CIe2e-perfjobapps/wwwdev server — N/A (no UI changes)New component checklist
Skip — no new components or blocks. Existing entries gain metadata only; registry artifacts regenerated via
pnpm build:registry.Related issues
Refs Phase 11 backlog — registry schema enrichment (tags, peer-deps, compatibility, accessibility, motion), reduced-motion gate,
add --all, axe in CI, perf budgets.Commits (4)
9593d67feat(schema): add motion, tags, compatibility, accessibility, peer-deps, cssVariables to registryc3905b2fix(schema): resolve peer-deps schema asymmetry, sync info.ts RegistryItem, tighten cssVariable regex61dfe14feat(schema,cli,ci): phase 12 — integrity SRI, cross-links, add --all, axe, perf budgets0bc3c6dfix(cli,e2e): integrity validation, cwd restore, LCP observer, wcag21aa tagSummary by CodeRabbit
New Features
Tests
Chores