SuperAPI AI Directory

shadcn-agents-rsc-boundary-validator

Agents & models

View repository

Updated 10-15 22:18

About this skill

shadcn ui : RSC Boundary Validator (Agent / Validator)

This skill is an AGENT skill. It does NOT teach how to compose RSC vs client trees (that is shadcn-impl-rsc-vs-client-boundaries, B10). It teaches an automated AUDIT : given a project tree, classify every shadcn import, verify the "use client" directive is in the right files, in the right form, in the right place, and that the boundary crossing follows the React Server Components rules.

ALWAYS run the validator as a six-point checklist in a fixed order. NEVER stop at the first failure ; a project usually has several boundary defects layered together, and the checklist surfaces all of them in one pass.

Companion Skills

  • shadcn-impl-rsc-vs-client-boundaries (B10) : the rulebook that defines WHICH primitives need the directive and WHY ; this validator enforces those rules. The full per-primitive matrix lives in B10 references/methods.md (60 rows).
  • shadcn-core-cli : components.json schema including the rsc flag, shadcn add --overwrite for regenerating a file whose directive is wrong.
  • shadcn-impl-component-install : the install workflow that emits files with or without the directive ; use this skill if the validator reports a missing-directive file and you want to re-add cleanly.
  • shadcn-impl-framework-integration : per-framework defaults (App Router -> rsc:true, Vite -> rsc:false, Astro client island -> rsc:false).

Quick Reference : Validator Workflow

