Skip to content

Feat/phase 11 schema and motion#65

Merged
pras75299 merged 9 commits into
mainfrom
feat/phase-11-schema-and-motion
May 28, 2026
Merged

Feat/phase 11 schema and motion#65
pras75299 merged 9 commits into
mainfrom
feat/phase-11-schema-and-motion

Conversation

@pras75299
Copy link
Copy Markdown
Owner

@pras75299 pras75299 commented May 27, 2026

Type

  • New component / block (registry)
  • CLI change (init / add / list / info / doctor / search / shadcn JSON)
  • Docs site (apps/www) — no UI redesign; e2e a11y/perf only
  • Build / scripts / CI
  • Bug fix — integrity validation, peer-deps schema asymmetry, add --all cwd restore, LCP observer timing
  • Refactor / chore — registry artifact regeneration with enriched metadata

Summary

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)

  • New optional fields on RegistryEntry: tags, peerDependencies, cssVariables, compatibility, accessibility, motion, relatedSlugs, usedByBlocks, assets, per-file integrity (SHA-384 SRI).
  • New slug-keyed source maps under registry/:
    • motion.json, tags.json, peer-dependencies.json, compatibility.json, accessibility.json, related-slugs.json, used-by-blocks.json
  • Stricter IsoDate validation (real calendar dates, not just regex).
  • resolvePathUnderDir retained for build-time path safety.

