| name | debugging-shuip-silent-failures |
| description | Use when something in shuip silently doesn't work — Tailwind classes not applying, "module not found" after install, item missing from registry.json or docs site, a runtime crash referencing a React hook, wrong dep version installed at a consumer, fresh-checkout docs pages 404. The shared symptom is "no clear error message, the system just behaves wrong". |
Debugging shuip Silent Failures
Overview
shuip has several resolution chains stacked on top of each other: Tailwind v4 scans @source globs for utility classes, TS path mappings resolve @/... imports through stubs to real files, the registry generator emits artifacts that depend on a specific folder shape, fumadocs picks MDX from declared collections, turbo caches task outputs. Each chain fails silently: the build succeeds, runtime renders something, but the output is subtly wrong.
This skill is the symptom → chain lookup, plus the per-chain diagnostic walkthrough. It is reactive (you have a symptom). For proactive registry hygiene, use auditing-registry-consistency.
When to Use
Match the Symptom column below. If nothing matches, this skill probably doesn't apply — check the failing build logs first, then run auditing-registry-consistency if you suspect drift.
Symptom → Chain
| Symptom | Chain | Section |
|---|
| Tailwind class doesn't apply, page renders unstyled in places | CSS @source resolution | §1 |
Module not found '@/components/ui/shuip/...' at consumer after shadcn add, or in bun build:docs | TS path aliases / stubs | §2 |
Item not in registry.json; no docs page generated; preview empty | Registry generator scan | §3 |
| Docs page 404, blank, or duplicated under two URLs | fumadocs collections + symlinks | §4 |
Works at bun dev, breaks on fresh checkout / CI build | Turbo cache outputs | §5 |
| Runtime: "Server Component imported a Client Component" / hook error | 'use client' directive | §6 |
| Wrong version of a shared dep installed somewhere | Bun catalogs | §7 |
§1. Tailwind @source resolution
The chain (entry: apps/docs/src/styles/globals.css):
@import 'tailwindcss'; Tailwind scanner uses @source globs
@import 'fumadocs-ui/css/shadcn.css'; bridges --color-fd-* ↔ --*
@import 'fumadocs-ui/css/preset.css'; fumadocs styling
@import '@repo/ui/styles/base.css'; shuip @theme + :root/.dark + @source rules
Critical fact: @source paths in packages/ui/src/styles/base.css are relative to that file's location, not to the entry. Current rules cover:
../components/** → packages/ui/src/components
../../../registry/items/** → packages/registry/items
../../../../apps/docs/src/** → apps/docs/src
A file using Tailwind classes that's outside every glob is invisible to the scanner — its classes are silently stripped from the generated CSS.
Diagnostic
- Pin down the file containing the unapplied class.
- Compute its path relative to
packages/ui/src/styles/base.css (use realpath if unsure — don't compute by hand).
- Check whether it matches any
@source glob.
- If not, add a new
@source line in base.css, path relative to base.css. Prefer narrow globs (packages/charts/src/**/*.{ts,tsx} rather than packages/charts/**) — they scan faster.
- Invalidate the dev cache after editing
@source. Tailwind's PostCSS plugin caches scan results inside .next/ — without invalidation, the fix appears to do nothing. rm -rf apps/docs/.next/ and restart dev. bun clean is overkill.
Common causes
- A new workspace added (
packages/charts/, apps/dashboard/) — @source doesn't cover it yet.
- A new directory inside an existing workspace outside
src/ (e.g. apps/docs/views/) — @source only matches apps/docs/src/**.
- Stale
.next/ cache after editing @source rules.
- Tailwind class names built dynamically (e.g.
bg-${color}) — the scanner can't resolve them. Use a static safelist or refactor to an explicit class map.
§2. TS path aliases and stubs
The chain (both apps/docs/tsconfig.json and packages/registry/tsconfig.json declare these):
@/components/ui/shuip/* → packages/registry/stubs/* (auto-generated)
@/components/block/shuip/* → packages/registry/stubs/blocks/* (auto-generated)
@/components/ui/* → packages/ui/src/components/ui/* (shadcn primitives)
@/lib/utils → packages/ui/src/lib/utils
@/actions/shuip/places → packages/registry/items/.../places.action (hardcoded precedent)
The stubs (packages/registry/stubs/<...>.tsx) are generated by packages/registry/scripts/generate.ts. Each stub is a one-line re-export of the corresponding items/<cat>/<name>/component.tsx. The stub layer exists so the docs app can resolve @/components/ui/shuip/<...> to a real file during dev, mirroring what consumers will get after shadcn add.
Diagnostic
- Read the import path. Identify the alias prefix (
@/components/ui/shuip/..., @/components/ui/..., @/lib/utils, etc.).
- Look up the alias in both tsconfigs. Both must agree.
- For
@/components/ui/shuip/<cat-subdir>/<name> — does the stub exist? Check packages/registry/stubs/<cat-subdir>/<name>.tsx. If not, run bun registry:generate and verify [generate] N items processed includes your item.
- For a newly added alias (e.g. an extras-driven mapping): is it present in both tsconfigs? Forgetting either side breaks IDE OR the docs build.
Common causes
bun registry:generate not run after adding/renaming an item — stub absent.
- Folder name with
rhf-/tsf-/tsq- prefix — produces rhf-rhf-foo; stub lands at the doubled name.
- Path mapping added to one tsconfig only.
component.tsx missing or misnamed (Component.tsx capital) — generator silently skips; stub never created.
§3. Registry generator scan
packages/registry/scripts/generate.ts walks packages/registry/items/<category>/<name>/. Each item must have component.tsx (lowercase, exact). Categories come from the CATEGORIES config at the top of generate.ts; unknown directories now log a warning instead of being silently cast (since #39).
Diagnostic
- Run
bun registry:generate. Read every line of output — [generate] skipping <cat>/<name>: no component.tsx means an item is invisible. Investigate.
[generate] N items processed — verify N matches your expectation.
packages/registry/registry.json — search for your item's prefixed name (grep '"<prefix>-<name>"' registry.json).
- If a
meta.shuip.json declares dependsOn, verify those names appear in your item's registryDependencies in registry.json.
Common causes
component.tsx missing, misnamed, or wrong case.
- Folder placed under an unknown category (now logs warning).
- Folder name pre-prefixed (produces double prefix).
- For composite items:
meta.shuip.json missing or dependsOn wrong.
§4. fumadocs collections and symlinks
Two collections, defined in apps/docs/source.config.ts:
docs ← apps/docs/content/docs/<cat>/<name>.mdx (populated by symlinks from items/<cat>/<name>/index.mdx)
blocks ← apps/docs/content/blocks/<name>.mdx (real hand-written files; not symlinked)
The generator writes symlinks for every item that has index.mdx into content/docs/<cat>/. Block items must therefore NOT have index.mdx — if they do, the generator creates a stray symlink at content/docs/blocks/<name>.mdx which the docs collection picks up, producing a duplicate page.
Diagnostic
- Identify the URL of the missing/wrong page.
- Determine which collection should own it (
/docs/... → docs; /blocks/... → blocks).
- For
docs: is there a symlink at apps/docs/content/docs/<cat>/<name>.mdx? Does its target exist (i.e. packages/registry/items/<cat>/<name>/index.mdx)? Run find apps/docs/content/docs -type l ! -exec test -e {} \; -print to surface broken symlinks.
- For
blocks: is there a real file at apps/docs/content/blocks/<name>.mdx? (Not a symlink.)
- If the page appears under both
/docs/blocks/<name> and /blocks/<name>: a block has a stray index.mdx in its item folder. Delete it.
Common causes
- Block item containing
index.mdx.
- Stale symlink left by an item rename — generator writes new symlinks but doesn't delete old ones.
- New category added without rerunning the generator.
§5. Turbo cache outputs vs side effects
turbo.json declares registry:generate outputs as ["registry.json", "__index__.ts", "stubs/**"] — all under packages/registry/. But generate.ts also writes to apps/docs/content/docs/<cat>/<name>.mdx (symlinks) and apps/docs/content/docs/<cat>/.gitignore. Those cross-workspace artifacts are NOT in outputs, so a turbo cache hit restores only the in-package files; the symlinks may be missing on a fresh checkout.
Diagnostic
- If symptoms only show on a fresh checkout or in CI: suspect this layer.
- After
bun install, before bun build:docs: check ls apps/docs/content/docs/react-hook-form/*.mdx — symlinks should exist. If none exist or only some do, the cache restored partial state.
- Force-run the generator:
cd packages/registry && bun scripts/generate.ts. This bypasses turbo entirely.
- Compare: was the symptom the absence of symlinks? Yes → turbo cache hit was wrong.
Common causes
- Fresh checkout with hot turbo cache (local or remote).
- The skill
auditing-registry-consistency Check 12 covers this proactively. Fix at the root: set "cache": false on registry:generate in turbo.json, or declare cross-workspace outputs.
§6. 'use client' directive
Next.js 16 App Router defaults to Server Components. Any file using React state hooks (useState, useEffect, useRef, useCallback, useMemo), next-themes' useTheme, RHF's useFormContext, or browser APIs (window, document, navigator) must declare 'use client' as its first non-comment line.
Diagnostic
- Find the file in the runtime crash stack.
head -3 <file> — is 'use client'; present?
- Inspect imports/usages: any hook starting with
use? Any browser-global access?
- If yes and
'use client' is missing: add it.
Caveat
Radix primitives that internally need 'use client' already declare it. A purely presentational wrapper around them (like input-field/component.tsx) does NOT need 'use client' because no client-only API is touched at the wrapper level.
Common causes
- New component using
useState/useEffect without the directive.
- Migrated from a Pages Router codebase (where the distinction didn't exist).
- A
useTheme or useFormContext import accidentally landing in a Server file.
§7. Bun catalogs
Shared external versions live in root package.json → workspaces.catalog (default) and workspaces.catalogs.{fumadocs,radix,forms}. Workspaces reference them as "<pkg>": "catalog:" or "catalog:<name>". A workspace pinning a literal version ("react": "^19.2.5" instead of "catalog:") creates drift: bun installs the pinned version locally, but the catalog's version remains the source of truth elsewhere.
Diagnostic
- Symptom: a consumer of shuip reports an unexpected version of a dep, or two workspaces resolve different transitive trees.
- Inspect each workspace's
package.json — every shared dep should be "catalog:" (default) or "catalog:<name>" (named).
- If a literal version is pinned: was it intentional? If not, replace with the catalog reference.
- If a dep needs a different version in one workspace: it shouldn't be in the catalog. Move it out.
Common causes
bun add <pkg> run inside a workspace — bun adds the literal version, not the catalog reference.
- Forgotten replacement after a migration.
General tactics
Across all chains:
- Trace the chain end-to-end before guessing. Read each step, don't jump to "this looks wrong" without grounding.
- Verify with
ls / grep, not from memory. State drifts.
bun clean is a last resort, not a first try. It re-downloads everything. Prefer targeted invalidation (rm -rf apps/docs/.next, cd packages/registry && bun scripts/generate.ts).
- Compare to a working sibling. If
input-field works and your new item doesn't, diff every file — folder name, filename case, imports, MDX frontmatter.