| name | auditing-registry-consistency |
| description | Use when auditing the shuip registry for accumulated drift, silent failures, or convention violations — before a release, after a refactor or merge, when a newly added item doesn't appear in the build, or when a consumer reports `shadcn add` fetching incomplete files. |
Auditing Registry Consistency
Overview
The shuip registry has many implicit conventions (folder layout, import paths, per-category doc flow, catalog pinning) that the generator enforces only loosely. Several failure modes are silent — items skipped without errors, dead symlinks fumadocs doesn't read, path mappings that work in IDE but not at build, deps silently dropped from registryDependencies. This skill is a systematic audit pass that catches them.
CLAUDE.md has the layout reference; the creating-registry-item skill has the procedure for creating items. This one catches drift in what already exists.
Do not auto-fix during the audit. Surface findings grouped by severity, let the human triage. Auto-fixing conflates discovery with remediation and may erase context the human needs.
When to Use
- Before tagging a release
- After a merge that touched the registry, the generator, or path mappings
- When a new item silently fails to appear in
registry.json or on the docs site
- When a consumer reports a broken install (
shadcn add fetches incomplete files)
- After bulk renames, category moves, or workspace restructures
- Periodic hygiene pass
Procedure
Run each check, capture findings, report at the end. Don't fix mid-audit — the act of running bun registry:generate mid-pass cleans up some inconsistencies before you've recorded them.
1. Items missing component.tsx — silent skip
Generator logs [generate] skipping <cat>/<name>: no component.tsx and the item never reaches registry.json. Catches wrong-case (Component.tsx), missing file, or stub-only folders.
find packages/registry/items -mindepth 2 -maxdepth 2 -type d \
! -exec test -e {}/component.tsx \; -print
2. Blocks with stray index.mdx — wrong fumadocs collection
Block items should not contain index.mdx. Their docs are hand-written in apps/docs/content/blocks/<name>.mdx (separate fumadocs collection). A stray index.mdx makes the generator symlink to apps/docs/content/docs/blocks/, which then shows as a duplicate page under /docs/blocks/<name> while the canonical /blocks/<name> keeps the real version.
ls packages/registry/items/blocks/*/index.mdx 2>/dev/null
3. component.tsx shuip-internal imports that silently drop
parseShuipDeps (packages/registry/scripts/generate.ts) auto-detects @/components/ui/shuip/<...> imports into registryDependencies:
- One segment (
side-dialog) → the bare components-category item.
- Two segments (
tanstack-form/form-context) → the prefixed item (tsf-form-context), only if the first segment is a real category (isCategory).
So a stub import does not need a meta.shuip.json — it resolves automatically. meta.shuip.json with dependsOn is only for a dependency that is not expressed as a stub import (a transitive/runtime dep). The actual silent-failure modes to look for:
- a two-segment import whose category segment isn't one of
components / blocks / react-hook-form / tanstack-form / tanstack-query → silently dropped, consumer install missing the dep;
- a one-segment import to a
components item that doesn't exist → dangling registryDependency.
grep -rn "from '@/components/ui/shuip/" packages/registry/items/*/*/component.tsx
responsive-dialog (dependsOn: ["side-dialog"]) is the canonical meta.shuip.json example — note its dependsOn is now redundant with the single-segment auto-detection, but harmless (the generator dedups). Reach for dependsOn only when the dep isn't an import.
4. Examples with relative ./component imports
Examples must import via the stub alias (@/components/ui/shuip/<cat-subdir>/<name>) so the preview exercises the same path consumers will use after shadcn add. Relative imports compile locally but never test the install path.
grep -rn "from '\./component'" packages/registry/items/
5. Folder names with rhf- / tsf- / tsq- prefix
The generator adds the prefix from the CATEGORIES map (packages/registry/scripts/generate.ts). Manual prefixing produces rhf-rhf-foo.
ls packages/registry/items/react-hook-form/ | grep '^rhf-' || echo OK
ls packages/registry/items/tanstack-form/ | grep '^tsf-' || echo OK
ls packages/registry/items/tanstack-query/ | grep '^tsq-' || echo OK
6. tsconfig path-mapping hygiene
Two checks. (a) Mappings present in packages/registry/tsconfig.json but missing from apps/docs/tsconfig.json (or vice versa) break either IDE resolution or the docs build. (b) Mapping targets pointing to files that no longer exist.
diff <(jq -r '.compilerOptions.paths | keys[]' apps/docs/tsconfig.json | sort) \
<(jq -r '.compilerOptions.paths | keys[]' packages/registry/tsconfig.json | sort)
cd packages/registry && \
jq -r '.compilerOptions.paths | to_entries[] | "\(.key)\t\(.value[0])"' tsconfig.json | \
while IFS=$'\t' read -r alias target; do
base="${target%/\*}"
[ -e "$base" ] || ls "$base".* >/dev/null 2>&1 || echo "stale: $alias -> $target"
done
7. Broken / stale symlinks in apps/docs/content/docs/
After a rename or category move, the old symlink isn't auto-removed. Symlink targets that no longer exist cause fumadocs to render an empty doc or a build failure depending on collection wiring.
find apps/docs/content/docs -type l ! -exec test -e {} \; -print
8. 'use client' missing where required
Components using useState/useEffect/useRef/useCallback/useMemo/any use<X> hook (including useTheme, useFormContext) must declare 'use client' or they break in Server Component contexts (the App Router default). This check needs manual triage — many client hooks come from Radix transitively where the directive isn't needed.
for f in $(find packages/registry/items -name component.tsx); do
if grep -q "useState\|useEffect\|useRef\|useCallback\|useMemo\|useTheme\|useFormContext" "$f"; then
head -1 "$f" | grep -q "'use client'" || echo "missing 'use client': $f"
fi
done
9. Item completeness
For each item: should have default.example.tsx, should have at least one variant example, and should have index.mdx (unless it's a block, in which case the doc lives in apps/docs/content/blocks/).
find packages/registry/items -mindepth 2 -maxdepth 2 -type d \
! -exec test -e {}/default.example.tsx \; -print
find packages/registry/items -mindepth 2 -maxdepth 2 -type d \
! -path '*/blocks/*' ! -exec test -e {}/index.mdx \; -print
for d in $(find packages/registry/items -mindepth 2 -maxdepth 2 -type d); do
count=$(ls "$d"/*.example.tsx 2>/dev/null | wc -l)
[ "$count" -le 1 ] && echo "single example: $d"
done
10. MDX prose using the wrong stub path
The creating-registry-item skill says examples must use the stub alias matching the item's category subdir (react-hook-form/, tanstack-form/, tanstack-query/, or none for components). MDX prose snippets often copy-paste the bare @/components/ui/shuip/<name> and miss the subdir — leading copy-paste users to module-not-found.
for f in $(find packages/registry/items/react-hook-form packages/registry/items/tanstack-form packages/registry/items/tanstack-query -name index.mdx 2>/dev/null); do
cat=$(echo "$f" | sed 's|.*items/\([^/]*\)/.*|\1|')
grep "from '@/components/ui/shuip/" "$f" | grep -v "$cat" \
&& echo " ^ in $f (should include /$cat/)"
done
11. Workspace deps not pinned through catalogs
Catalog drift between workspaces breaks reproducibility and may cause subtle version mismatches at consumer end. Every shared external dep should be "catalog:" or "catalog:<name>".
for pkg in apps/*/package.json packages/*/package.json; do
jq -r '. as $p | (.dependencies // {}) + (.devDependencies // {}) | to_entries[] | "\($p.name)\t\(.key)\t\(.value)"' "$pkg"
done | grep -v 'catalog:\|workspace:\|^[^\t]*\t[^\t]*\t[0-9]' | grep -v 'biome\|husky\|knip\|portless\|rimraf\|turbo\|typescript\|@types/node\|@vercel\|shadcn' || echo OK
(Some root-tools — biome, husky, knip, turbo, typescript, etc. — are intentionally not in catalogs because they're devtools, not shared libs. Adjust the exclusion list to taste.)
12. turbo.json task outputs cover all generator side effects
generate.ts writes to packages/registry/{registry.json,__index__.ts,stubs/**} AND to apps/docs/content/docs/<cat>/<name>.mdx (symlinks) AND to apps/docs/content/docs/<cat>/.gitignore. Turbo's registry:generate task declares only the first set in its outputs, so a cache hit on this task leaves the cross-workspace side effects unrestored — on a fresh checkout, fumadocs may see no MDX in per-category folders and 404 docs pages.
jq '.tasks["registry:generate"].outputs' turbo.json
Expected: every path the generator writes is either declared as an output OR the task has "cache": false. Today the cross-workspace symlinks/.gitignore are not declared. Cross-workspace outputs is awkward in turbo; setting "cache": false on the task is the smaller honest fix.
13. index.mdx registryName frontmatter matches the prefixed folder name
Each non-block item's index.mdx has frontmatter registryName: <prefixed-name>. The value must match what the generator produces for that folder (i.e., <prefix>-<folder-name> for react-hook-form / tanstack-form / tanstack-query, or the bare folder name for components). A mismatch means <ItemExamples registryName={...}/> looks up the wrong key in REGISTRY_INDEX and renders an empty preview.
for f in $(find packages/registry/items -name index.mdx); do
declared=$(grep -m1 '^registryName:' "$f" | sed 's/^registryName:[[:space:]]*//; s/[[:space:]]*$//')
folder=$(echo "$f" | sed 's|.*items/\([^/]*\)/\([^/]*\)/index.mdx|\1\t\2|')
cat=$(echo "$folder" | cut -f1); name=$(echo "$folder" | cut -f2)
case "$cat" in
react-hook-form) expected="rhf-$name" ;;
tanstack-form) expected="tsf-$name" ;;
tanstack-query) expected="tsq-$name" ;;
*) expected="$name" ;;
esac
[ "$declared" = "$expected" ] || echo "mismatch: $f declared=$declared expected=$expected"
done
Reporting
Group findings by severity. For each:
- File path or pattern
- What's wrong, what it silently breaks
- Suggested fix (don't apply it — surface only)
Sample shape:
CRITICAL
packages/registry/items/components/foo/Component.tsx
Generator skips the folder (filename must be lowercase).
Fix: rename to component.tsx.
WARNING
packages/registry/items/components/bar/
Missing default.example.tsx; docs page renders empty <ItemExamples>.
Fix: add default.example.tsx importing via the stub alias.
Common Mistakes During the Audit
| Mistake | Why it matters |
|---|
Running bun registry:generate before recording findings | Generator may rewrite the .gitignore lists or clear stale stubs before you've inspected them. Inspect first, regen after. |
Treating [generate] skipping ... as benign | That warning means an item is invisible. Always investigate. |
Trusting git status to surface drift in generated files | registry.json, __index__.ts, stubs/ are gitignored. Read the file contents directly. |
| Auto-fixing during the audit | Conflates discovery with triage. Many "findings" turn out to be intentional once context is added. |
| Reporting only the file path | Include the silent-failure mode (what breaks, not just "X is wrong"). Pelavo triages by impact. |
Red Flags
If any of these are true during a build / dev session, run this audit:
[generate] N items processed returned a smaller N than expected after adding files
- A consumer reports a missing file after
shadcn add
- A docs page renders blank previews or 404s the preview iframe
- A merge / rebase touched
packages/registry/items/, packages/registry/scripts/generate.ts, turbo.json, or any tsconfig
- A new category was added but only one of
apps/docs/tsconfig.json and packages/registry/tsconfig.json got the new mapping