Build (scripts/build-registry.ts)

  • Loads and merges all metadata maps onto each registry entry.
  • Computes SHA-384 integrity hashes for every emitted file.
  • Regenerates registry.json and apps/www/public/registry/*.json with enriched payloads.

CLI (@uniqueui/cli-core / uniqueui-cli)

  • uniqueui add --all — greenfield bulk install (skips hero-* blocks; refuses non-empty components/ui unless --force; restores process.cwd() in finally).
  • add integrity checks — verifies per-file SRI before write; optional registry-root pin in components.json.
  • search — tag-aware ranking improvements.
  • list / info — surface new metadata fields.

Motion enforcement

  • scripts/check-reduced-motion.mjs — cross-checks registry/motion.json against component source (motion API usage ↔ metadata; full requires useReducedMotion or CSS prefers-reduced-motion).
  • Wired into pnpm test:repo via scripts/check-reduced-motion.test.ts.

CI / e2e (apps/www)

  • e2e-a11y job — @axe-core/playwright on 5 canonical routes; fails on critical/serious violations (wcag2a, wcag2aa, wcag21aa, best-practice).
  • e2e-perf job — LCP < 2.5s on /components, CLS < 0.05 on /components/moving-border (observers injected via addInitScript before navigation).
  • Playwright projects: a11y, perf (test:e2e:a11y, test:e2e:perf).

Validation

  • scripts/validate-registry.mjs extended 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-cli
  • pnpm build:registry — regenerates enriched registry.json + apps/www/public/registry/* (committed)
  • pnpm --filter uniqueui-cli build — tsup bundle succeeds
  • pnpm check:reduced-motion — 54 motion-using components covered
  • pnpm --filter @uniqueui/cli-core test — includes add-all.test.ts (14 tests)
  • pnpm --filter @uniqueui/registry-schema test — schema + cross-check tests (50 tests)
  • pnpm test:repo — includes check-reduced-motion.test.ts
  • pnpm --dir apps/www test:e2e:a11y — run locally or rely on CI e2e-a11y job
  • pnpm --dir apps/www test:e2e:perf — run locally or rely on CI e2e-perf job
  • Manually verified in apps/www dev 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)

Commit Message
9593d67 feat(schema): add motion, tags, compatibility, accessibility, peer-deps, cssVariables to registry
c3905b2 fix(schema): resolve peer-deps schema asymmetry, sync info.ts RegistryItem, tighten cssVariable regex
61dfe14 feat(schema,cli,ci): phase 12 — integrity SRI, cross-links, add --all, axe, perf budgets
0bc3c6d fix(cli,e2e): integrity validation, cwd restore, LCP observer, wcag21aa tag

Summary by CodeRabbit

  • New Features

    • CI now runs accessibility and performance audits; CLI gained an option to install all registry components.
    • Component registry enriched with tags, peer-deps, compatibility, accessibility and motion metadata; registry assets now carry integrity checks.
    • Search ranking improved to prioritize tag matches; many site UI color styles were refined for dark/light themes and reduced-motion support.
  • Tests

    • Added accessibility/performance e2e tests and reduced-motion/registry schema validation suites.
  • Chores

    • Registry build/validation tooling updated to emit and verify new metadata.

Review Change Stack

pras75299 added 4 commits May 27, 2026 17:20
…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>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
uniqueui-platform Ready Ready Preview, Comment May 27, 2026 5:59pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Warning

Review limit reached

@pras75299, we couldn't start this review because you've reached your PR review rate limit.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eb66f024-6763-4c06-ae09-9d6d6ff79d54

📥 Commits

Reviewing files that changed from the base of the PR and between bae2793 and 75f690b.

📒 Files selected for processing (1)
  • apps/www/e2e/a11y.spec.ts
📝 Walkthrough

Walkthrough

This 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.

Changes

Registry Metadata and Integrity System

Layer / File(s) Summary
Registry schema and types
packages/registry-schema/src/index.ts, packages/registry-schema/src/index.test.ts
Adds IntegrityHash and related schemas; extends RegistryFile and RegistryEntry with optional integrity, motion, tags, peerDependencies, compatibility, accessibility, cssVariables, relatedSlugs, and usedByBlocks, plus tests for new validations.
Metadata registries
registry/motion.json, registry/accessibility.json, registry/compatibility.json, registry/tags.json, registry/peer-dependencies.json, registry/related-slugs.json, registry/used-by-blocks.json
Introduces JSON maps that provide motion stance, accessibility notes, compatibility ranges, tags, peer-dependencies, related slugs, and used-by references for components.
Registry build pipeline
scripts/build-registry.ts
Loads new metadata maps, computes SHA-384 integrity for files, extracts CSS variables from Tailwind @theme blocks, and emits enriched RegistryEntry objects with optional metadata fields.
Registry validation
scripts/validate-registry.mjs
Validates metadata files against registry.json, enforces full-coverage maps, and detects stray/missing entries.
Updated per-component registry artifacts
apps/www/public/registry/*.json
Backfills integrity hashes and new meta fields (motion, tags, compatibility, accessibility, peerDependencies, relatedSlugs, cssVariables, usedByBlocks) across component manifests (60+ files).

CLI Integrity Verification and Add-All Command

Layer / File(s) Summary
addAll command
packages/cli-core/src/commands/add-all.ts, packages/cli-core/src/commands/add-all.test.ts
Adds addAll to bulk-install registry components (filters hero-), enforces a pre-install gate to avoid overwriting existing components unless --force/--dryRun, reports per-component status, and aggregates exit status.
Integrity in add()
packages/cli-core/src/commands/add.ts
Accepts per-file integrity, computes/ verifies registry root SHA-384 when pinned via components.json.registryIntegrity, and aborts on mismatches. Adds helpers for compute/verify SHA-384 and raw text fetching.
CLI wiring and types
packages/cli-core/src/index.ts, packages/cli/src/index.ts, packages/cli-core/src/commands/list.ts, packages/cli-core/src/commands/search.ts, packages/cli-core/src/commands/info.ts, packages/cli-core/src/validators/registry-schema.ts
Exports addAll, wires --all into the CLI, propagates optional metadata (tags, peerDependencies) into list/search/info, and makes RegistryEntry schema passthrough accept extra properties.

E2E Testing Infrastructure (Accessibility and Performance)

Layer / File(s) Summary
Playwright specs
apps/www/e2e/a11y.spec.ts, apps/www/e2e/perf.spec.ts
Adds an axe-based accessibility audit for five routes (asserting no critical/serious violations) and performance budget tests measuring LCP (<=2500ms) and CLS (<=0.05) via injected PerformanceObservers.
Test config and CI
apps/www/playwright.config.ts, apps/www/package.json, .github/workflows/ci.yml
Registers a11y and perf Playwright projects, adds npm scripts and @axe-core/playwright devDependency, and adds CI jobs that build the docs, run tests, and upload failure artifacts.

Reduced Motion Consistency Checking

Layer / File(s) Summary
Motion consistency checker
scripts/check-reduced-motion.mjs, scripts/check-reduced-motion.test.ts
Implements heuristics to detect motion API usage and reduced-motion guards, checks registry/motion.json for coverage and correctness (missing/stray entries and unguarded "full" claims), and provides unit + repository integration tests.
Repository script integration
package.json
Adds check:reduced-motion script and includes its test in the repo test runner.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through registry fields with care,

hashes tucked in files and metadata to spare.
Playwright checks the pages in the night,
addAll gathers components—safe and right.
Motion guards whisper, gentle and fair.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/phase-11-schema-and-motion

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (2)
packages/registry-schema/src/index.test.ts (1)

108-199: ⚡ Quick win

Add explicit validator tests for files[].integrity (and optionally assets) 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 win

Align RegistryEntry.files type with emitted integrity field.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6dc32b3 and 0bc3c6d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (94)
  • .github/workflows/ci.yml
  • apps/www/e2e/a11y.spec.ts
  • apps/www/e2e/perf.spec.ts
  • apps/www/package.json
  • apps/www/playwright.config.ts
  • apps/www/public/registry.json
  • apps/www/public/registry/3d-tilt-card.json
  • apps/www/public/registry/ambient-glow-card.json
  • apps/www/public/registry/animated-glowing-text-outline.json
  • apps/www/public/registry/animated-tabs.json
  • apps/www/public/registry/animated-timeline.json
  • apps/www/public/registry/aurora-background.json
  • apps/www/public/registry/bento-grid.json
  • apps/www/public/registry/blur-reveal.json
  • apps/www/public/registry/border-beam.json
  • apps/www/public/registry/caustic-light-card.json
  • apps/www/public/registry/chromatic-aberration-reveal.json
  • apps/www/public/registry/confetti-burst.json
  • apps/www/public/registry/count-up.json
  • apps/www/public/registry/cursor-trail.json
  • apps/www/public/registry/data-table.json
  • apps/www/public/registry/dot-grid-background.json
  • apps/www/public/registry/drawer-slide.json
  • apps/www/public/registry/dynamic-info.json
  • apps/www/public/registry/flip-card.json
  • apps/www/public/registry/floating-dock.json
  • apps/www/public/registry/glow-hero-section.json
  • apps/www/public/registry/gradient-text-reveal.json
  • apps/www/public/registry/hero-iridescent-sweep.json
  • apps/www/public/registry/hero-liquid-aurora-mesh.json
  • apps/www/public/registry/hero-logo-marquee.json
  • apps/www/public/registry/hero-magnetic-letters.json
  • apps/www/public/registry/hero-noise-dot-field.json
  • apps/www/public/registry/hero-reference-pulse.json
  • apps/www/public/registry/horizontal-scroll-gallery.json
  • apps/www/public/registry/hover-reveal-card.json
  • apps/www/public/registry/infinite-marquee.json
  • apps/www/public/registry/interactive-cursor.json
  • apps/www/public/registry/iridescent-foil-button.json
  • apps/www/public/registry/kinetic-variable-headline.json
  • apps/www/public/registry/limelight-nav.json
  • apps/www/public/registry/liquid-glass-panel.json
  • apps/www/public/registry/macbook-mock.json
  • apps/www/public/registry/magnetic-button.json
  • apps/www/public/registry/magnetic-text.json
  • apps/www/public/registry/meteors-card.json
  • apps/www/public/registry/mini-mac-keyboard.json
  • apps/www/public/registry/morphing-card-stack.json
  • apps/www/public/registry/morphing-modal.json
  • apps/www/public/registry/moving-border.json
  • apps/www/public/registry/multi-step-auth-card.json
  • apps/www/public/registry/nested-comments.json
  • apps/www/public/registry/notification-stack.json
  • apps/www/public/registry/outlined-mega-mark.json
  • apps/www/public/registry/particle-field.json
  • apps/www/public/registry/pen-cursor.json
  • apps/www/public/registry/radial-menu.json
  • apps/www/public/registry/refractive-cursor-lens.json
  • apps/www/public/registry/ripple.json
  • apps/www/public/registry/scramble-text.json
  • apps/www/public/registry/scroll-reveal.json
  • apps/www/public/registry/shader-mesh-gradient.json
  • apps/www/public/registry/shiny-text.json
  • apps/www/public/registry/shooting-stars-grid.json
  • apps/www/public/registry/skeleton-shimmer.json
  • apps/www/public/registry/spotlight-card.json
  • apps/www/public/registry/typewriter-text.json
  • apps/www/public/registry/word-rotate.json
  • package.json
  • packages/cli-core/src/commands/add-all.test.ts
  • packages/cli-core/src/commands/add-all.ts
  • packages/cli-core/src/commands/add.ts
  • packages/cli-core/src/commands/info.ts
  • packages/cli-core/src/commands/list.ts
  • packages/cli-core/src/commands/registry-build.ts
  • packages/cli-core/src/commands/search.test.ts
  • packages/cli-core/src/commands/search.ts
  • packages/cli-core/src/index.ts
  • packages/cli-core/src/validators/registry-schema.ts
  • packages/cli/src/index.ts
  • packages/registry-schema/src/index.test.ts
  • packages/registry-schema/src/index.ts
  • registry.json
  • registry/accessibility.json
  • registry/compatibility.json
  • registry/motion.json
  • registry/peer-dependencies.json
  • registry/related-slugs.json
  • registry/tags.json
  • registry/used-by-blocks.json
  • scripts/build-registry.ts
  • scripts/check-reduced-motion.mjs
  • scripts/check-reduced-motion.test.ts
  • scripts/validate-registry.mjs

Comment thread apps/www/e2e/a11y.spec.ts
Comment on lines +8 to +14
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" },
];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

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.

Comment thread apps/www/e2e/perf.spec.ts
Comment on lines +27 to +45
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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Comment on lines +44 to +48
"compatibility": {
"react": "18+",
"next": "14+",
"tailwind": "3+|4+",
"rsc": false,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 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:


🏁 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."
fi

Repository: 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."
fi

Repository: 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.

Comment on lines +50 to +53
"accessibility": {
"status": "n/a",
"screenReaderNotes": "Decorative background; should be aria-hidden."
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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.

Comment on lines +77 to 83
// 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 } : {}) });
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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/;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

@pras75299 pras75299 merged commit d943d54 into main May 28, 2026
12 checks passed
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