INPUT  : a repo path with components.json + components/ui/* + app/ (or pages/)
PHASE 1 : scan components.json -> read `rsc` flag, detect framework
PHASE 2 : classify every file under components/ui/* and app/ using
          the per-primitive matrix (RSC-safe / client-required / mixed)
PHASE 3 : verify the `"use client"` directive is present where required,
          absent where forbidden, well-formed where present
PHASE 4 : scan for boundary-violation props (onClick / function / class
          instance passed from server file to client file)
PHASE 5 : scan for Provider-in-server-layout anti-pattern
PHASE 6 : emit a verdict report : PASS / WARN / FAIL with file + line
OUTPUT : 6-point checklist with PASS / FAIL per item + remediation hint

ALWAYS run all six phases even on small projects ; the cheap phases (grep) cost milliseconds and catch the silently-broken cases.

Quick Reference : 6-Point Checklist

[1] components.json `rsc` flag matches the framework :
    App Router -> rsc:true.
    Pages Router / Vite / Astro / Remix / TanStack Start -> rsc:false.

[2] Every file importing a CLIENT-REQUIRED primitive starts with the
    directive :
    Dialog / Sheet / Drawer / AlertDialog / Popover / Tooltip /
    HoverCard / Form / Select / Combobox / Command / Sonner (Toaster
    mount) / Tabs / Accordion / Carousel / Sidebar / Calendar /
    DropdownMenu / ContextMenu / Menubar / NavigationMenu / Switch /
    Slider / Checkbox / RadioGroup / Collapsible / ScrollArea /
    InputOTP / Toggle / ToggleGroup / Resizable / Chart / Progress.

[3] No `app/layout.tsx` / `app/page.tsx` / `app/template.tsx` carries
    the directive (defeats RSC for the whole subtree).

[4] Every `"use client"` is :
    - the FIRST non-whitespace / non-comment line of the file,
    - double-quoted or single-quoted (NEVER backticks, NEVER a
      template literal, NEVER inside an `if`),
    - file-level (no per-function or per-component attempt).

[5] Every context Provider (next-themes ThemeProvider, TanStack Query
    QueryClientProvider, jotai Provider, zustand Provider, Sonner
    Toaster, OverlayProvider, NuqsAdapter) lives in a dedicated
    `"use client"` wrapper file ; never imported directly into
    `app/layout.tsx` or any server component.

[6] No server file (no `"use client"` directive) passes a function
    (`onClick`, `onChange`, `onSubmit`, `onSelect`, ...) or a non-
    serialisable instance (class instance, Decimal, Date with custom
    methods, Map<key, classInstance>) to a client child.

For ripgrep / find one-liners that implement each check see references/methods.md §3.

Quick Reference : Per-Primitive Lookup Matrix

The validator inherits the canonical per-primitive table from shadcn-impl-rsc-vs-client-boundaries references/methods.md §3 (60 rows). The condensed lookup used by this validator :

CLIENT-REQUIRED (must carry "use client") :
  Accordion, AlertDialog, Calendar, Carousel, Chart, Checkbox,
  Collapsible, Combobox, Command, ContextMenu, Dialog, Drawer,
  DropdownMenu, Form (react-hook-form), HoverCard, InputOTP, Menubar,
  NavigationMenu, Popover, Progress, RadioGroup, Resizable,
  ScrollArea, Select, Sheet, Sidebar, Slider, Sonner Toaster,
  Switch, Tabs, Toggle, ToggleGroup, Tooltip.

RSC-SAFE (must NOT carry "use client" unless the parent file also
imports a client primitive) :
  Alert, AspectRatio, Avatar, Badge, Breadcrumb, Button, Card,
  Empty, Field, Input, InputGroup, Item, Kbd, Label, Native Select
  (without onChange), Pagination, Separator, Skeleton, Spinner,
  Table primitives (Table / TableRow / ...), Textarea, Typography.

MIXED (the export bundle includes both a client root and pure-HTML
subcomponents ; the emitted file is client) :
  AlertDialog (Trigger + Content + body), Direction.

Rule of thumb the validator uses : if the source file imports anything from @radix-ui/* (other than @radix-ui/react-label and @radix-ui/ react-separator) or from cmdk / embla-carousel-react / vaul / react-day-picker / react-resizable-panels / react-hook-form / sonner / @tanstack/react-table / recharts, the file MUST carry the directive. See references/methods.md §2 for the full grep pattern and §5 for the matrix snapshot.

Quick Reference : Three Verdict Levels

PASS  : all six checks succeed ; the project's RSC boundary is sound.
WARN  : at least one rule fired on a RSC-safe primitive (cosmetic
        over-clientification : Card / Badge / Button with the directive
        for no reason ; ships extra JavaScript but does not crash).
FAIL  : at least one rule fired on a CLIENT-REQUIRED primitive (the
        project WILL crash at render with `useState is not a function`
        or similar) ; a Provider was found in a server layout ; a
        server file passed a function to a client child ; the `rsc`
        flag mismatches the framework.

ALWAYS report ALL failures in a single verdict ; never short-circuit.

Decision Tree : Verifying a Single File

For file F :

1. Read first non-comment, non-blank line.
2. If F is `app/layout.tsx` / `app/page.tsx` / `app/template.tsx` :
     and line 1 == `"use client"` -> FAIL (rule 3)
3. Else if line 1 == `"use client"` :
     verify the directive is well-formed (rule 4)
     mark F as a CLIENT file ; continue.
4. Else (no directive on line 1) :
     scan imports for CLIENT-REQUIRED primitives or for any
     `@radix-ui/*` package (minus Label / Separator) or any
     cmdk / vaul / embla / react-day-picker / react-hook-form /
     sonner / @tanstack/react-table / recharts dependency.
     If any match -> FAIL (rule 2 ; directive missing).
     If only RSC-safe primitives -> file is OK as server.
5. For every JSX child element rendered in F :
     if the element is a known client component (imported from a
     "use client" file) AND F is server (no directive) AND a
     prop on that element is a function literal, an arrow function,
     a class instance, a Date with custom methods, or a Map ->
     FAIL (rule 6).

See references/examples.md for two worked traces (broken vs correct) of this decision tree.

Pattern : Running the Validator

# Tier 1 : two-second grep audit. Run in every shadcn project before merge.
grep -L '^"use client"' \
  components/ui/{accordion,alert-dialog,calendar,carousel,chart,checkbox,collapsible,combobox,command,context-menu,dialog,drawer,dropdown-menu,form,hover-card,input-otp,menubar,navigation-menu,popover,progress,radio-group,resizable,scroll-area,select,sheet,sidebar,slider,sonner,switch,tabs,toggle,toggle-group,tooltip}.tsx 2>/dev/null
# Expected output : EMPTY. Any filename printed is FAIL.

grep -l '^"use client"' \
  components/ui/{alert,aspect-ratio,avatar,badge,breadcrumb,button,card,empty,field,input,input-group,item,kbd,label,pagination,separator,skeleton,spinner,table,textarea}.tsx 2>/dev/null
# Expected output : EMPTY. Any filename printed is WARN.

grep -l '^"use client"' app/layout.tsx app/page.tsx app/template.tsx 2>/dev/null
# Expected output : EMPTY. Any filename printed is FAIL (rule 3).

jq '.rsc' components.json
# Expected : true for App Router. false for Vite / Pages / Astro / Remix.

ALWAYS treat the grep output as authoritative for rules 2, 3, and the RSC-safe over-clientification check (WARN). For rules 4 (well-formed directive), 5 (Provider wrappers), and 6 (boundary crossing) use the extended ripgrep patterns documented in references/methods.md §3.

Pattern : Reporting Format

RSC Boundary Audit : <repo-name>

[1] components.json rsc flag      :  PASS  (rsc:true matches framework=app-router)
[2] Client primitive directives    :  FAIL
      MISSING in components/ui/dialog.tsx       (imports @radix-ui/react-dialog)
      MISSING in components/ui/sheet.tsx        (imports @radix-ui/react-dialog)
[3] Layout / page / template not client : FAIL
      OVER-CLIENT app/layout.tsx
[4] Directive well-formedness      :  PASS  (all 12 directives valid)
[5] Provider wrappers              :  FAIL
      ThemeProvider imported directly from next-themes in app/layout.tsx
      Wrap in components/theme-provider.tsx with "use client"
[6] Boundary props                 :  PASS  (no inline functions across server->client)

Verdict : FAIL. 3 critical defects + 0 warnings.
Run remediation : see shadcn-impl-rsc-vs-client-boundaries B10.

ALWAYS print a section per rule with a single PASS / WARN / FAIL keyword and the offending paths + reason. NEVER aggregate ; per-file detail is what the developer needs to fix.

Pattern : Remediation Hand-Off

After a FAIL verdict the validator does NOT auto-fix. It hands off :

Defect class Remediation skill
Rule 1 : rsc flag wrong shadcn-core-cli : edit components.json, rerun add
Rule 2 : missing directive on client file shadcn-impl-component-install : shadcn add <name> --overwrite
Rule 3 : layout marked client shadcn-impl-rsc-vs-client-boundaries §"Server Shell + Client Island"
Rule 4 : directive form wrong manual fix : move to first line, double-quote it
Rule 5 : Provider in server layout shadcn-impl-rsc-vs-client-boundaries §"Provider Wrapper"
Rule 6 : function/instance across boundary shadcn-impl-rsc-vs-client-boundaries §"Passing Data Across the Boundary"

ALWAYS forward to the skill ; NEVER inline the remediation steps in the validator output. Keeps the validator small and the rulebook authoritative.

When This Skill Does NOT Apply

  • The project is a Pages Router project AND rsc:false is set : the emitted directive on every file is correct ; rule 2 still applies (Dialog must have it), rule 3 does not (there is no app/layout). Run rules 1, 2, 4, 5, 6 ; skip rule 3.
  • The project is a Vite + React SPA : there are no server components ; every file is client ; rule 3 does not apply. The rsc:false flag must be set or the CLI omits the directive on Dialog and the app silently mis-bundles. Run rules 1, 2, 4 ; skip 3 ; rules 5 and 6 become advisory (Provider hygiene is still recommended).
  • The project is a single-file demo or a Storybook story : the validator is overkill ; rely on the build error instead.

Reference Files

  • references/methods.md : full ripgrep / find / jq one-liners per rule, per-primitive matrix snapshot (60 rows), AST patterns for rule 6 (server-to-client prop crossing), and a Bash driver script that runs all six rules and emits the report block above.
  • references/examples.md : two worked traces : a BROKEN App Router project (layout client + missing Dialog directive + ThemeProvider in layout + onClick across boundary) with verdict, and the CORRECT refactored version showing the canonical server-shell + client- island layout.
  • references/anti-patterns.md : six anti-patterns the validator catches with diagnosis, fix, and verification step. Covers the six checklist failure modes plus the suppressHydrationWarning-as- bandaid trap.