{"owner":"kriasoft","repo":"react-starter-kit","hasSkills":true,"totalSkillsCount":27,"totalTokensCount":29080,"categories":["anthropic-skill","command-prompt","copilot-instructions","root-instruction","claude-rule","plugin-manifest"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/merge-seed/SKILL.md",".agents/skills/shadcn/SKILL.md",".agents/skills/shadcn/cli.md",".agents/skills/shadcn/customization.md",".agents/skills/shadcn/evals/evals.json",".agents/skills/shadcn/mcp.md",".agents/skills/shadcn/registry.md",".agents/skills/shadcn/rules/base-vs-radix.md",".agents/skills/shadcn/rules/chat.md",".agents/skills/shadcn/rules/composition.md",".agents/skills/shadcn/rules/forms.md",".agents/skills/shadcn/rules/icons.md",".agents/skills/shadcn/rules/styling.md",".claude/commands/validate-auth-schema.md",".github/copilot-instructions.md","AGENTS.md","CLAUDE.md","db/AGENTS.md","db/CLAUDE.md","infra/AGENTS.md","infra/CLAUDE.md","packages/core/README.md","packages/typescript-config/README.md","packages/ui/AGENTS.md","packages/ui/CLAUDE.md","packages/ui/README.md","packages/ws-protocol/README.md"],"skills":{".agents/skills/merge-seed/SKILL.md":"---\nname: merge-seed\ndescription: Merge upstream React Starter Kit updates (the `seed` remote) into main, preserving this project's identity, scope, and behavior. Use when asked to sync, pull, or merge the seed / starter kit / upstream template.\n---\n\n# Merge seed updates\n\nThis project is a fork of React Starter Kit. `seed/main` is upstream; `main` is upstream plus this project's divergence. Syncing is a recurring operation.\n\n## Core rule\n\n**Upstream owns mechanism. This project owns identity and scope.**\n\nAdopt from upstream: dependency and toolchain versions, build and config structure, framework API updates, refactors, newly required bindings and variables.\n\nKeep local: names, domains, routes, worker and service identifiers; product copy and positioning; plans, features, scope; project-only integrations and config; deliberate additions and deletions.\n\nWhen a hunk mixes both, take upstream's structure and reapply local values. **Never take a whole side merely to clear a conflict** — whole-file resolution is valid only where a rule below makes that side authoritative. The repository defines what counts as local identity and scope: `AGENTS.md`, `docs/adr/`, `docs/product/`, and the current config on `main`.\n\n## Merge\n\n```bash\ngit status --short   # must be clean\n\ngit remote get-url seed || git remote add seed https://github.com/kriasoft/react-starter-kit.git\ngit fetch seed\n\nMB=$(git merge-base main seed/main)\ngit log --oneline \"$MB..seed/main\"   # read before resolving anything\n\ngit switch -c \"chore/merge-seed-$(git rev-parse --short seed/main)\" main\ngit -c merge.conflictStyle=zdiff3 merge --no-ff --no-commit --no-rerere-autoupdate seed/main\ngit diff --name-only --diff-filter=U   # unresolved conflicts\n```\n\n`--no-commit` stops git auto-committing a clean merge, so the commit and its subject are always ours to write. `--no-rerere-autoupdate` keeps every conflict in that list even when `rerere` has a recorded resolution to replay. If the branch already exists, an earlier sync was left unfinished — inspect it and resume or delete it deliberately, never force-reset over it.\n\n`rerere` writes its replayed resolution straight into the working tree, so a conflicted file can arrive already looking finished. Treat pre-filled content as unreviewed; `git rerere forget <path>` to redo it.\n\nIn ordinary text conflicts `zdiff3` includes the merge base, which is how to tell deliberate local divergence from a line upstream simply changed. Where there is no usable inline base — binary, add/add, modify/delete — read the index stages instead: `git ls-files -u <path>` lists which exist, then `git show :1:<path>` base, `:2:` ours, `:3:` theirs. Only the sides that still have content are present, so add/add has no base and a modify/delete has no stage for the side that deleted. For an ambiguous hunk, read the commit behind it: `git log -p \"$MB..seed/main\" -- <path>`.\n\nResolve, stage each resolved path, then commit with the subject in **Land**.\n\n## Resolution rules\n\n- **Infra, Wrangler, Terraform** — keep local names, hostnames, zones, routes, IDs, and values; adopt upstream compatibility dates, new bindings, and newly required variables, then fill in local values.\n- **Env files** — union: keep local values and project-only keys, add new upstream keys, and drop upstream-deleted keys only when the mechanism behind them is also gone.\n- **Manifests** — adopt upstream versions and upstream's own scripts; keep project-only dependencies and scripts still in use; drop local entries an upstream migration made obsolete.\n- **Lockfile** — never hand-merge: resolve every manifest first, take upstream's lockfile, then `bun install` and stage the regenerated result.\n- **Product copy — marketing pages, docs** — local copy and positioning are authoritative; port only technical changes.\n- **App and API code** — prefer upstream's refactor, then reapply local behavior on the new structure; a rewritten config or key list must still carry project-only keys.\n- **Migrations** — a migration that may have run is immutable. Never resolve changes to one in place: if upstream modified it, escalate; otherwise keep local migration history and express incoming schema changes as new migrations.\n- **ADRs** — local numbering is stable; renumber a colliding incoming ADR and fix its links.\n- **Local deletion vs upstream edit, and upstream renames** — deliberate deletions stay deleted; follow renames, carrying local content to the new path.\n\nDon't bring in upstream features that conflict with documented product scope.\n\n## Verify\n\n```bash\nbun install --frozen-lockfile && bun typecheck && bun lint && bun run test -- --run && bun run build\n```\n\n`--frozen-lockfile` fails if the manifests and lockfile disagree, which is what verification should prove. Green checks are still necessary, not sufficient — they cannot see lost identity or copy. Compare the result against each endpoint, per conflicted file and overall:\n\n```bash\ngit diff main..HEAD -- <path>        # upstream's mechanism, plus adaptations it forced\ngit diff seed/main..HEAD -- <path>   # the fork's divergence as it now stands\n```\n\nThese are endpoint comparisons, not per-side attribution: a local value may legitimately move or change shape in the first diff when upstream restructures around it, and the second carries the fork's whole history, not just this merge. What matters is that every difference is explainable — upstream mechanism adopted, or local intent kept. An unexplainable one is a bad resolution.\n\nThen confirm:\n\n- no leftover conflict markers: `git diff --check main..HEAD | grep 'conflict marker'` — they survive in files no build step reads, such as env and docs\n- no upstream branding, placeholder values, or template copy in changed files\n- no local config or key silently dropped by an upstream refactor\n- no stale paths or call sites left by an upstream rename\n- `AGENTS.md` invariants still hold\n- nothing unexpected in files that auto-merged (`git diff main..HEAD --stat`)\n\n## Land\n\nCommit subject `chore: merge seed up to <short-sha>`, notable resolutions in the body. Then, once verification passes:\n\n```bash\ngit switch main && git merge --ff-only <branch> && git branch -d <branch>\n```\n\nIf `--ff-only` is refused, `main` advanced during the merge: integrate the new `main` into the branch and re-verify before landing.\n\n## Escalate\n\nStop and report — file, upstream intent, local intent, the open choice — instead of guessing, when upstream modifies a migration that may have run or changes live-data semantics, ships a feature against documented product scope, or when preserving both mechanism and local behavior is genuinely ambiguous.\n",".agents/skills/shadcn/SKILL.md":"---\nname: shadcn\ndescription: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for \"shadcn init\", \"create an app with --preset\", or \"switch to --preset\".\nuser-invocable: false\nallowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)\n---\n\n# shadcn/ui\n\nA framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI.\n\n> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.\n\n## Current Project Context\n\n```json\n!`npx shadcn@latest info --json`\n```\n\nThe JSON above contains the project config and installed components. Use `npx shadcn@latest docs <component>` to get documentation and example URLs for any component.\n\n## Principles\n\n1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too.\n2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table.\n3. **Use built-in variants before custom styles.** `variant=\"outline\"`, `size=\"sm\"`, etc.\n4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`.\n\n## Critical Rules\n\nThese rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs.\n\n### Styling & Tailwind → [styling.md](./rules/styling.md)\n\n- **`className` for layout, not styling.** Never override component colors or typography.\n- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`.\n- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`.\n- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`.\n- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`).\n- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries.\n- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking.\n\n### Forms & Inputs → [forms.md](./rules/forms.md)\n\n- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.\n- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`.\n- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.**\n- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state.\n- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading.\n- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control.\n\n### Component Structure → [composition.md](./rules/composition.md)\n\n- **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`.\n- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md)\n- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className=\"sr-only\"` if visually hidden.\n- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`.\n- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`.\n- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`.\n- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load.\n\n### Use Components, Not Custom Markup → [composition.md](./rules/composition.md)\n\n- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.\n- **Callouts use `Alert`.** Don't build custom styled divs.\n- **Empty states use `Empty`.** Don't build custom empty state markup.\n- **Toast follows the project base.** Use `toast` from the `toast` component for Base UI projects. Use `toast()` from `sonner` for Radix and React Aria projects.\n- **Use `Separator`** instead of `<hr>` or `<div className=\"border-t\">`.\n- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.\n- **Use `Badge`** instead of custom styled spans.\n\n### Icons → [icons.md](./rules/icons.md)\n\n- **Icons in `Button` use `data-icon`.** `data-icon=\"inline-start\"` or `data-icon=\"inline-end\"` on the icon.\n- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`.\n- **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup.\n\n### Chat & Messaging → [chat.md](./rules/chat.md)\n\n- **Chat UI composes the chat primitives.** Conversations use `MessageScroller`, rows use `Message`, surfaces use `Bubble`. Never hand-rolled bubble `div`s or a raw scroll container.\n- **`MessageScroller` owns scroll behavior.** Streaming follow, anchoring, and jump-to-latest (`MessageScrollerButton`) are built in. Don't write a `useStickToBottom`/`ResizeObserver` hook.\n- **Attachments use `Attachment`; system notes and dividers use `Marker`.** Not `Item` cards or `Separator` + a label.\n\n### CLI\n\n- **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@latest preset decode <code>`, `preset url <code>`, or `preset open <code>`. For project-aware preset detection, use `npx shadcn@latest preset resolve`.\n- **Apply preset codes directly with the CLI.** Use `npx shadcn@latest apply <code>` for existing projects, or `npx shadcn@latest init --preset <code>` when initializing.\n\n## Key Patterns\n\nThese are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above.\n\n```tsx\n// Form layout: FieldGroup + Field, not div + Label.\n<FieldGroup>\n  <Field>\n    <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n    <Input id=\"email\" />\n  </Field>\n</FieldGroup>\n\n// Validation: data-invalid on Field, aria-invalid on the control.\n<Field data-invalid>\n  <FieldLabel>Email</FieldLabel>\n  <Input aria-invalid />\n  <FieldDescription>Invalid email.</FieldDescription>\n</Field>\n\n// Icons in buttons: data-icon, no sizing classes.\n<Button>\n  <SearchIcon data-icon=\"inline-start\" />\n  Search\n</Button>\n\n// Spacing: gap-*, not space-y-*.\n<div className=\"flex flex-col gap-4\">  // correct\n<div className=\"space-y-4\">           // wrong\n\n// Equal dimensions: size-*, not w-* h-*.\n<Avatar className=\"size-10\">   // correct\n<Avatar className=\"w-10 h-10\"> // wrong\n\n// Status colors: Badge variants or semantic tokens, not raw colors.\n<Badge variant=\"secondary\">+20.1%</Badge>    // correct\n<span className=\"text-emerald-600\">+20.1%</span> // wrong\n```\n\n## Component Selection\n\n| Need | Use |\n| --- | --- |\n| Button/action | `Button` with appropriate variant |\n| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` |\n| Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` |\n| Data display | `Table`, `Card`, `Badge`, `Avatar` |\n| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |\n| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |\n| Feedback | `toast` (Base UI), `sonner` (Radix/Aria), `Alert`, `Progress`, `Skeleton`, `Spinner` |\n| Command palette | `Command` inside `Dialog` |\n| Charts | `Chart` (wraps Recharts) |\n| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |\n| Empty states | `Empty` |\n| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` |\n| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` |\n| Chat / conversation UI | `MessageScroller`, `Message`, `Bubble`, `Attachment`, `Marker` |\n\n## Key Fields\n\nThe injected project context contains these key fields:\n\n- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode.\n- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `\"use client\"` at the top of the file. Always reference this field when advising on the directive.\n- **`tailwindVersion`** → `\"v4\"` uses `@theme inline` blocks; `\"v3\"` uses `tailwind.config.js`.\n- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one.\n- **`style`** → component visual treatment (e.g. `nova`, `vega`).\n- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props.\n- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`.\n- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc.\n- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA).\n- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`).\n- **`preset`** → resolved preset code and values for the current project. Use `npx shadcn@latest preset resolve --json` when you only need preset information.\n\nSee [cli.md — `info` command](./cli.md) for the full field reference.\n\n## Component Docs, Examples, and Usage\n\nRun `npx shadcn@latest docs <component>` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content.\n\n```bash\nnpx shadcn@latest docs button dialog select\n```\n\n**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing.\n\n## Workflow\n\n1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh.\n2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed.\n3. **Find components** — `npx shadcn@latest search`.\n4. **Get docs and examples** — run `npx shadcn@latest docs <component>` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`.\n5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below).\n6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.\n7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on.\n8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says \"add a login block\" without specifying `@shadcn`, `@tailark`, `owner/repo`, etc.), ask which registry to use. Never default to a registry on behalf of the user.\n9. **Switching presets** — Ask the user first: **overwrite**, **partial**, **merge**, or **skip**?\n   - **Inspect current preset**: `npx shadcn@latest preset resolve`. Use `--json` when you need structured values.\n   - **Inspect incoming preset**: `npx shadcn@latest preset decode <code>`. Use `preset url <code>` or `preset open <code>` to share or open the preset builder.\n   - **Overwrite**: `npx shadcn@latest apply <code>`. Overwrites detected components, fonts, and CSS variables.\n   - **Partial**: `npx shadcn@latest apply <code> --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms.\n   - **Merge**: `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually.\n   - **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.\n   - **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.\n\n## Updating Components\n\nWhen the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**\n\n1. Run `npx shadcn@latest add <component> --dry-run` to see all files that would be affected.\n2. For each file, run `npx shadcn@latest add <component> --diff <file>` to see what changed upstream vs local.\n3. Decide per file based on the diff:\n   - No local changes → safe to overwrite.\n   - Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications.\n   - User says \"just update everything\" → use `--overwrite`, but confirm first.\n4. **Never use `--overwrite` without the user's explicit approval.**\n\n## Quick Reference\n\n```bash\n# Create a new project.\nnpx shadcn@latest init --name my-app --preset base-nova\nnpx shadcn@latest init --name my-app --preset a2r6bw --template vite\n\n# Create a monorepo project.\nnpx shadcn@latest init --name my-app --preset base-nova --monorepo\nnpx shadcn@latest init --name my-app --preset base-nova --template next --monorepo\n\n# Initialize existing project.\nnpx shadcn@latest init --preset base-nova\nnpx shadcn@latest init --defaults  # shortcut: --template=next --preset=nova (base style implied)\n\n# Apply a preset to an existing project.\nnpx shadcn@latest apply a2r6bw\nnpx shadcn@latest apply a2r6bw --only theme\nnpx shadcn@latest apply a2r6bw --only font\nnpx shadcn@latest apply a2r6bw --only theme,font\n\n# Inspect preset codes and project preset state.\nnpx shadcn@latest preset decode a2r6bw\nnpx shadcn@latest preset url a2r6bw\nnpx shadcn@latest preset open a2r6bw\nnpx shadcn@latest preset resolve\nnpx shadcn@latest preset resolve --json\n\n# Add components.\nnpx shadcn@latest add button card dialog\nnpx shadcn@latest add @magicui/shimmer-button\nnpx shadcn@latest add owner/repo/item\nnpx shadcn@latest add --all\n\n# Preview changes before adding/updating.\nnpx shadcn@latest add button --dry-run\nnpx shadcn@latest add button --diff button.tsx\nnpx shadcn@latest add @acme/form --view button.tsx\nnpx shadcn@latest add owner/repo/item --dry-run\n\n# Search registries.\nnpx shadcn@latest search @shadcn -q \"sidebar\"\nnpx shadcn@latest search @tailark -q \"stats\"\nnpx shadcn@latest search owner/repo -q \"login\"\nnpx shadcn@latest search                          # all configured registries\nnpx shadcn@latest search @shadcn -q \"menu\" -t ui  # filter by item type\n\n# Get component docs and example URLs.\nnpx shadcn@latest docs button dialog select\n\n# View registry item details (for items not yet installed).\nnpx shadcn@latest view @shadcn/button\nnpx shadcn@latest view owner/repo/item\n```\n\n**Named presets:** `nova`, `vega`, `maia`, `lyra`, `mira`, `luma` **Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo) **Preset codes:** Version-prefixed base62 strings (e.g. `a2r6bw` or `b0`), from [ui.shadcn.com](https://ui.shadcn.com).\n\n## Detailed References\n\n- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states\n- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading\n- [rules/chat.md](./rules/chat.md) — MessageScroller, Message, Bubble, Attachment, Marker; streaming, anchoring, jump-to-latest\n- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects\n- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index\n- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion\n- [cli.md](./cli.md) — Commands, flags, presets, templates\n- [registry.md](./registry.md) — Authoring source registries, `include`, item definitions, dependencies, GitHub registry rules\n- [customization.md](./customization.md) — Theming, CSS variables, extending components\n",".agents/skills/shadcn/cli.md":"# shadcn CLI Reference\n\nConfiguration is read from `components.json`.\n\n> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.\n\n> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag.\n\n## Contents\n\n- Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build\n- Templates: next, vite, start, react-router, astro\n- Presets: named, code, URL formats and fields\n- Switching presets\n\n---\n\n## Commands\n\n### `init` — Initialize or create a project\n\n```bash\nnpx shadcn@latest init [components...] [options]\n```\n\nInitializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--template <template>` | `-t` | Template (next, start, vite, next-monorepo, react-router) | — |\n| `--preset [name]` | `-p` | Preset configuration (named, code, or URL) | — |\n| `--yes` | `-y` | Skip confirmation prompt | `true` |\n| `--defaults` | `-d` | Use defaults (`--template=next --preset=base-nova`) | `false` |\n| `--force` | `-f` | Force overwrite existing configuration | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n| `--name <name>` | `-n` | Name for new project | — |\n| `--silent` | `-s` | Mute output | `false` |\n| `--rtl` |  | Enable RTL support | — |\n| `--reinstall` |  | Re-install existing UI components | `false` |\n| `--monorepo` |  | Scaffold a monorepo project | — |\n| `--no-monorepo` |  | Skip the monorepo prompt | — |\n\n`npx shadcn@latest create` is an alias for `npx shadcn@latest init`.\n\n### `apply` — Apply a preset to an existing project\n\n```bash\nnpx shadcn@latest apply [preset] [options]\n```\n\nApplies a preset to an existing project, overwriting preset-driven config, fonts, CSS variables, and detected UI components.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--preset <preset>` | — | Preset configuration (named, code, or URL) | — |\n| `--yes` | `-y` | Skip confirmation prompt | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n| `--silent` | `-s` | Mute output | `false` |\n\n`[preset]` is a shorthand for `--preset <preset>`. If both are provided, they must match. If no preset is provided, the CLI offers to open the custom preset builder on `ui.shadcn.com/create`.\n\n### `add` — Add components\n\n> **IMPORTANT:** To compare local components against upstream or to preview changes, ALWAYS use `npx shadcn@latest add <component> --dry-run`, `--diff`, or `--view`. NEVER fetch raw files from GitHub or other sources manually. The CLI handles registry resolution, file paths, and CSS diffing automatically.\n\n```bash\nnpx shadcn@latest add [components...] [options]\n```\n\nAccepts component names, registry-prefixed names (`@magicui/shimmer-button`), GitHub item addresses (`owner/repo/item`), URLs, or local paths.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--yes` | `-y` | Skip confirmation prompt | `false` |\n| `--overwrite` | `-o` | Overwrite existing files | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n| `--all` | `-a` | Add all available components | `false` |\n| `--path <path>` | `-p` | Target path for the component | — |\n| `--silent` | `-s` | Mute output | `false` |\n| `--dry-run` |  | Preview all changes without writing files | `false` |\n| `--diff [path]` |  | Show diffs. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |\n| `--view [path]` |  | Show file contents. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |\n\n#### Dry-Run Mode\n\nUse `--dry-run` to preview what `add` would do without writing any files. `--diff` and `--view` both imply `--dry-run`.\n\n```bash\n# Preview all changes.\nnpx shadcn@latest add button --dry-run\n\n# Show diffs for all files (top 5).\nnpx shadcn@latest add button --diff\n\n# Show the diff for a specific file.\nnpx shadcn@latest add button --diff button.tsx\n\n# Show contents for all files (top 5).\nnpx shadcn@latest add button --view\n\n# Show the full content of a specific file.\nnpx shadcn@latest add button --view button.tsx\n\n# Works with URLs too.\nnpx shadcn@latest add https://api.npoint.io/abc123 --dry-run\n\n# Works with public GitHub registries too.\nnpx shadcn@latest add owner/repo/item --dry-run\n\n# CSS diffs.\nnpx shadcn@latest add button --diff globals.css\n```\n\n**When to use dry-run:**\n\n- When the user asks \"what files will this add?\" or \"what will this change?\" — use `--dry-run`.\n- Before overwriting existing components — use `--diff` to preview the changes first.\n- When the user wants to inspect component source code without installing — use `--view`.\n- When checking what CSS changes would be made to `globals.css` — use `--diff globals.css`.\n- When the user asks to review or audit third-party registry code before installing — use `--view` to inspect the source.\n\n> **`npx shadcn@latest add --dry-run` vs `npx shadcn@latest view`:** Prefer `npx shadcn@latest add --dry-run/--diff/--view` over `npx shadcn@latest view` when the user wants to preview changes to their project. `npx shadcn@latest view` only shows raw registry metadata. `npx shadcn@latest add --dry-run` shows exactly what would happen in the user's project: resolved file paths, diffs against existing files, and CSS updates. Use `npx shadcn@latest view` only when the user wants to browse registry info without a project context.\n\n#### Smart Merge from Upstream\n\nSee [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full workflow.\n\n### `search` — Search registries\n\n```bash\nnpx shadcn@latest search [registries...] [options]\n```\n\nFuzzy search across registries. Also aliased as `npx shadcn@latest list`. Supports namespaces (`@acme`), public GitHub registry sources (`owner/repo`), and registry catalog URLs. Without `-q`, lists all items. When no registries are passed, searches every registry configured in `components.json`.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--query <query>` | `-q` | Search query | — |\n| `--type <type>` | `-t` | Filter by item type (e.g. `ui`, `block`, `hook`); comma-separated | — |\n| `--limit <number>` | `-l` | Max items to display | `100` |\n| `--offset <number>` | `-o` | Items to skip | `0` |\n| `--json` |  | Output as JSON | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n\n### `view` — View item details\n\n```bash\nnpx shadcn@latest view <items...> [options]\n```\n\nDisplays item info including file contents. Examples: `npx shadcn@latest view @shadcn/button`, `npx shadcn@latest view owner/repo/item`.\n\n### `docs` — Get component documentation URLs\n\n```bash\nnpx shadcn@latest docs <components...> [options]\n```\n\nOutputs resolved URLs for component documentation, examples, and API references. Accepts one or more component names. Fetch the URLs to get the actual content.\n\nExample output for `npx shadcn@latest docs input button`:\n\n```\nbase  radix\n\ninput\n  docs      https://ui.shadcn.com/docs/components/radix/input\n  examples  https://raw.githubusercontent.com/.../examples/input-example.tsx\n\nbutton\n  docs      https://ui.shadcn.com/docs/components/radix/button\n  examples  https://raw.githubusercontent.com/.../examples/button-example.tsx\n```\n\nSome components include an `api` link to the underlying library (e.g. `cmdk` for the command component).\n\n### `diff` — Check for updates\n\nDo not use this command. Use `npx shadcn@latest add --diff` instead.\n\n### `info` — Project information\n\n```bash\nnpx shadcn@latest info [options]\n```\n\nDisplays project info and `components.json` configuration. Run this first to discover the project's framework, aliases, Tailwind version, and resolved paths.\n\n| Flag          | Short | Description       | Default |\n| ------------- | ----- | ----------------- | ------- |\n| `--cwd <cwd>` | `-c`  | Working directory | current |\n\n**Project Info fields:**\n\n| Field | Type | Meaning |\n| --- | --- | --- |\n| `framework` | `string` | Detected framework (`next`, `vite`, `react-router`, `start`, etc.) |\n| `frameworkVersion` | `string` | Framework version (e.g. `15.2.4`) |\n| `isSrcDir` | `boolean` | Whether the project uses a `src/` directory |\n| `isRSC` | `boolean` | Whether React Server Components are enabled |\n| `isTsx` | `boolean` | Whether the project uses TypeScript |\n| `tailwindVersion` | `string` | `\"v3\"` or `\"v4\"` |\n| `tailwindConfigFile` | `string` | Path to the Tailwind config file |\n| `tailwindCssFile` | `string` | Path to the global CSS file |\n| `aliasPrefix` | `string` | Import alias prefix (e.g. `@`, `~`, `@/`) |\n| `packageManager` | `string` | Detected package manager (`npm`, `pnpm`, `yarn`, `bun`) |\n\n**Components.json fields:**\n\n| Field | Type | Meaning |\n| --- | --- | --- |\n| `base` | `string` | Primitive library (`radix` or `base`) — determines component APIs and available props |\n| `style` | `string` | Visual style (e.g. `nova`, `vega`) |\n| `rsc` | `boolean` | RSC flag from config |\n| `tsx` | `boolean` | TypeScript flag |\n| `tailwind.config` | `string` | Tailwind config path |\n| `tailwind.css` | `string` | Global CSS path — this is where custom CSS variables go |\n| `iconLibrary` | `string` | Icon library — determines icon import package (e.g. `lucide-react`, `@tabler/icons-react`) |\n| `aliases.components` | `string` | Component import alias (e.g. `@/components`) |\n| `aliases.utils` | `string` | Utils import alias (e.g. `@/lib/utils`) |\n| `aliases.ui` | `string` | UI component alias (e.g. `@/components/ui`) |\n| `aliases.lib` | `string` | Lib alias (e.g. `@/lib`) |\n| `aliases.hooks` | `string` | Hooks alias (e.g. `@/hooks`) |\n| `resolvedPaths` | `object` | Absolute file-system paths for each alias |\n| `registries` | `object` | Configured custom registries |\n\n**Links fields:**\n\nThe `info` output includes a **Links** section with templated URLs for component docs, source, and examples. For resolved URLs, use `npx shadcn@latest docs <component>` instead.\n\n### `build` — Build a custom registry\n\n```bash\nnpx shadcn@latest build [registry] [options]\n```\n\nBuilds `registry.json` into individual JSON files for distribution. Default input: `./registry.json`, default output: `./public/r`.\n\nFor authoring rules, `include`, item definitions, `registryDependencies`, and GitHub registry behavior, see [registry.md](./registry.md).\n\n| Flag              | Short | Description       | Default      |\n| ----------------- | ----- | ----------------- | ------------ |\n| `--output <path>` | `-o`  | Output directory  | `./public/r` |\n| `--cwd <cwd>`     | `-c`  | Working directory | current      |\n\n---\n\n## Templates\n\n| Value          | Framework      | Monorepo support |\n| -------------- | -------------- | ---------------- |\n| `next`         | Next.js        | Yes              |\n| `vite`         | Vite           | Yes              |\n| `start`        | TanStack Start | Yes              |\n| `react-router` | React Router   | Yes              |\n| `astro`        | Astro          | Yes              |\n| `laravel`      | Laravel        | No               |\n\nAll templates support monorepo scaffolding via the `--monorepo` flag. When passed, the CLI uses a monorepo-specific template directory (e.g. `next-monorepo`, `vite-monorepo`). When neither `--monorepo` nor `--no-monorepo` is passed, the CLI prompts interactively. Laravel does not support monorepo scaffolding.\n\n---\n\n## Presets\n\nThree ways to specify a preset via `--preset`:\n\n1. **Named:** `--preset nova` or `--preset lyra`\n2. **Code:** `--preset a2r6bw` (version-prefixed base62 string, e.g. `a2r6bw` or `b0`)\n3. **URL:** `--preset \"https://ui.shadcn.com/init?base=radix&style=nova&...\"`\n\n> **IMPORTANT:** Never try to decode, fetch, or resolve preset codes manually. Preset codes are opaque — pass them directly to `npx shadcn@latest init --preset <code>` and let the CLI handle resolution. Use `npx shadcn@latest apply --preset <code>` when overwriting an existing project's preset.\n\n## Switching Presets\n\nAsk the user first: **overwrite**, **merge**, or **skip** existing components?\n\n- **Overwrite / Re-install** → `npx shadcn@latest apply --preset <code>`. Overwrites all detected component files with the new preset styles. Use when the user hasn't customized components.\n- **Merge** → `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to get the list of installed components and use the [smart merge workflow](./SKILL.md#updating-components) to update them one by one, preserving local changes. Use when the user has customized components.\n- **Skip** → `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS variables, leaves existing components as-is.\n\nAlways run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.\n",".agents/skills/shadcn/customization.md":"# Customization & Theming\n\nComponents reference semantic CSS variable tokens. Change the variables to change every component.\n\n## Contents\n\n- How it works (CSS variables → Tailwind utilities → components)\n- Color variables and OKLCH format\n- Dark mode setup\n- Changing the theme (presets, CSS variables)\n- Adding custom colors (Tailwind v3 and v4)\n- Border radius\n- Customizing components (variants, className, wrappers)\n- Checking for updates\n\n---\n\n## How It Works\n\n1. CSS variables defined in `:root` (light) and `.dark` (dark mode).\n2. Tailwind maps them to utilities: `bg-primary`, `text-muted-foreground`, etc.\n3. Components use these utilities — changing a variable changes all components that reference it.\n\n---\n\n## Color Variables\n\nEvery color follows the `name` / `name-foreground` convention. The base variable is for backgrounds, `-foreground` is for text/icons on that background.\n\n| Variable | Purpose |\n| --- | --- |\n| `--background` / `--foreground` | Page background and default text |\n| `--card` / `--card-foreground` | Card surfaces |\n| `--primary` / `--primary-foreground` | Primary buttons and actions |\n| `--secondary` / `--secondary-foreground` | Secondary actions |\n| `--muted` / `--muted-foreground` | Muted/disabled states |\n| `--accent` / `--accent-foreground` | Hover and accent states |\n| `--destructive` / `--destructive-foreground` | Error and destructive actions |\n| `--border` | Default border color |\n| `--input` | Form input borders |\n| `--ring` | Focus ring color |\n| `--chart-1` through `--chart-5` | Chart/data visualization |\n| `--sidebar-*` | Sidebar-specific colors |\n| `--surface` / `--surface-foreground` | Secondary surface |\n\nColors use OKLCH: `--primary: oklch(0.205 0 0)` where values are lightness (0–1), chroma (0 = gray), and hue (0–360).\n\n---\n\n## Dark Mode\n\nClass-based toggle via `.dark` on the root element. In Next.js, use `next-themes`:\n\n```tsx\nimport { ThemeProvider } from \"next-themes\";\n\n<ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n  {children}\n</ThemeProvider>;\n```\n\n---\n\n## Changing the Theme\n\n```bash\n# Apply a preset code from ui.shadcn.com.\nnpx shadcn@latest apply --preset a2r6bw\n\n# Positional shorthand also works.\nnpx shadcn@latest apply a2r6bw\n\n# Switch to a named preset and overwrite existing components.\nnpx shadcn@latest apply --preset nova\n\n# Preserve existing components instead.\nnpx shadcn@latest init --preset nova --force --no-reinstall\n\n# Use a custom theme URL.\nnpx shadcn@latest apply --preset \"https://ui.shadcn.com/init?base=radix&style=nova&theme=blue&...\"\n```\n\nOr edit CSS variables directly in `globals.css`.\n\n---\n\n## Adding Custom Colors\n\nAdd variables to the file at `tailwindCssFile` from `npx shadcn@latest info` (typically `globals.css`). Never create a new CSS file for this.\n\n```css\n/* 1. Define in the global CSS file. */\n:root {\n  --warning: oklch(0.84 0.16 84);\n  --warning-foreground: oklch(0.28 0.07 46);\n}\n.dark {\n  --warning: oklch(0.41 0.11 46);\n  --warning-foreground: oklch(0.99 0.02 95);\n}\n```\n\n```css\n/* 2a. Register with Tailwind v4 (@theme inline). */\n@theme inline {\n  --color-warning: var(--warning);\n  --color-warning-foreground: var(--warning-foreground);\n}\n```\n\nWhen `tailwindVersion` is `\"v3\"` (check via `npx shadcn@latest info`), register in `tailwind.config.js` instead:\n\n```js\n// 2b. Register with Tailwind v3 (tailwind.config.js).\nmodule.exports = {\n  theme: {\n    extend: {\n      colors: {\n        warning: \"oklch(var(--warning) / <alpha-value>)\",\n        \"warning-foreground\":\n          \"oklch(var(--warning-foreground) / <alpha-value>)\",\n      },\n    },\n  },\n};\n```\n\n```tsx\n// 3. Use in components.\n<div className=\"bg-warning text-warning-foreground\">Warning</div>\n```\n\n---\n\n## Border Radius\n\n`--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`).\n\n---\n\n## Customizing Components\n\nSee also: [rules/styling.md](./rules/styling.md) for Incorrect/Correct examples.\n\nPrefer these approaches in order:\n\n### 1. Built-in variants\n\n```tsx\n<Button variant=\"outline\" size=\"sm\">\n  Click\n</Button>\n```\n\n### 2. Tailwind classes via `className`\n\n```tsx\n<Card className=\"mx-auto max-w-md\">...</Card>\n```\n\n### 3. Add a new variant\n\nEdit the component source to add a variant via `cva`:\n\n```tsx\n// components/ui/button.tsx\nwarning: \"bg-warning text-warning-foreground hover:bg-warning/90\",\n```\n\n### 4. Wrapper components\n\nCompose shadcn/ui primitives into higher-level components:\n\n```tsx\nexport function ConfirmDialog({ title, description, onConfirm, children }) {\n  return (\n    <AlertDialog>\n      <AlertDialogTrigger asChild>{children}</AlertDialogTrigger>\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogTitle>{title}</AlertDialogTitle>\n          <AlertDialogDescription>{description}</AlertDialogDescription>\n        </AlertDialogHeader>\n        <AlertDialogFooter>\n          <AlertDialogCancel>Cancel</AlertDialogCancel>\n          <AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n  );\n}\n```\n\n---\n\n## Checking for Updates\n\n```bash\nnpx shadcn@latest add button --diff\n```\n\nTo preview exactly what would change before updating, use `--dry-run` and `--diff`:\n\n```bash\nnpx shadcn@latest add button --dry-run        # see all affected files\nnpx shadcn@latest add button --diff button.tsx # see the diff for a specific file\n```\n\nSee [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full smart merge workflow.\n",".agents/skills/shadcn/evals/evals.json":"{\n  \"skill_name\": \"shadcn\",\n  \"evals\": [\n    {\n      \"id\": 1,\n      \"prompt\": \"I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.\",\n      \"expected_output\": \"A React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses FieldGroup and Field components for form layout instead of raw div with space-y\",\n        \"Uses Switch for independent on/off notification toggles (not looping Button with manual active state)\",\n        \"Uses data-invalid on Field and aria-invalid on the input control for validation states\",\n        \"Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing\",\n        \"Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500\",\n        \"No manual dark: color overrides\"\n      ]\n    },\n    {\n      \"id\": 2,\n      \"prompt\": \"Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn/ui with radix-nova preset and tabler icons.\",\n      \"expected_output\": \"A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Includes DialogTitle for accessibility (visible or with sr-only class)\",\n        \"Avatar component includes AvatarFallback\",\n        \"Icons on buttons use the data-icon attribute (data-icon=\\\"inline-start\\\" or data-icon=\\\"inline-end\\\")\",\n        \"No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)\",\n        \"Uses tabler icons (@tabler/icons-react) instead of lucide-react\",\n        \"Uses asChild for custom triggers (radix preset)\"\n      ]\n    },\n    {\n      \"id\": 3,\n      \"prompt\": \"Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn/ui with base-nova preset and lucide icons.\",\n      \"expected_output\": \"A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)\",\n        \"Uses Skeleton component for loading placeholders instead of custom animate-pulse divs\",\n        \"Uses Badge component for percentage change instead of custom styled spans\",\n        \"Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600\",\n        \"Uses gap-* instead of space-y-* or space-x-* for spacing\",\n        \"Uses size-* when width and height are equal instead of separate w-* h-*\"\n      ]\n    },\n    {\n      \"id\": 4,\n      \"prompt\": \"I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Build a chat conversation view: a scrollable thread of messages from two different people, each with an avatar, sender name, timestamp, and message bubble. A couple of messages include an image attachment and a PDF file attachment, and there's a 'Today' divider separating the days.\",\n      \"expected_output\": \"A React component composing MessageScroller, Message, Bubble, Attachment, and Marker from the registry instead of hand-rolled bubble/divider/attachment markup.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses MessageScroller (MessageScrollerProvider, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem) for the scrollable thread instead of a raw overflow-y-auto div or ScrollArea\",\n        \"Wraps each row in MessageScrollerItem inside MessageScrollerContent\",\n        \"Uses Message with MessageAvatar/MessageContent/MessageHeader for row layout instead of custom flex divs\",\n        \"Uses Bubble + BubbleContent for the message surface instead of a styled div with bg-muted/bg-primary\",\n        \"Uses Attachment (AttachmentMedia, AttachmentContent, AttachmentTitle, AttachmentDescription) for the file and image attachments instead of Item or a custom card\",\n        \"Uses Marker (variant=\\\"separator\\\") for the 'Today' divider instead of Separator plus a centered label\",\n        \"Uses semantic color tokens and gap-* spacing; no raw colors like bg-emerald-500 and no space-y-*\",\n        \"Includes \\\"use client\\\" when the component uses state or event handlers (isRSC)\"\n      ]\n    },\n    {\n      \"id\": 5,\n      \"prompt\": \"Using shadcn/ui (base-nova preset, lucide icons), build a streaming AI chat UI. The assistant's reply streams in while it generates, the view auto-scrolls to follow the latest content but stops following if the user scrolls up to read earlier messages, a 'jump to latest' button appears when the user has scrolled away from the bottom, and a subtle 'thinking…' shimmer shows while the model is generating.\",\n      \"expected_output\": \"A React component that delegates scroll/anchor behavior to MessageScroller and uses MessageScrollerButton for jump-to-latest and the shimmer utility for the thinking indicator — no hand-rolled scroll logic or custom shimmer keyframes.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses MessageScroller with MessageScrollerProvider (autoScroll) and scrollAnchor on message items for the stick-to-bottom/follow behavior instead of a custom useStickToBottom hook or ResizeObserver/scrollTop wiring\",\n        \"Uses MessageScrollerButton for the jump-to-latest control instead of a hand-built conditional button driven by manual scroll-position state\",\n        \"Uses the shimmer utility class for the 'thinking…' indicator instead of a custom @keyframes or bg-clip-text gradient animation\",\n        \"Wraps each message row in MessageScrollerItem inside MessageScrollerContent\",\n        \"Uses Message + Bubble + BubbleContent for the conversation rows instead of hand-rolled bubble divs\",\n        \"Uses semantic color tokens and gap-* spacing; includes \\\"use client\\\" (isRSC)\"\n      ]\n    }\n  ]\n}\n",".agents/skills/shadcn/mcp.md":"# shadcn MCP Server\n\nThe CLI includes an MCP server that lets AI assistants search, browse, view, and install items from registries.\n\n---\n\n## Setup\n\n```bash\nshadcn mcp        # start the MCP server (stdio)\nshadcn mcp init   # write config for your editor\n```\n\nEditor config files:\n\n| Editor      | Config file                     |\n| ----------- | ------------------------------- |\n| Claude Code | `.mcp.json`                     |\n| Cursor      | `.cursor/mcp.json`              |\n| VS Code     | `.vscode/mcp.json`              |\n| OpenCode    | `opencode.json`                 |\n| Codex       | `~/.codex/config.toml` (manual) |\n\n---\n\n## Tools\n\n> **Tip:** MCP tools handle registry operations (search, view, install). For project configuration (aliases, framework, Tailwind version), use `npx shadcn@latest info` — there is no MCP equivalent.\n\n### `shadcn:get_project_registries`\n\nReturns registry names from `components.json`. Errors if no `components.json` exists.\n\n**Input:** none\n\n### `shadcn:list_items_in_registries`\n\nLists all items from one or more registries. Registries can be configured namespaces such as `@acme`, public GitHub sources such as `owner/repo`, or registry catalog URLs. Omit `registries` to list from every registry configured in `components.json`.\n\n**Input:** `registries` (string[], optional — omit for all configured), `types` (string[], optional — e.g. `[\"ui\", \"block\"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)\n\n### `shadcn:search_items_in_registries`\n\nFuzzy search across registries. Registries can be configured namespaces, public GitHub sources, or registry catalog URLs. Omit `registries` to search every registry configured in `components.json` — e.g. \"find me a hero\" across all configured registries.\n\n**Input:** `registries` (string[], optional — omit for all configured), `query` (string), `types` (string[], optional — e.g. `[\"ui\", \"block\"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)\n\n### `shadcn:view_items_in_registries`\n\nView item details including full file contents.\n\n**Input:** `items` (string[]) — e.g. `[\"@shadcn/button\", \"@shadcn/card\", \"owner/repo/item\"]`\n\n### `shadcn:get_item_examples_from_registries`\n\nFind usage examples and demos with source code. Omit `registries` to search every registry configured in `components.json`.\n\n**Input:** `registries` (string[], optional — omit for all configured), `query` (string) — e.g. `\"accordion-demo\"`, `\"button example\"`\n\n### `shadcn:get_add_command_for_items`\n\nReturns the CLI install command.\n\n**Input:** `items` (string[]) — e.g. `[\"@shadcn/button\"]`\n\n### `shadcn:get_audit_checklist`\n\nReturns a checklist for verifying components (imports, deps, lint, TypeScript).\n\n**Input:** none\n\n---\n\n## Configuring Registries\n\nNamespaced and authenticated registries are set in `components.json`. The `@shadcn` registry is always built-in. Public GitHub registries can also be used directly as `owner/repo` registry sources when the repository has a root `registry.json`; they do not need `components.json` configuration.\n\n```json\n{\n  \"registries\": {\n    \"@acme\": \"https://acme.com/r/{name}.json\",\n    \"@private\": {\n      \"url\": \"https://private.com/r/{name}.json\",\n      \"headers\": { \"Authorization\": \"Bearer ${MY_TOKEN}\" }\n    }\n  }\n}\n```\n\n- Names must start with `@`.\n- URLs must contain `{name}`.\n- `${VAR}` references are resolved from environment variables.\n\nCommunity registry index: `https://ui.shadcn.com/r/registries.json`\n",".agents/skills/shadcn/registry.md":"# Registry Authoring and Addresses\n\nUse this reference when the user wants to create, fix, publish, or reason about a shadcn registry.\n\n## Mental Model\n\nA registry has two forms:\n\n- **Source registry**: an authored `registry.json` in a project or repository. It may use `include` and file paths that point at source files.\n- **Built registry**: generated JSON files served to CLI consumers, usually from `public/r`. Use `npx shadcn@latest build` to create this form.\n\nThe CLI installer consumes registry item payloads. A source registry is a way to author those payloads from real files.\n\nRegistry items are not limited to React components. They can distribute components, hooks, utilities, design tokens, pages, config files, docs, rules, workflows, templates, MCP files, and other project files.\n\n## Root `registry.json`\n\nThe root registry file should define registry metadata and either `items` or `include`.\n\n```json\n{\n  \"$schema\": \"https://ui.shadcn.com/schema/registry.json\",\n  \"name\": \"acme\",\n  \"homepage\": \"https://acme.com\",\n  \"items\": [\n    {\n      \"name\": \"absolute-url\",\n      \"type\": \"registry:lib\",\n      \"title\": \"Absolute URL\",\n      \"description\": \"A utility to turn any path into an absolute URL.\",\n      \"files\": [\n        {\n          \"path\": \"lib/absolute-url.ts\",\n          \"type\": \"registry:lib\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nRoot registry rules:\n\n- Root `registry.json` must include `name` and `homepage`.\n- `items` is an array of registry item definitions.\n- `include` may be used to split the source registry into multiple files.\n- Included registry files may omit `name` and `homepage`.\n\n## Include\n\nUse `include` to keep large registries modular.\n\n```json\n{\n  \"$schema\": \"https://ui.shadcn.com/schema/registry.json\",\n  \"name\": \"acme\",\n  \"homepage\": \"https://acme.com\",\n  \"include\": [\"registry/ui/registry.json\", \"registry/blocks/registry.json\"]\n}\n```\n\nInclude rules:\n\n- Include paths are relative to the `registry.json` that declares them.\n- Include paths must explicitly point to a `registry.json` file.\n- Do not use remote URLs, absolute paths, or parent traversal (`..`).\n- Item file paths are relative to the registry file that declares the item.\n- Duplicate item names fail across the resolved registry.\n\nExample included file:\n\n```json\n{\n  \"items\": [\n    {\n      \"name\": \"button\",\n      \"type\": \"registry:ui\",\n      \"files\": [\n        {\n          \"path\": \"button.tsx\",\n          \"type\": \"registry:ui\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nIf this file is at `registry/ui/registry.json`, then `button.tsx` is read from `registry/ui/button.tsx`, and the built item path is emitted relative to the root registry.\n\n## Item Definitions\n\nCommon item fields:\n\n```json\n{\n  \"name\": \"login-form\",\n  \"type\": \"registry:block\",\n  \"title\": \"Login Form\",\n  \"description\": \"A login form with email and password fields.\",\n  \"dependencies\": [\"zod\"],\n  \"registryDependencies\": [\"button\", \"input\", \"label\"],\n  \"files\": [\n    {\n      \"path\": \"blocks/login-form.tsx\",\n      \"type\": \"registry:block\"\n    }\n  ],\n  \"cssVars\": {\n    \"light\": {\n      \"brand\": \"oklch(0.62 0.18 250)\"\n    },\n    \"dark\": {\n      \"brand\": \"oklch(0.72 0.16 250)\"\n    }\n  }\n}\n```\n\nImportant fields:\n\n- `name`: the installable item name. It is not necessarily a file path.\n- `type`: one of the registry item types, such as `registry:ui`, `registry:block`, `registry:lib`, `registry:hook`, `registry:file`, `registry:page`, `registry:theme`, `registry:style`, `registry:font`, or `registry:item`.\n- `files`: source files copied or generated by the item.\n- `dependencies`: npm runtime dependencies.\n- `devDependencies`: npm development dependencies.\n- `registryDependencies`: other registry items required by this item.\n- `cssVars`, `css`, `tailwind`, `envVars`, and `docs`: optional install-time additions.\n\nFile rules:\n\n- File paths are relative to the declaring `registry.json`.\n- `registry:file` and `registry:page` files require a `target`.\n- Do not use remote file URLs in source registry file paths.\n- Keep source files copy-pasteable: no hidden app-only imports.\n\n## Registry Dependencies\n\n`registryDependencies` entries are item addresses, not file paths.\n\n```json\n{\n  \"name\": \"login-form\",\n  \"type\": \"registry:block\",\n  \"registryDependencies\": [\"button\", \"@acme/input\", \"acme/ui/card#v1.2.0\"],\n  \"files\": [\n    {\n      \"path\": \"blocks/login-form.tsx\",\n      \"type\": \"registry:block\"\n    }\n  ]\n}\n```\n\nDependency rules:\n\n- Bare names such as `\"button\"` mean official shadcn items.\n- Bare names never mean same-registry or same-repository items.\n- Namespaced dependencies use `@namespace/item-name`.\n- GitHub dependencies use `owner/repo/item-name`.\n- Pin GitHub dependencies with `owner/repo/item-name#ref` when needed.\n- Refs are not inherited. If `owner/repo/foo#v2` depends on `bar` from the same repo at `v2`, write `owner/repo/bar#v2`.\n- Do not use relative dependencies such as `\"./bar\"`.\n\n## Address Schemes\n\nWhen reasoning about a registry item string, classify it first.\n\n| Address | Scheme | Meaning |\n| --- | --- | --- |\n| `button` | shadcn | Official shadcn item named `button`. |\n| `@acme/button` | namespace | Item `button` from configured registry `@acme`. |\n| `@acme/ui/button` | namespace | Item `ui/button` from configured registry `@acme`. |\n| `https://example.com/r/button.json` | url | Built registry item JSON at that URL. |\n| `./button.json` | file | Built registry item JSON on disk. |\n| `acme/ui/button` | github | Item `button` from GitHub repo `acme/ui`. |\n| `acme/ui/forms/login#main` | github | Item `forms/login` from GitHub repo `acme/ui` at ref `main`. |\n\nFor namespace and GitHub addresses, slashful item names are allowed and are item names, not file paths. Addresses ending in `.json` keep file-address precedence, so `acme/ui/data/schema.json` is treated as a file path, not a GitHub item address.\n\n## GitHub Registries\n\nA public GitHub repository can act as a source registry when it has a root `registry.json`.\n\n```txt\nowner/repo/item-name[#ref]\n```\n\nRules:\n\n- The first two path segments are GitHub owner and repo.\n- All remaining path segments are the registry item name.\n- The source entrypoint is always root `registry.json`.\n- GitHub registries are source registries consumed directly by the CLI. They do not require `shadcn build` or generated item JSON files.\n- `include` follows the same source-registry rules as local registries.\n- Currently, GitHub addresses support public `github.com` repositories only.\n- Private repos and GitHub Enterprise require explicit product decisions.\n\nWhen implementing GitHub registry fetching, resolve refs to a commit SHA before reading source files. Do not read moving refs directly from `raw.githubusercontent.com`, because branch-like refs can be cached for several minutes.\n\nPreferred flow:\n\n```txt\nowner/repo[#ref]\n  -> resolve ref with git ls-remote\n  -> commit SHA\n  -> read https://raw.githubusercontent.com/{owner}/{repo}/{sha}/registry.json\n  -> read includes and item files from the same SHA\n```\n\nThis keeps a command on one consistent repository snapshot.\n\nFull 40-character commit SHAs are already stable and can be used directly. Branches, tags, and short refs require Git so the CLI can resolve them to a commit SHA first.\n\n## Build and Verify\n\nUse the CLI to build source registries:\n\n```bash\nnpx shadcn@latest build\nnpx shadcn@latest build registry.json --output public/r\n```\n\nUse CLI commands to inspect the result:\n\n```bash\nnpx shadcn@latest list @acme\nnpx shadcn@latest search @acme -q \"login\"\nnpx shadcn@latest view @acme/login-form\nnpx shadcn@latest add @acme/login-form --dry-run\nnpx shadcn@latest registry validate ./registry.json\n```\n\nUse GitHub addresses directly for public GitHub registries:\n\n```bash\nnpx shadcn@latest list owner/repo\nnpx shadcn@latest search owner/repo -q \"login\"\nnpx shadcn@latest view owner/repo/item\nnpx shadcn@latest add owner/repo/item --dry-run\nnpx shadcn@latest registry validate owner/repo\n```\n\nWhen working on registry implementation in the shadcn/ui codebase:\n\n- Keep address parsing pure and testable.\n- Do not add side effects to validators.\n- Preserve existing behavior for official shadcn, namespace, URL, and file schemes.\n- Add tests for address parsing, source loading, dependency resolution, list, search, view, and add paths.\n- Prefer small source-reader abstractions over a plugin system until there are multiple real providers.\n",".agents/skills/shadcn/rules/base-vs-radix.md":"# Base vs Radix\n\nAPI differences between `base` and `radix`. Check the `base` field from `npx shadcn@latest info`.\n\n## Contents\n\n- Composition: asChild vs render\n- Button / trigger as non-button element\n- Select (items prop, placeholder, positioning, multiple, object values)\n- ToggleGroup (type vs multiple)\n- Slider (scalar vs array)\n- Accordion (type and defaultValue)\n\n---\n\n## Composition: asChild (radix) vs render (base)\n\nRadix uses `asChild` to replace the default element. Base uses `render`. Don't wrap triggers in extra elements.\n\n**Incorrect:**\n\n```tsx\n<DialogTrigger>\n  <div>\n    <Button>Open</Button>\n  </div>\n</DialogTrigger>\n```\n\n**Correct (radix):**\n\n```tsx\n<DialogTrigger asChild>\n  <Button>Open</Button>\n</DialogTrigger>\n```\n\n**Correct (base):**\n\n```tsx\n<DialogTrigger render={<Button />}>Open</DialogTrigger>\n```\n\nThis applies to all trigger and close components: `DialogTrigger`, `SheetTrigger`, `AlertDialogTrigger`, `DropdownMenuTrigger`, `PopoverTrigger`, `TooltipTrigger`, `CollapsibleTrigger`, `DialogClose`, `SheetClose`, `NavigationMenuLink`, `BreadcrumbLink`, `SidebarMenuButton`, `Badge`, `Item`.\n\n---\n\n## Button / trigger as non-button element (base only)\n\nWhen `render` changes an element to a non-button (`<a>`, `<span>`), add `nativeButton={false}`.\n\n**Incorrect (base):** missing `nativeButton={false}`.\n\n```tsx\n<Button render={<a href=\"/docs\" />}>Read the docs</Button>\n```\n\n**Correct (base):**\n\n```tsx\n<Button render={<a href=\"/docs\" />} nativeButton={false}>\n  Read the docs\n</Button>\n```\n\n**Correct (radix):**\n\n```tsx\n<Button asChild>\n  <a href=\"/docs\">Read the docs</a>\n</Button>\n```\n\nSame for triggers whose `render` is not a `Button`:\n\n```tsx\n// base.\n<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>\n  Pick date\n</PopoverTrigger>\n```\n\n---\n\n## Select\n\n**items prop (base only).** Base requires an `items` prop on the root. Radix uses inline JSX only.\n\n**Incorrect (base):**\n\n```tsx\n<Select>\n  <SelectTrigger>\n    <SelectValue placeholder=\"Select a fruit\" />\n  </SelectTrigger>\n</Select>\n```\n\n**Correct (base):**\n\n```tsx\nconst items = [\n  { label: \"Select a fruit\", value: null },\n  { label: \"Apple\", value: \"apple\" },\n  { label: \"Banana\", value: \"banana\" },\n]\n\n<Select items={items}>\n  <SelectTrigger>\n    <SelectValue />\n  </SelectTrigger>\n  <SelectContent>\n    <SelectGroup>\n      {items.map((item) => (\n        <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>\n      ))}\n    </SelectGroup>\n  </SelectContent>\n</Select>\n```\n\n**Correct (radix):**\n\n```tsx\n<Select>\n  <SelectTrigger>\n    <SelectValue placeholder=\"Select a fruit\" />\n  </SelectTrigger>\n  <SelectContent>\n    <SelectGroup>\n      <SelectItem value=\"apple\">Apple</SelectItem>\n      <SelectItem value=\"banana\">Banana</SelectItem>\n    </SelectGroup>\n  </SelectContent>\n</Select>\n```\n\n**Placeholder.** Base uses a `{ value: null }` item in the items array. Radix uses `<SelectValue placeholder=\"...\">`.\n\n**Content positioning.** Base uses `alignItemWithTrigger`. Radix uses `position`.\n\n```tsx\n// base.\n<SelectContent alignItemWithTrigger={false} side=\"bottom\">\n\n// radix.\n<SelectContent position=\"popper\">\n```\n\n---\n\n## Select — multiple selection and object values (base only)\n\nBase supports `multiple`, render-function children on `SelectValue`, and object values with `itemToStringValue`. Radix is single-select with string values only.\n\n**Correct (base — multiple selection):**\n\n```tsx\n<Select items={items} multiple defaultValue={[]}>\n  <SelectTrigger>\n    <SelectValue>\n      {(value: string[]) =>\n        value.length === 0 ? \"Select fruits\" : `${value.length} selected`\n      }\n    </SelectValue>\n  </SelectTrigger>\n  ...\n</Select>\n```\n\n**Correct (base — object values):**\n\n```tsx\n<Select defaultValue={plans[0]} itemToStringValue={(plan) => plan.name}>\n  <SelectTrigger>\n    <SelectValue>{(value) => value.name}</SelectValue>\n  </SelectTrigger>\n  ...\n</Select>\n```\n\n---\n\n## ToggleGroup\n\nBase uses a `multiple` boolean prop. Radix uses `type=\"single\"` or `type=\"multiple\"`.\n\n**Incorrect (base):**\n\n```tsx\n<ToggleGroup type=\"single\" defaultValue=\"daily\">\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n</ToggleGroup>\n```\n\n**Correct (base):**\n\n```tsx\n// Single (no prop needed), defaultValue is always an array.\n<ToggleGroup defaultValue={[\"daily\"]} spacing={2}>\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n  <ToggleGroupItem value=\"weekly\">Weekly</ToggleGroupItem>\n</ToggleGroup>\n\n// Multi-selection.\n<ToggleGroup multiple>\n  <ToggleGroupItem value=\"bold\">Bold</ToggleGroupItem>\n  <ToggleGroupItem value=\"italic\">Italic</ToggleGroupItem>\n</ToggleGroup>\n```\n\n**Correct (radix):**\n\n```tsx\n// Single, defaultValue is a string.\n<ToggleGroup type=\"single\" defaultValue=\"daily\" spacing={2}>\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n  <ToggleGroupItem value=\"weekly\">Weekly</ToggleGroupItem>\n</ToggleGroup>\n\n// Multi-selection.\n<ToggleGroup type=\"multiple\">\n  <ToggleGroupItem value=\"bold\">Bold</ToggleGroupItem>\n  <ToggleGroupItem value=\"italic\">Italic</ToggleGroupItem>\n</ToggleGroup>\n```\n\n**Controlled single value:**\n\n```tsx\n// base — wrap/unwrap arrays.\nconst [value, setValue] = React.useState(\"normal\")\n<ToggleGroup value={[value]} onValueChange={(v) => setValue(v[0])}>\n\n// radix — plain string.\nconst [value, setValue] = React.useState(\"normal\")\n<ToggleGroup type=\"single\" value={value} onValueChange={setValue}>\n```\n\n---\n\n## Slider\n\nBase accepts a plain number for a single thumb. Radix always requires an array.\n\n**Incorrect (base):**\n\n```tsx\n<Slider defaultValue={[50]} max={100} step={1} />\n```\n\n**Correct (base):**\n\n```tsx\n<Slider defaultValue={50} max={100} step={1} />\n```\n\n**Correct (radix):**\n\n```tsx\n<Slider defaultValue={[50]} max={100} step={1} />\n```\n\nBoth use arrays for range sliders. Controlled `onValueChange` in base may need a cast:\n\n```tsx\n// base.\nconst [value, setValue] = React.useState([0.3, 0.7])\n<Slider value={value} onValueChange={(v) => setValue(v as number[])} />\n\n// radix.\nconst [value, setValue] = React.useState([0.3, 0.7])\n<Slider value={value} onValueChange={setValue} />\n```\n\n---\n\n## Accordion\n\nRadix requires `type=\"single\"` or `type=\"multiple\"` and supports `collapsible`. `defaultValue` is a string. Base uses no `type` prop, uses `multiple` boolean, and `defaultValue` is always an array.\n\n**Incorrect (base):**\n\n```tsx\n<Accordion type=\"single\" collapsible defaultValue=\"item-1\">\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n</Accordion>\n```\n\n**Correct (base):**\n\n```tsx\n<Accordion defaultValue={[\"item-1\"]}>\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n</Accordion>\n\n// Multi-select.\n<Accordion multiple defaultValue={[\"item-1\", \"item-2\"]}>\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n  <AccordionItem value=\"item-2\">...</AccordionItem>\n</Accordion>\n```\n\n**Correct (radix):**\n\n```tsx\n<Accordion type=\"single\" collapsible defaultValue=\"item-1\">\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n</Accordion>\n```\n",".agents/skills/shadcn/rules/chat.md":"# Chat & Messaging\n\nComponents for conversation and chat UI. Compose these instead of hand-rolling bubbles, scroll containers, dividers, or attachment cards.\n\nInstall: `npx shadcn@latest add message-scroller message bubble attachment marker`\n\nThe same component names and props ship for both `base` and `radix`; only composition differs (`render` vs `asChild`). See [base-vs-radix.md](./base-vs-radix.md).\n\n## Contents\n\n- Scrollable threads use MessageScroller\n- Message rows use Message\n- Message surfaces use Bubble\n- Attachments use Attachment\n- System notes and dividers use Marker\n- Streaming, anchoring, and jump-to-latest are built in\n- Escape hatch: the scroller hooks\n\n---\n\n## Scrollable threads use MessageScroller\n\nA conversation that scrolls, follows new messages, restores position, or jumps to a message uses `MessageScroller`. Don't build a raw overflow container with manual scroll wiring, and don't reach for `ScrollArea`.\n\nThe parts nest in a fixed order. Every direct child of the content is wrapped in a `MessageScrollerItem` so the scroller can measure, anchor, preserve position, track visibility, and jump to it. `MessageScrollerButton` sits inside `MessageScroller`, after the viewport.\n\n**Incorrect:**\n\n```tsx\n// Hand-rolled scroll container with manual stick-to-bottom logic.\n<div ref={scrollRef} onScroll={handleScroll} className=\"flex-1 overflow-y-auto\">\n  <div className=\"flex flex-col gap-6 p-4\">\n    {messages.map((m) => (\n      <ChatMessage key={m.id} message={m} />\n    ))}\n  </div>\n</div>\n```\n\n**Correct:**\n\n```tsx\n<MessageScrollerProvider autoScroll>\n  <MessageScroller>\n    <MessageScrollerViewport>\n      <MessageScrollerContent>\n        {messages.map((message) => (\n          <MessageScrollerItem\n            key={message.id}\n            messageId={message.id}\n            scrollAnchor={message.role === \"user\"}\n          >\n            <Message align={message.role === \"user\" ? \"end\" : \"start\"}>\n              {/* ...message content... */}\n            </Message>\n          </MessageScrollerItem>\n        ))}\n      </MessageScrollerContent>\n    </MessageScrollerViewport>\n    <MessageScrollerButton />\n  </MessageScroller>\n</MessageScrollerProvider>\n```\n\n---\n\n## Message rows use Message\n\n`Message` lays out a single row: avatar, header, content, footer, with alignment. Group consecutive rows from one sender with `MessageGroup`. Don't rebuild the row from flex divs.\n\n`align=\"end\"` is the current user's side; `align=\"start\"` is everyone else.\n\n```tsx\n<Message align=\"start\">\n  <MessageAvatar>\n    <Avatar>\n      <AvatarImage src={sender.avatar} alt={sender.name} />\n      <AvatarFallback>{initials}</AvatarFallback>\n    </Avatar>\n  </MessageAvatar>\n  <MessageContent>\n    <MessageHeader>{sender.name}</MessageHeader>\n    <Bubble>\n      <BubbleContent>{text}</BubbleContent>\n    </Bubble>\n    <MessageFooter>{time}</MessageFooter>\n  </MessageContent>\n</Message>\n```\n\n---\n\n## Message surfaces use Bubble\n\nThe colored message surface is `Bubble` + `BubbleContent`, never a styled `div` with `bg-muted` / `bg-primary` and hand-managed corners.\n\n- `variant`: `default`, `secondary`, `muted`, `tinted`, `outline`, `ghost`, `destructive`.\n- `align`: `start` or `end` (matches the `Message` side).\n\n`BubbleReactions` renders the reaction cluster. `side` (`top` | `bottom`) and `align` (`start` | `end`) position it against the bubble. Don't lay reactions out with absolutely-positioned `Badge`s.\n\n**Incorrect:**\n\n```tsx\n<div className=\"w-fit rounded-2xl bg-primary px-3 py-2 text-primary-foreground\">\n  {text}\n</div>\n```\n\n**Correct:**\n\n```tsx\n<Bubble variant=\"default\" align=\"end\">\n  <BubbleContent>{text}</BubbleContent>\n  <BubbleReactions side=\"bottom\" align=\"end\">\n    <Badge variant=\"secondary\">👍 2</Badge>\n  </BubbleReactions>\n</Bubble>\n```\n\n---\n\n## Attachments use Attachment\n\nFile and image attachments use `Attachment`, not `Item` or a custom card. It carries upload state, so wire `state` to the real status rather than rendering a separate spinner.\n\n- `state`: `idle`, `uploading`, `processing`, `error`, `done`. `uploading` and `processing` apply the `shimmer` animation to the title automatically.\n- `size`: `default`, `sm`, `xs`. `orientation`: `horizontal`, `vertical`.\n- Use `AttachmentGroup` to lay out several attachments in a scrolling row.\n\n```tsx\n<Attachment state=\"done\">\n  <AttachmentMedia variant=\"icon\">\n    <FileTextIcon />\n  </AttachmentMedia>\n  <AttachmentContent>\n    <AttachmentTitle>homepage-feedback.pdf</AttachmentTitle>\n    <AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>\n  </AttachmentContent>\n  <AttachmentActions>\n    <AttachmentAction>\n      <DownloadIcon />\n    </AttachmentAction>\n  </AttachmentActions>\n</Attachment>\n```\n\nFor an image, use `<AttachmentMedia variant=\"image\">` with an `img` child.\n\n---\n\n## System notes and dividers use Marker\n\nStatus lines (\"Sarah joined the conversation\"), date dividers (\"Today\"), and labeled separators are `Marker`, not a `Separator` plus a centered span.\n\n- `variant`: `default` (plain row), `separator` (centered label with rules on each side), `border` (bottom-bordered row).\n- `MarkerIcon` holds a leading icon; `MarkerContent` holds the label.\n\n**Incorrect:**\n\n```tsx\n<div className=\"flex items-center gap-3 py-2\">\n  <Separator className=\"flex-1\" />\n  <span className=\"text-xs text-muted-foreground\">Today</span>\n  <Separator className=\"flex-1\" />\n</div>\n```\n\n**Correct:**\n\n```tsx\n<Marker variant=\"separator\">\n  <MarkerContent>Today</MarkerContent>\n</Marker>\n```\n\n---\n\n## Streaming, anchoring, and jump-to-latest are built in\n\n`MessageScroller` handles the behavior that chat UIs usually reinvent. Don't write a `useStickToBottom` hook, a `ResizeObserver`, or manual `scrollTop` math.\n\n- **Follow the live edge while streaming.** `MessageScrollerProvider` with `autoScroll` keeps the view pinned to new content and yields the moment the user scrolls up. Streaming token updates that grow the last message are followed automatically.\n- **Anchor a turn.** `scrollAnchor` on a `MessageScrollerItem` marks the row to hold in view (typically the user's message that started the turn).\n- **Jump to latest.** `MessageScrollerButton` appears when the user scrolls away and scrolls back on click. `direction=\"end\"` (default) or `direction=\"start\"`. It is a self-managing control, so don't gate it behind your own scroll-position state.\n\nFor a \"thinking…\" indicator while the model generates, apply the `shimmer` utility to text. Don't author a custom keyframe animation. See [styling.md](./styling.md).\n\n---\n\n## Escape hatch: the scroller hooks\n\nFor behavior the parts don't expose, read state from the hooks rather than re-implementing the scroller: `useMessageScroller`, `useMessageScrollerVisibility`, and `useMessageScrollerScrollable`. They come from the auto-installed `@shadcn/react` dependency, so there's nothing extra to install. Reach for them only when composition can't express what you need.\n",".agents/skills/shadcn/rules/composition.md":"# Component Composition\n\n## Contents\n\n- Items always inside their Group component\n- Callouts use Alert\n- Empty states use Empty component\n- Toast notifications follow the project base\n- Choosing between overlay components\n- Dialog, Sheet, and Drawer always need a Title\n- Card structure\n- Button has no isPending or isLoading prop\n- TabsTrigger must be inside TabsList\n- Avatar always needs AvatarFallback\n- Use Separator instead of raw hr or border divs\n- Use Skeleton for loading placeholders\n- Use Badge instead of custom styled spans\n\n---\n\n## Items always inside their Group component\n\nNever render items directly inside the content container.\n\n**Incorrect:**\n\n```tsx\n<SelectContent>\n  <SelectItem value=\"apple\">Apple</SelectItem>\n  <SelectItem value=\"banana\">Banana</SelectItem>\n</SelectContent>\n```\n\n**Correct:**\n\n```tsx\n<SelectContent>\n  <SelectGroup>\n    <SelectItem value=\"apple\">Apple</SelectItem>\n    <SelectItem value=\"banana\">Banana</SelectItem>\n  </SelectGroup>\n</SelectContent>\n```\n\nThis applies to all group-based components:\n\n| Item | Group |\n| --- | --- |\n| `SelectItem`, `SelectLabel` | `SelectGroup` |\n| `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` |\n| `MenubarItem` | `MenubarGroup` |\n| `ContextMenuItem` | `ContextMenuGroup` |\n| `CommandItem` | `CommandGroup` |\n| `MessageScrollerItem` | `MessageScrollerContent` |\n| `Message` (consecutive, same sender) | `MessageGroup` |\n| `Bubble` (stacked) | `BubbleGroup` |\n| `Attachment` (in a row) | `AttachmentGroup` |\n\nChat components nest in a fixed order (`MessageScrollerProvider` → `MessageScroller` → `MessageScrollerViewport` → `MessageScrollerContent` → `MessageScrollerItem`). See [chat.md](./chat.md).\n\n---\n\n## Callouts use Alert\n\n```tsx\n<Alert>\n  <AlertTitle>Warning</AlertTitle>\n  <AlertDescription>Something needs attention.</AlertDescription>\n</Alert>\n```\n\n---\n\n## Empty states use Empty component\n\n```tsx\n<Empty>\n  <EmptyHeader>\n    <EmptyMedia variant=\"icon\">\n      <FolderIcon />\n    </EmptyMedia>\n    <EmptyTitle>No projects yet</EmptyTitle>\n    <EmptyDescription>Get started by creating a new project.</EmptyDescription>\n  </EmptyHeader>\n  <EmptyContent>\n    <Button>Create Project</Button>\n  </EmptyContent>\n</Empty>\n```\n\n---\n\n## Toast notifications follow the project base\n\nFor Base UI projects, use the `toast` component:\n\n```tsx\nimport { toast } from \"@/components/ui/toast\";\n\ntoast.add({\n  title: \"Changes saved.\",\n});\n```\n\nFor Radix and React Aria projects, use Sonner:\n\n```tsx\nimport { toast } from \"sonner\";\n\ntoast.success(\"Changes saved.\");\ntoast.error(\"Something went wrong.\");\ntoast(\"File deleted.\", {\n  action: { label: \"Undo\", onClick: () => undoDelete() },\n});\n```\n\n---\n\n## Choosing between overlay components\n\n| Use case                           | Component     |\n| ---------------------------------- | ------------- |\n| Focused task that requires input   | `Dialog`      |\n| Destructive action confirmation    | `AlertDialog` |\n| Side panel with details or filters | `Sheet`       |\n| Mobile-first bottom panel          | `Drawer`      |\n| Quick info on hover                | `HoverCard`   |\n| Small contextual content on click  | `Popover`     |\n\n---\n\n## Dialog, Sheet, and Drawer always need a Title\n\n`DialogTitle`, `SheetTitle`, `DrawerTitle` are required for accessibility. Use `className=\"sr-only\"` if visually hidden.\n\n```tsx\n<DialogContent>\n  <DialogHeader>\n    <DialogTitle>Edit Profile</DialogTitle>\n    <DialogDescription>Update your profile.</DialogDescription>\n  </DialogHeader>\n  ...\n</DialogContent>\n```\n\n---\n\n## Card structure\n\nUse full composition — don't dump everything into `CardContent`:\n\n```tsx\n<Card>\n  <CardHeader>\n    <CardTitle>Team Members</CardTitle>\n    <CardDescription>Manage your team.</CardDescription>\n  </CardHeader>\n  <CardContent>...</CardContent>\n  <CardFooter>\n    <Button>Invite</Button>\n  </CardFooter>\n</Card>\n```\n\n---\n\n## Button has no isPending or isLoading prop\n\nCompose with `Spinner` + `data-icon` + `disabled`:\n\n```tsx\n<Button disabled>\n  <Spinner data-icon=\"inline-start\" />\n  Saving...\n</Button>\n```\n\n---\n\n## TabsTrigger must be inside TabsList\n\nNever render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`:\n\n```tsx\n<Tabs defaultValue=\"account\">\n  <TabsList>\n    <TabsTrigger value=\"account\">Account</TabsTrigger>\n    <TabsTrigger value=\"password\">Password</TabsTrigger>\n  </TabsList>\n  <TabsContent value=\"account\">...</TabsContent>\n</Tabs>\n```\n\n---\n\n## Avatar always needs AvatarFallback\n\nAlways include `AvatarFallback` for when the image fails to load:\n\n```tsx\n<Avatar>\n  <AvatarImage src=\"/avatar.png\" alt=\"User\" />\n  <AvatarFallback>JD</AvatarFallback>\n</Avatar>\n```\n\n---\n\n## Use existing components instead of custom markup\n\n| Instead of | Use |\n| --- | --- |\n| `<hr>` or `<div className=\"border-t\">` | `<Separator />` |\n| `<div className=\"animate-pulse\">` with styled divs | `<Skeleton className=\"h-4 w-3/4\" />` |\n| `<span className=\"rounded-full bg-green-100 ...\">` | `<Badge variant=\"secondary\">` |\n",".agents/skills/shadcn/rules/forms.md":"# Forms & Inputs\n\n## Contents\n\n- Forms use FieldGroup + Field\n- InputGroup requires InputGroupInput/InputGroupTextarea\n- Buttons inside inputs use InputGroup + InputGroupAddon\n- Option sets (2–7 choices) use ToggleGroup\n- FieldSet + FieldLegend for grouping related fields\n- Field validation and disabled states\n\n---\n\n## Forms use FieldGroup + Field\n\nAlways use `FieldGroup` + `Field` — never raw `div` with `space-y-*`:\n\n```tsx\n<FieldGroup>\n  <Field>\n    <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n    <Input id=\"email\" type=\"email\" />\n  </Field>\n  <Field>\n    <FieldLabel htmlFor=\"password\">Password</FieldLabel>\n    <Input id=\"password\" type=\"password\" />\n  </Field>\n</FieldGroup>\n```\n\nUse `Field orientation=\"horizontal\"` for settings pages. Use `FieldLabel className=\"sr-only\"` for visually hidden labels.\n\n**Choosing form controls:**\n\n- Simple text input → `Input`\n- Dropdown with predefined options → `Select`\n- Searchable dropdown → `Combobox`\n- Native HTML select (no JS) → `native-select`\n- Boolean toggle → `Switch` (for settings) or `Checkbox` (for forms)\n- Single choice from few options → `RadioGroup`\n- Toggle between 2–5 options → `ToggleGroup` + `ToggleGroupItem`\n- OTP/verification code → `InputOTP`\n- Multi-line text → `Textarea`\n\n---\n\n## InputGroup requires InputGroupInput/InputGroupTextarea\n\nNever use raw `Input` or `Textarea` inside an `InputGroup`.\n\n**Incorrect:**\n\n```tsx\n<InputGroup>\n  <Input placeholder=\"Search...\" />\n</InputGroup>\n```\n\n**Correct:**\n\n```tsx\nimport { InputGroup, InputGroupInput } from \"@/components/ui/input-group\";\n\n<InputGroup>\n  <InputGroupInput placeholder=\"Search...\" />\n</InputGroup>;\n```\n\n---\n\n## Buttons inside inputs use InputGroup + InputGroupAddon\n\nNever place a `Button` directly inside or adjacent to an `Input` with custom positioning.\n\n**Incorrect:**\n\n```tsx\n<div className=\"relative\">\n  <Input placeholder=\"Search...\" className=\"pr-10\" />\n  <Button className=\"absolute right-0 top-0\" size=\"icon\">\n    <SearchIcon />\n  </Button>\n</div>\n```\n\n**Correct:**\n\n```tsx\nimport {\n  InputGroup,\n  InputGroupInput,\n  InputGroupAddon,\n} from \"@/components/ui/input-group\";\n\n<InputGroup>\n  <InputGroupInput placeholder=\"Search...\" />\n  <InputGroupAddon>\n    <Button size=\"icon\">\n      <SearchIcon data-icon=\"inline-start\" />\n    </Button>\n  </InputGroupAddon>\n</InputGroup>;\n```\n\n---\n\n## Option sets (2–7 choices) use ToggleGroup\n\nDon't manually loop `Button` components with active state.\n\n**Incorrect:**\n\n```tsx\nconst [selected, setSelected] = useState(\"daily\")\n\n<div className=\"flex gap-2\">\n  {[\"daily\", \"weekly\", \"monthly\"].map((option) => (\n    <Button\n      key={option}\n      variant={selected === option ? \"default\" : \"outline\"}\n      onClick={() => setSelected(option)}\n    >\n      {option}\n    </Button>\n  ))}\n</div>\n```\n\n**Correct:**\n\n```tsx\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\n\n<ToggleGroup spacing={2}>\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n  <ToggleGroupItem value=\"weekly\">Weekly</ToggleGroupItem>\n  <ToggleGroupItem value=\"monthly\">Monthly</ToggleGroupItem>\n</ToggleGroup>;\n```\n\nCombine with `Field` for labelled toggle groups:\n\n```tsx\n<Field orientation=\"horizontal\">\n  <FieldTitle id=\"theme-label\">Theme</FieldTitle>\n  <ToggleGroup aria-labelledby=\"theme-label\" spacing={2}>\n    <ToggleGroupItem value=\"light\">Light</ToggleGroupItem>\n    <ToggleGroupItem value=\"dark\">Dark</ToggleGroupItem>\n    <ToggleGroupItem value=\"system\">System</ToggleGroupItem>\n  </ToggleGroup>\n</Field>\n```\n\n> **Note:** `defaultValue` and `type`/`multiple` props differ between base and radix. See [base-vs-radix.md](./base-vs-radix.md#togglegroup).\n\n---\n\n## FieldSet + FieldLegend for grouping related fields\n\nUse `FieldSet` + `FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading:\n\n```tsx\n<FieldSet>\n  <FieldLegend variant=\"label\">Preferences</FieldLegend>\n  <FieldDescription>Select all that apply.</FieldDescription>\n  <FieldGroup className=\"gap-3\">\n    <Field orientation=\"horizontal\">\n      <Checkbox id=\"dark\" />\n      <FieldLabel htmlFor=\"dark\" className=\"font-normal\">\n        Dark mode\n      </FieldLabel>\n    </Field>\n  </FieldGroup>\n</FieldSet>\n```\n\n---\n\n## Field validation and disabled states\n\nBoth attributes are needed — `data-invalid`/`data-disabled` styles the field (label, description), while `aria-invalid`/`disabled` styles the control.\n\n```tsx\n// Invalid.\n<Field data-invalid>\n  <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n  <Input id=\"email\" aria-invalid />\n  <FieldDescription>Invalid email address.</FieldDescription>\n</Field>\n\n// Disabled.\n<Field data-disabled>\n  <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n  <Input id=\"email\" disabled />\n</Field>\n```\n\nWorks for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`, `NativeSelect`, `InputOTP`.\n",".agents/skills/shadcn/rules/icons.md":"# Icons\n\n**Always use the project's configured `iconLibrary` for imports.** Check the `iconLibrary` field from project context: `lucide` → `lucide-react`, `tabler` → `@tabler/icons-react`, etc. Never assume `lucide-react`.\n\n---\n\n## Icons in Button use data-icon attribute\n\nAdd `data-icon=\"inline-start\"` (prefix) or `data-icon=\"inline-end\"` (suffix) to the icon. No sizing classes on the icon.\n\n**Incorrect:**\n\n```tsx\n<Button>\n  <SearchIcon className=\"mr-2 size-4\" />\n  Search\n</Button>\n```\n\n**Correct:**\n\n```tsx\n<Button>\n  <SearchIcon data-icon=\"inline-start\"/>\n  Search\n</Button>\n\n<Button>\n  Next\n  <ArrowRightIcon data-icon=\"inline-end\"/>\n</Button>\n```\n\n---\n\n## No sizing classes on icons inside components\n\nComponents handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons inside `Button`, `DropdownMenuItem`, `Alert`, `Sidebar*`, or other shadcn components. Unless the user explicitly asks for custom icon sizes.\n\n**Incorrect:**\n\n```tsx\n<Button>\n  <SearchIcon className=\"size-4\" data-icon=\"inline-start\" />\n  Search\n</Button>\n\n<DropdownMenuItem>\n  <SettingsIcon className=\"mr-2 size-4\" />\n  Settings\n</DropdownMenuItem>\n```\n\n**Correct:**\n\n```tsx\n<Button>\n  <SearchIcon data-icon=\"inline-start\" />\n  Search\n</Button>\n\n<DropdownMenuItem>\n  <SettingsIcon />\n  Settings\n</DropdownMenuItem>\n```\n\n---\n\n## Pass icons as component objects, not string keys\n\nUse `icon={CheckIcon}`, not a string key to a lookup map.\n\n**Incorrect:**\n\n```tsx\nconst iconMap = {\n  check: CheckIcon,\n  alert: AlertIcon,\n};\n\nfunction StatusBadge({ icon }: { icon: string }) {\n  const Icon = iconMap[icon];\n  return <Icon />;\n}\n\n<StatusBadge icon=\"check\" />;\n```\n\n**Correct:**\n\n```tsx\n// Import from the project's configured iconLibrary (e.g. lucide-react, @tabler/icons-react).\nimport { CheckIcon } from \"lucide-react\";\n\nfunction StatusBadge({ icon: Icon }: { icon: React.ComponentType }) {\n  return <Icon />;\n}\n\n<StatusBadge icon={CheckIcon} />;\n```\n",".agents/skills/shadcn/rules/styling.md":"# Styling & Customization\n\nSee [customization.md](../customization.md) for theming, CSS variables, and adding custom colors.\n\n## Contents\n\n- Semantic colors\n- Built-in variants first\n- className for layout only\n- No space-x-* / space-y-*\n- Prefer size-* over w-* h-* when equal\n- Prefer truncate shorthand\n- No manual dark: color overrides\n- Use cn() for conditional classes\n- No manual z-index on overlay components\n- Use shimmer / scroll-fade utilities, not custom animations\n\n---\n\n## Semantic colors\n\n**Incorrect:**\n\n```tsx\n<div className=\"bg-blue-500 text-white\">\n  <p className=\"text-gray-600\">Secondary text</p>\n</div>\n```\n\n**Correct:**\n\n```tsx\n<div className=\"bg-primary text-primary-foreground\">\n  <p className=\"text-muted-foreground\">Secondary text</p>\n</div>\n```\n\n---\n\n## No raw color values for status/state indicators\n\nFor positive, negative, or status indicators, use Badge variants, semantic tokens like `text-destructive`, or define custom CSS variables — don't reach for raw Tailwind colors.\n\n**Incorrect:**\n\n```tsx\n<span className=\"text-emerald-600\">+20.1%</span>\n<span className=\"text-green-500\">Active</span>\n<span className=\"text-red-600\">-3.2%</span>\n```\n\n**Correct:**\n\n```tsx\n<Badge variant=\"secondary\">+20.1%</Badge>\n<Badge>Active</Badge>\n<span className=\"text-destructive\">-3.2%</span>\n```\n\nIf you need a success/positive color that doesn't exist as a semantic token, use a Badge variant or ask the user about adding a custom CSS variable to the theme (see [customization.md](../customization.md)).\n\n---\n\n## Built-in variants first\n\n**Incorrect:**\n\n```tsx\n<Button className=\"border border-input bg-transparent hover:bg-accent\">\n  Click me\n</Button>\n```\n\n**Correct:**\n\n```tsx\n<Button variant=\"outline\">Click me</Button>\n```\n\n---\n\n## className for layout only\n\nUse `className` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables.\n\n**Incorrect:**\n\n```tsx\n<Card className=\"bg-blue-100 text-blue-900 font-bold\">\n  <CardContent>Dashboard</CardContent>\n</Card>\n```\n\n**Correct:**\n\n```tsx\n<Card className=\"max-w-md mx-auto\">\n  <CardContent>Dashboard</CardContent>\n</Card>\n```\n\nTo customize a component's appearance, prefer these approaches in order:\n\n1. **Built-in variants** — `variant=\"outline\"`, `variant=\"destructive\"`, etc.\n2. **Semantic color tokens** — `bg-primary`, `text-muted-foreground`.\n3. **CSS variables** — define custom colors in the global CSS file (see [customization.md](../customization.md)).\n\n---\n\n## No space-x-* / space-y-*\n\nUse `gap-*` instead. `space-y-4` → `flex flex-col gap-4`. `space-x-2` → `flex gap-2`.\n\n```tsx\n<div className=\"flex flex-col gap-4\">\n  <Input />\n  <Input />\n  <Button>Submit</Button>\n</div>\n```\n\n---\n\n## Prefer size-* over w-* h-* when equal\n\n`size-10` not `w-10 h-10`. Applies to icons, avatars, skeletons, etc.\n\n---\n\n## Prefer truncate shorthand\n\n`truncate` not `overflow-hidden text-ellipsis whitespace-nowrap`.\n\n---\n\n## No manual dark: color overrides\n\nUse semantic tokens — they handle light/dark via CSS variables. `bg-background text-foreground` not `bg-white dark:bg-gray-950`.\n\n---\n\n## Use cn() for conditional classes\n\nUse the `cn()` utility from the project for conditional or merged class names. Don't write manual ternaries in className strings.\n\n**Incorrect:**\n\n```tsx\n<div className={`flex items-center ${isActive ? \"bg-primary text-primary-foreground\" : \"bg-muted\"}`}>\n```\n\n**Correct:**\n\n```tsx\nimport { cn } from \"@/lib/utils\"\n\n<div className={cn(\"flex items-center\", isActive ? \"bg-primary text-primary-foreground\" : \"bg-muted\")}>\n```\n\n---\n\n## No manual z-index on overlay components\n\n`Dialog`, `Sheet`, `Drawer`, `AlertDialog`, `DropdownMenu`, `Popover`, `Tooltip`, `HoverCard` handle their own stacking. Never add `z-50` or `z-[999]`.\n\n---\n\n## Use shimmer / scroll-fade utilities, not custom animations\n\nFor a live \"thinking…\" or loading-text shimmer, apply the `shimmer` utility. Don't author a custom `@keyframes` or a `bg-clip-text` gradient sweep.\n\nFor scroll-aware edge fading on a scroll container, use `scroll-fade` (and the axis variants `scroll-fade-x` / `scroll-fade-b`). Don't hand-roll mask gradients. The chat components already apply these internally: `Attachment` shimmers its title during upload, and `MessageScrollerViewport` fades its edges.\n\n**Incorrect:**\n\n```tsx\n<span className=\"animate-pulse bg-gradient-to-r from-muted-foreground/40 via-foreground/70 to-muted-foreground/40 bg-clip-text text-transparent [animation:shimmer_1.6s_infinite]\">\n  Thinking…\n</span>\n```\n\n**Correct:**\n\n```tsx\n<span className=\"shimmer\">Thinking…</span>\n```\n",".claude/commands/validate-auth-schema.md":"# Validate Auth Schema\n\nCheck that the Drizzle schema in `db/schema/` still satisfies what Better Auth expects. Better Auth derives its required tables from the enabled plugins, so the generated JSON — not the upstream docs — is the source of truth:\n\n```bash\nbun run db/scripts/generate-auth-schema.ts\n```\n\nThe script builds a real auth instance from `apps/api/lib/auth.ts` with fixed placeholder credentials, every optional integration switched on, so the output is the full set of tables rather than the subset your environment happens to enable. Per field it carries the `type`, the `required`, `unique` and `index` flags, any literal `defaultValue`, and the reference target with its `onDelete`. Per table it carries `indexes`: the composite indexes a plugin declares.\n\nCompare that output against `db/schema/` and report:\n\n- Tables Better Auth expects that are missing, and tables no plugin in `auth.ts` needs any more.\n- Field-level drift: missing or extra columns, mismatched types, and `required` or `unique` flags that disagree.\n- Foreign keys pointing at the wrong table or column.\n- A `true` `index` flag, a table-level entry in `indexes`, a literal `defaultValue`, or an `onDelete` in the output is Better Auth asking for it: report a schema that lacks one. Indexes, defaults and cascades the project adds beyond that list are its own decisions — check those against the conventions in `db/AGENTS.md` and do not report them as drift.\n- Local naming that must stay mapped, not renamed away: Better Auth's `account` is our `identity` table.\n- Project-specific columns, indexes, and constraints are expected and fine on their own. Flag any that constrain a write Better Auth performs: a `NOT NULL` column with no database default breaks every insert into that table, and an extra unique or check constraint can reject a row Better Auth considers valid. Fields that must participate in its models belong in `additionalFields` rather than added to the table behind its back.\n\nReport findings with the specific fix for each. Do not run `db:push`, `db:generate`, or `db:migrate` as part of a validation — schema changes are a separate, deliberate step.\n",".github/copilot-instructions.md":"Read AGENTS.md in the repository root and any subdirectory AGENTS.md files for project context, conventions, and architecture details before making changes.\n","AGENTS.md":"## Monorepo Structure\n\n- `apps/web/` – Astro marketing site, served by an edge worker that also routes traffic to the app/api workers via service bindings\n- `apps/app/` – Main SPA (React, TanStack Router file-based routing)\n- `apps/api/` – API server (Hono + tRPC + Better Auth)\n- `apps/email/` – React Email templates (built before API dev server starts)\n- `packages/ui/` – shadcn/ui components (new-york style)\n- `packages/core/` – Shared utilities\n- `db/` – Drizzle ORM schemas and migrations (Neon PostgreSQL)\n- `infra/` – Terraform (Hyperdrive and optional R2 storage; Wrangler owns Workers and DNS)\n- `docs/` – VitePress docs; `docs/adr/` for architecture decision records\n\n## Tech Stack\n\n- **Toolchain:** Bun >=1.4.0 (package manager, scripts, local API server), TypeScript 7.0 (native compiler), Oxlint + Oxfmt, ESM (`\"type\": \"module\"`)\n- **Production runtime:** Cloudflare Workers (workerd) – not Bun. Code that ships must run on Workers APIs.\n- **Frontend:** React 19, TanStack Router, TanStack Query, Jotai, shadcn/ui (new-york), Tailwind CSS v4\n- **Backend:** Hono, tRPC 11, Better Auth (email OTP, passkey, Google OAuth, organizations)\n- **Database:** Neon PostgreSQL, Drizzle ORM (`snake_case` casing), Cloudflare Hyperdrive\n- **Email:** React Email, Resend\n- **Testing:** Vitest, Happy DOM, PGlite (in-process Postgres, no service to start)\n- **Deployment:** Cloudflare Workers (Wrangler), Terraform\n\n## Commands\n\n```bash\nbun dev                        # Start web + api + app concurrently\nbun run build                  # Build email, web, api, and app workspaces\nbun run test                   # Vitest (watch mode; --run for single run)\nbun lint                       # Oxlint\nbun run format:check           # Oxfmt (bun run format writes)\nbun typecheck                  # tsc --build (builds apps/email for its types)\nbun infra:check                # Terraform fmt + validate, no credentials or state\nbun ui:add <component>         # Add shadcn/ui component to packages/ui\nbun deploy:{staging,production} # Build and deploy api → app → web; no migrations\n\n# Per-app: bun {web,app,api}:{dev,build,deploy}; test for app/api, check for web\n# Database: bun db:{push,generate,migrate,studio,seed,export}\n#   :staging / :production on migrate, studio, export; seed stops at :staging;\n#   generate is local-only, and push refuses a non-local database\n```\n\n## Verification\n\n- Run the narrowest checks covering what you changed, then report what you ran. Never imply verification you did not perform.\n- Do not run migrations, deployments, or Terraform `plan`/`apply` unless explicitly asked – they act on shared environments.\n\n## Architecture\n\n- Three workers: web (marketing site + edge router), app (SPA assets), api (Hono server).\n- API worker has `nodejs_compat` enabled; web and app workers do NOT.\n- Web worker routes: `/api/*` → API worker, app routes → App worker, static → assets.\n- Service bindings connect workers internally (no public cross-worker URLs).\n- Per-workspace conventions live in subdirectory `AGENTS.md` files: `apps/api/`, `apps/app/`, `db/`, `infra/`, `packages/ui/`.\n\n## Agent Tooling\n\n- `AGENTS.md` files are the canonical instructions; per-tool files (`CLAUDE.md`, `.gemini/settings.json`) only point at them.\n- **Every directory with an `AGENTS.md` needs a sibling `CLAUDE.md` containing `@AGENTS.md`.** Codex scopes nested `AGENTS.md` to its directory tree natively; Claude Code's nested lookup matches only `CLAUDE.md`, so without the adapter it never loads the scoped file. Adding a scoped `AGENTS.md` without one is a silent no-op for Claude.\n- **Skills live in `.agents/skills/<name>/SKILL.md`, symlinked into `.claude/skills/<name>/`.** Both vendors build on the Agent Skills format, but only the discovery paths differ: Codex scans `.agents/skills/` from the repo root natively, Claude Code scans `.claude/skills/`. One canonical copy plus a symlink is what makes a skill visible to both. Third-party skills installed by the `skills` CLI are tracked in `skills-lock.json` – do not hand-edit those directories.\n- Skills are for workflows the model should recognise and reach for on its own; their `description` is the trigger. A prompt you invoke deliberately stays a Claude Code command in `.claude/commands/<name>.md`, which has no portable equivalent.\n- `*.local.md` and `*.local.json` are gitignored – personal prompts and settings stay out of the repo.\n\n## Design Philosophy\n\n- Simplest correct solution. No speculative abstractions – add them only when a real second use case exists.\n- No superficial work: no coverage-only tests, no redundant comments, no wrappers that just forward calls.\n- Fail loudly in core logic. Do not silently swallow errors or mask incorrect state.\n- Three similar lines are better than a premature abstraction.\n- Prefer explicit, readable code over clever or compressed patterns.\n- Use precise TypeScript types. Avoid `any` and unnecessary type assertions – let the compiler enforce correctness.\n- Document non-obvious trade-offs and decisions. Explain why, not what – every word must add value.\n\n## Formatting\n\n- Import order belongs to Oxfmt (`sortImports` in `.oxfmtrc.json`): applied by `bun run format`, checked by `bun run format:check` in CI and by lint-staged on commit. Do not reintroduce an editor code action or a lint rule for it.\n\n## Markdown\n\n- Prose is not hard-wrapped: keep each paragraph on one line and use paragraphs, lists and headings for structure. Oxfmt enforces this with `proseWrap: \"never\"`.\n- Keep a blank line after a VitePress container's opening marker and before its closing `:::`. Oxfmt does not recognise `:::`, so an adjacent line gets folded into the marker, turning the body into the container title and swallowing everything up to the next `:::`.\n","CLAUDE.md":"@AGENTS.md\n\n## Claude-Specific Guidance\n\n- Use Plan mode for multi-file or architectural changes.\n- Prefer an existing project skill or command over improvising the same workflow.\n- See \"Agent Tooling\" in `AGENTS.md` before adding a command or skill.\n","db/AGENTS.md":"## Schema Conventions\n\n- Every new table file must be re-exported from `schema/index.ts`. `drizzle.config.ts` points at that barrel rather than the directory, so an unexported table is invisible to `db:generate`, which reports no changes instead of failing.\n- Drizzle `casing: \"snake_case\"` – use camelCase in TypeScript, columns map to snake_case in DB.\n- Primary keys use application-generated prefixed CUID2 IDs: `generateAuthId(model)` for Better Auth models and `generateId(\"xxx\")` for domain tables. See `schema/id.ts` for the prefix map.\n- Timestamps: `timestamp({ withTimezone: true, mode: \"date\" })`. Every table has `createdAt` (`.defaultNow().notNull()`) and `updatedAt` (`.defaultNow().$onUpdate(() => new Date()).notNull()`).\n- `identity` table = Better Auth's `account` table, renamed via `account.modelName: \"identity\"` in auth config.\n- `member.role` (\"owner\", \"admin\", \"member\") and `invitation.status` are free `text`, not pgEnum – avoids fragile coupling with Better Auth's values.\n- `organization.metadata` is `text`, not JSONB – Better Auth handles serialization.\n- Migrations differ by role. A project generated from this kit keeps ordinary migration history – see `docs/database/migrations.md`. The starter kit itself squashes into a single `0000_init.sql`; contributors changing its schema should read `.github/CONTRIBUTING.md` first.\n\n## Extended Fields (beyond Better Auth defaults)\n\n- **Passkey:** `lastUsedAt` (security audits), `deviceName` (user-friendly label), `platform` (\"platform\" | \"cross-platform\").\n- **Invitation:** `acceptedAt`/`rejectedAt` are reserved for application hooks; Better Auth updates `status` but does not populate them.\n\n## Indexes and Constraints\n\n- Every foreign key column gets an index: `{table}_{column}_idx`. All foreign keys use `onDelete: \"cascade\"`.\n- Composite uniques: `member(userId, organizationId)`, `invitation(organizationId, email)`, `identity(providerId, accountId)`.\n- Better Auth keys an identity on `(providerId, accountId)` and throws when a lookup matches two rows. Never add an `issuer` column back – see the note on `identity` in `schema/user.ts`.\n- The `invitation` unique is a starter limitation: Better Auth creates a new row for a later invitation, so remove the constraint before supporting re-invites after acceptance, rejection, or cancellation.\n- `session.activeOrganizationId` has an index but no FK constraint (Better Auth design).\n\n## Seeds\n\n- Use `onConflictDoNothing()` for idempotent seeds (safe to rerun).\n\n## Testing\n\n- `createTestDatabase()` from `@repo/db/testing` boots PGlite and applies `migrations/`, so tests run on the schema production receives. No connection string, no service, no `.env.test` – there is deliberately no `test` environment in `drizzle.config.ts`.\n- It returns `{ db, reset, close }`. One instance at module scope (`await createTestDatabase()`), `reset()` between tests, `close()` in `afterAll`. `reset()` truncates every table in the schema, derived from the barrel, so a new table is covered automatically.\n- Use the test database when behavior depends on query scoping or database constraints. A mocked lookup cannot fail on a wrong `where`. See `docs/testing.md`.\n- `Database` (exported from `@repo/db`) is the driver-agnostic client type – postgres-js in production, PGlite in tests. Type parameters and helpers against it, not `PostgresJsDatabase`. Selected rows, relational queries and `.returning()` stay schema-typed; `execute()` and mutations without `.returning()` resolve to `unknown` (the command result belongs to the driver), and `$client` is not exposed. Add `.returning()` when a write needs to report anything.\n\n## Environment\n\n- `ENVIRONMENT` overrides `NODE_ENV` for env file selection. Database scripts use `production`, `staging`, and `dev`; the API runtime uses `production`, `staging`, and `development`.\n- `migrate`, `studio` and `export` have `:staging` / `:production` variants; `seed` stops at `:staging`; `generate` and `push` have none.\n- **`push` is enforced local, not merely documented as local.** `scripts/guard-push.ts` resolves the same `DATABASE_URL` the command would use and refuses a non-local host; `ALLOW_REMOTE_DB_PUSH=1` is the deliberate way past it. Reshaping in place is an unreviewed migration that can drop a column and its data.\n- `scripts/grant-app-role.sql` creates the least-privilege role Hyperdrive should use in staging and production. It is documentation-and-tooling only – nothing in the repo runs it.\n- Development loads `.env.{envName}.local` → `.env.local` → `.env`, first value wins. Staging and production load only `.env.{envName}.local`, override exported values, and throw if it is absent.\n","db/CLAUDE.md":"@AGENTS.md\n","infra/AGENTS.md":"Terraform for the durable Cloudflare resources the Workers consume. [`README.md`](./README.md) covers setup and usage, [ADR-002](../docs/adr/002-terraform-wrangler-boundary.md) the ownership decision, [ADR-003](../docs/adr/003-hcp-terraform-state.md) the state backend, and [`docs/specs/infra-terraform.md`](../docs/specs/infra-terraform.md) the full specification. Those carry the rationale and the operating procedure; this file is the short list of constraints that matter while editing `infra/`.\n\n## Ownership Boundary\n\n- Terraform provisions what the Workers consume: the two Hyperdrive configurations and the opt-in R2 uploads bucket. Nothing else. Worker names, code, routes, custom domains, bindings, vars, secrets, and assets belong to `apps/*/wrangler.jsonc` – adding a `cloudflare_workers_*` or DNS resource here is the change that breaks the design, and it will not announce itself as a conflict until a deploy overwrites something.\n- The handoff is manual and non-secret: Hyperdrive IDs and the R2 bucket name are pasted into `wrangler.jsonc` once per environment. A change to a handoff output is incomplete without the corresponding `wrangler.jsonc` update.\n\n## Layout and Naming\n\n- The two roots stay structurally parallel: same provider constraints, same module interface, same variable and output names. A structural change to one belongs in the other.\n- Their _values_ may differ on purpose. Enabling an optional resource in staging before production is a supported rollout, not drift – see the [file uploads recipe](../docs/recipes/file-uploads.md). Do not copy an environment-specific value across unless the change is meant for both.\n- Resource logic lives in `modules/cloudflare`. Each root holds its environment identity, provider and `cloud` configuration, root variables and outputs, and the module call.\n- Resource values follow `{project_slug}-{environment}[-role]`. `project_slug` must match the worker name prefix in `apps/*/wrangler.jsonc`, or the bindings point at resources that do not exist.\n- Resource identifiers name the concrete thing (`cloudflare_hyperdrive_config.cached`); module names describe the role (`module.edge`).\n\n## Variables and Outputs\n\n- Encode structural constraints in `validation` blocks so they fail locally rather than mid-apply, and write the `error_message` as the fix rather than the rule – see `uploads_cors_origins` rejecting `\"*\"`.\n- Inputs holding credentials are `sensitive = true` (`database_url`). Outputs are stable, non-secret identifiers only – they are printed into CI summaries and committed to `wrangler.jsonc`.\n\n## Running Terraform\n\nValidate locally with the same credential-free check CI runs:\n\n```bash\nbun infra:check\n```\n\nThat is `terraform fmt -check` plus `init -backend=false` and `validate` for each root. `-backend=false` installs providers and modules without contacting the backend, so it needs no HCP token and reads no state.\n\n- **Never run `plan`, `apply`, `destroy`, `import`, or any state mutation.** `plan` is not the safe half: HCP Terraform executes the configuration it is handed in the workspace's privileged run environment, with that workspace's state and variables, so planning uncommitted work exercises real credentials against real infrastructure. `bun infra:check` is the branch-level signal; applying is a manual dispatch of `.github/workflows/infra.yml` from `main`.\n- Never commit state, `.terraform/`, credentials, or `.tfvars` – state holds the database password. `infra/.gitignore` covers these; do not narrow it.\n","infra/CLAUDE.md":"@AGENTS.md\n","packages/core/README.md":"# Core Package\n\nShared utilities and helpers used across the monorepo.\n","packages/typescript-config/README.md":"# TypeScript Configuration\n\nShared TypeScript configuration for the monorepo.\n\n## Usage\n\nExtend from the appropriate configuration in your `tsconfig.json`:\n\n```jsonc\n// React applications\n{ \"extends\": \"@repo/typescript-config/react.jsonc\" }\n\n// Node.js/Bun applications\n{ \"extends\": \"@repo/typescript-config/node.jsonc\" }\n\n// Cloudflare Workers\n{ \"extends\": \"@repo/typescript-config/cloudflare.jsonc\" }\n```\n\n## Available Configurations\n\n- `base.jsonc` -- Core strict-mode configuration shared by all targets\n- `react.jsonc` -- React applications with DOM types and JSX support\n- `node.jsonc` -- Node.js/Bun backend services\n- `cloudflare.jsonc` -- Cloudflare Workers edge functions\n","packages/ui/AGENTS.md":"shadcn/ui component library (new-york style, Radix primitives, Tailwind v4). Consumed as `@repo/ui`.\n\n## Scope\n\n- Only generic primitives belong here. If a component imports a route, a query, or the session, it belongs in `apps/app/components/` instead.\n- Nothing here imports application or domain code (`apps/`, `db/`). Consumers still have to supply Tailwind `@source` entries and the theme tokens.\n- Imports inside this package go through Node subpath imports — `#lib/utils`, `#components/toggle` — declared in `package.json#imports` and mirrored by the `components.json` aliases, so the CLI generates them natively. They resolve against _this_ package from every workspace, so a consumer needs no matching file of its own. Requires `moduleResolution: \"bundler\"`, which `packages/typescript-config/base.jsonc` already sets.\n- `index.ts` exports the top-level primitives in `components/`. Nested files are implementation details — promote one deliberately if it should be public.\n\n- `scripts/` is intentionally outside the tsconfig `include`. These are Bun CLI tools, while the library's declaration build uses the browser-only React preset (`types: [\"vite/client\"]`). Including them would require Bun/Node types and emit declarations for tooling. Both commands hit the network and overwrite files, so smoke-test changes on a single component (`bun ui:update button`).\n\n## Adding and Updating Components\n\n- Use `bun ui:add <component>`; don't hand-write files the registry already has. The wrappers take item names only and reject flags — they exist for reusable `registry:ui` primitives and always write to `packages/ui`. Blocks, pages, custom registries, and the read-only flags go to the CLI directly — point it at the workspace, or it resolves the wrong `components.json`: `bunx shadcn@latest add button --dry-run --cwd packages/ui`.\n- Both run `scripts/postprocess.ts` afterwards: it strips `\"use client\"`, regenerates `index.ts`, and formats. `index.ts` is generated; add components with the CLI, not by editing it.\n- Components import primitives from the unified `radix-ui` package, not the per-primitive `@radix-ui/react-*` ones. The CLI adds whatever a component needs; review and commit the resulting `package.json` and `bun.lock` changes.\n- `bun ui:update` re-fetches every installed component in one `shadcn add` call, overwriting in place. Review `git diff` before committing; local edits are lost. Pass names (`bun ui:update button card`) to narrow it; unknown names are rejected rather than silently added, though a named component still pulls its registry dependencies.\n- Registry output is not uniform — read what it generated. Postprocessing is mechanical; API changes still need review. Convert React 18 patterns before committing — no lint rule covers any of these, so they are a review step:\n  - `<Context.Provider value={x}>` → `<Context value={x}>`\n  - `React.useContext(C)` → `React.use(C)`\n  - `React.ElementRef<T>` → `React.ComponentRef<T>` — `ElementRef` is a deprecated alias in `@types/react` 19. `bun --cwd apps/web check` reports it as a hint.\n- `components.json` needs `tailwind.config: \"\"`. That empty string is how the CLI detects Tailwind v4; without it the registry silently serves the v3-era components (`forwardRef`, no `data-slot`) into a v4 project.\n\n## Styling\n\n- Every component accepts `className` and passes it through `cn()` last — directly, or via the `className` slot on a `cva` variants call — so callers can override defaults without a specificity fight.\n- Use theme tokens (`bg-primary`, `text-muted-foreground`), never raw colors. Each consumer defines the values in its own `styles/globals.css` — `apps/app` and `apps/web` keep separate copies, so a palette change means editing both. `styles.css` here exists only to satisfy the shadcn CLI.\n- Class names must appear as complete literals — Tailwind scans text, so `` `bg-${color}-500` `` produces nothing.\n- Consuming apps must `@source` every directory here that holds class names, or those classes are stripped from their build.\n- Enter/exit utilities (`animate-in`, `fade-in-0`, `zoom-in-95`, `slide-in-from-*`) come from `tw-animate-css`, which each consumer imports separately — `apps/app` does, `apps/web` does not, because nothing it renders is animated. Adding an animated component to a consumer that lacks the import fails silently: Tailwind does not recognise the class names, drops them, and reports nothing, so the component simply renders without transitions.\n\n## Conventions\n\n- Named exports only — no default exports.\n- Variants via `class-variance-authority`; export the variants object when another component composes it (see `toggle.tsx` → `toggle-group.tsx`).\n- Prefer a Radix primitive over hand-rolled behavior — it brings the ARIA roles and keyboard handling with it. `ToggleGroup type=\"single\"` already renders `role=\"radiogroup\"` with `role=\"radio\"` items and arrow-key navigation, so callers add no keyboard code.\n","packages/ui/CLAUDE.md":"@AGENTS.md\n","packages/ui/README.md":"# UI Components\n\nShared UI component library built on shadcn/ui (new-york style), Radix UI, and Tailwind CSS v4.\n\n[Documentation](https://reactstarter.com/frontend/ui)\n\n## Usage\n\n```typescript\nimport { Button, Card, Input, cn } from \"@repo/ui\";\n```\n\n## Commands\n\n```bash\nbun ui:add <component>    # Add a shadcn/ui component\nbun ui:update             # Re-fetch installed components\n```\n\nSee [AGENTS.md](./AGENTS.md) for the conventions these commands assume.\n\n## Structure\n\n```bash\ncomponents/       # shadcn/ui components\nhooks/            # Custom React hooks\nlib/              # Utilities (cn function)\nscripts/          # ui:add / ui:update and their postprocessing\nindex.ts          # Generated barrel export\n```\n\nConsuming apps must `@source` every directory here that holds class names, or those classes are stripped from their build. See [apps/app/tailwind.config.css](../../apps/app/tailwind.config.css) for an example.\n","packages/ws-protocol/README.md":"# WebSocket Protocol\n\nType-safe WebSocket protocol definitions using [WS-Kit](https://github.com/kriasoft/ws-kit).\n\n[Documentation](https://reactstarter.com/recipes/websockets)\n\n## Quick Start\n\n```bash\nbun run example       # Run example server\n```\n\n## Usage\n\n```typescript\nimport { z, message, rpc } from \"@ws-kit/zod\";\nimport { createRouter, withZod } from \"@ws-kit/zod\";\n\nconst Ping = message(\"PING\", { timestamp: z.number().optional() });\nconst Pong = message(\"PONG\", { timestamp: z.number() });\n\nconst router = createRouter<{ userId?: string }>()\n  .plugin(withZod())\n  .on(Ping, (ctx) => {\n    ctx.send(Pong, { timestamp: Date.now() });\n  });\n```\n\n## Structure\n\n```bash\nmessages.ts   # Message schema definitions\nrouter.ts     # Router factory with handlers\nexample.ts    # Example server\nindex.ts      # Public exports\n```\n"},"files":{".agents/skills/merge-seed/SKILL.md":"---\nname: merge-seed\ndescription: Merge upstream React Starter Kit updates (the `seed` remote) into main, preserving this project's identity, scope, and behavior. Use when asked to sync, pull, or merge the seed / starter kit / upstream template.\n---\n\n# Merge seed updates\n\nThis project is a fork of React Starter Kit. `seed/main` is upstream; `main` is upstream plus this project's divergence. Syncing is a recurring operation.\n\n## Core rule\n\n**Upstream owns mechanism. This project owns identity and scope.**\n\nAdopt from upstream: dependency and toolchain versions, build and config structure, framework API updates, refactors, newly required bindings and variables.\n\nKeep local: names, domains, routes, worker and service identifiers; product copy and positioning; plans, features, scope; project-only integrations and config; deliberate additions and deletions.\n\nWhen a hunk mixes both, take upstream's structure and reapply local values. **Never take a whole side merely to clear a conflict** — whole-file resolution is valid only where a rule below makes that side authoritative. The repository defines what counts as local identity and scope: `AGENTS.md`, `docs/adr/`, `docs/product/`, and the current config on `main`.\n\n## Merge\n\n```bash\ngit status --short   # must be clean\n\ngit remote get-url seed || git remote add seed https://github.com/kriasoft/react-starter-kit.git\ngit fetch seed\n\nMB=$(git merge-base main seed/main)\ngit log --oneline \"$MB..seed/main\"   # read before resolving anything\n\ngit switch -c \"chore/merge-seed-$(git rev-parse --short seed/main)\" main\ngit -c merge.conflictStyle=zdiff3 merge --no-ff --no-commit --no-rerere-autoupdate seed/main\ngit diff --name-only --diff-filter=U   # unresolved conflicts\n```\n\n`--no-commit` stops git auto-committing a clean merge, so the commit and its subject are always ours to write. `--no-rerere-autoupdate` keeps every conflict in that list even when `rerere` has a recorded resolution to replay. If the branch already exists, an earlier sync was left unfinished — inspect it and resume or delete it deliberately, never force-reset over it.\n\n`rerere` writes its replayed resolution straight into the working tree, so a conflicted file can arrive already looking finished. Treat pre-filled content as unreviewed; `git rerere forget <path>` to redo it.\n\nIn ordinary text conflicts `zdiff3` includes the merge base, which is how to tell deliberate local divergence from a line upstream simply changed. Where there is no usable inline base — binary, add/add, modify/delete — read the index stages instead: `git ls-files -u <path>` lists which exist, then `git show :1:<path>` base, `:2:` ours, `:3:` theirs. Only the sides that still have content are present, so add/add has no base and a modify/delete has no stage for the side that deleted. For an ambiguous hunk, read the commit behind it: `git log -p \"$MB..seed/main\" -- <path>`.\n\nResolve, stage each resolved path, then commit with the subject in **Land**.\n\n## Resolution rules\n\n- **Infra, Wrangler, Terraform** — keep local names, hostnames, zones, routes, IDs, and values; adopt upstream compatibility dates, new bindings, and newly required variables, then fill in local values.\n- **Env files** — union: keep local values and project-only keys, add new upstream keys, and drop upstream-deleted keys only when the mechanism behind them is also gone.\n- **Manifests** — adopt upstream versions and upstream's own scripts; keep project-only dependencies and scripts still in use; drop local entries an upstream migration made obsolete.\n- **Lockfile** — never hand-merge: resolve every manifest first, take upstream's lockfile, then `bun install` and stage the regenerated result.\n- **Product copy — marketing pages, docs** — local copy and positioning are authoritative; port only technical changes.\n- **App and API code** — prefer upstream's refactor, then reapply local behavior on the new structure; a rewritten config or key list must still carry project-only keys.\n- **Migrations** — a migration that may have run is immutable. Never resolve changes to one in place: if upstream modified it, escalate; otherwise keep local migration history and express incoming schema changes as new migrations.\n- **ADRs** — local numbering is stable; renumber a colliding incoming ADR and fix its links.\n- **Local deletion vs upstream edit, and upstream renames** — deliberate deletions stay deleted; follow renames, carrying local content to the new path.\n\nDon't bring in upstream features that conflict with documented product scope.\n\n## Verify\n\n```bash\nbun install --frozen-lockfile && bun typecheck && bun lint && bun run test -- --run && bun run build\n```\n\n`--frozen-lockfile` fails if the manifests and lockfile disagree, which is what verification should prove. Green checks are still necessary, not sufficient — they cannot see lost identity or copy. Compare the result against each endpoint, per conflicted file and overall:\n\n```bash\ngit diff main..HEAD -- <path>        # upstream's mechanism, plus adaptations it forced\ngit diff seed/main..HEAD -- <path>   # the fork's divergence as it now stands\n```\n\nThese are endpoint comparisons, not per-side attribution: a local value may legitimately move or change shape in the first diff when upstream restructures around it, and the second carries the fork's whole history, not just this merge. What matters is that every difference is explainable — upstream mechanism adopted, or local intent kept. An unexplainable one is a bad resolution.\n\nThen confirm:\n\n- no leftover conflict markers: `git diff --check main..HEAD | grep 'conflict marker'` — they survive in files no build step reads, such as env and docs\n- no upstream branding, placeholder values, or template copy in changed files\n- no local config or key silently dropped by an upstream refactor\n- no stale paths or call sites left by an upstream rename\n- `AGENTS.md` invariants still hold\n- nothing unexpected in files that auto-merged (`git diff main..HEAD --stat`)\n\n## Land\n\nCommit subject `chore: merge seed up to <short-sha>`, notable resolutions in the body. Then, once verification passes:\n\n```bash\ngit switch main && git merge --ff-only <branch> && git branch -d <branch>\n```\n\nIf `--ff-only` is refused, `main` advanced during the merge: integrate the new `main` into the branch and re-verify before landing.\n\n## Escalate\n\nStop and report — file, upstream intent, local intent, the open choice — instead of guessing, when upstream modifies a migration that may have run or changes live-data semantics, ships a feature against documented product scope, or when preserving both mechanism and local behavior is genuinely ambiguous.\n",".agents/skills/shadcn/SKILL.md":"---\nname: shadcn\ndescription: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for \"shadcn init\", \"create an app with --preset\", or \"switch to --preset\".\nuser-invocable: false\nallowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)\n---\n\n# shadcn/ui\n\nA framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI.\n\n> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.\n\n## Current Project Context\n\n```json\n!`npx shadcn@latest info --json`\n```\n\nThe JSON above contains the project config and installed components. Use `npx shadcn@latest docs <component>` to get documentation and example URLs for any component.\n\n## Principles\n\n1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too.\n2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table.\n3. **Use built-in variants before custom styles.** `variant=\"outline\"`, `size=\"sm\"`, etc.\n4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`.\n\n## Critical Rules\n\nThese rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs.\n\n### Styling & Tailwind → [styling.md](./rules/styling.md)\n\n- **`className` for layout, not styling.** Never override component colors or typography.\n- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`.\n- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`.\n- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`.\n- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`).\n- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries.\n- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking.\n\n### Forms & Inputs → [forms.md](./rules/forms.md)\n\n- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.\n- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`.\n- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.**\n- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state.\n- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading.\n- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control.\n\n### Component Structure → [composition.md](./rules/composition.md)\n\n- **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`.\n- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md)\n- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className=\"sr-only\"` if visually hidden.\n- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`.\n- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`.\n- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`.\n- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load.\n\n### Use Components, Not Custom Markup → [composition.md](./rules/composition.md)\n\n- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.\n- **Callouts use `Alert`.** Don't build custom styled divs.\n- **Empty states use `Empty`.** Don't build custom empty state markup.\n- **Toast follows the project base.** Use `toast` from the `toast` component for Base UI projects. Use `toast()` from `sonner` for Radix and React Aria projects.\n- **Use `Separator`** instead of `<hr>` or `<div className=\"border-t\">`.\n- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.\n- **Use `Badge`** instead of custom styled spans.\n\n### Icons → [icons.md](./rules/icons.md)\n\n- **Icons in `Button` use `data-icon`.** `data-icon=\"inline-start\"` or `data-icon=\"inline-end\"` on the icon.\n- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`.\n- **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup.\n\n### Chat & Messaging → [chat.md](./rules/chat.md)\n\n- **Chat UI composes the chat primitives.** Conversations use `MessageScroller`, rows use `Message`, surfaces use `Bubble`. Never hand-rolled bubble `div`s or a raw scroll container.\n- **`MessageScroller` owns scroll behavior.** Streaming follow, anchoring, and jump-to-latest (`MessageScrollerButton`) are built in. Don't write a `useStickToBottom`/`ResizeObserver` hook.\n- **Attachments use `Attachment`; system notes and dividers use `Marker`.** Not `Item` cards or `Separator` + a label.\n\n### CLI\n\n- **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@latest preset decode <code>`, `preset url <code>`, or `preset open <code>`. For project-aware preset detection, use `npx shadcn@latest preset resolve`.\n- **Apply preset codes directly with the CLI.** Use `npx shadcn@latest apply <code>` for existing projects, or `npx shadcn@latest init --preset <code>` when initializing.\n\n## Key Patterns\n\nThese are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above.\n\n```tsx\n// Form layout: FieldGroup + Field, not div + Label.\n<FieldGroup>\n  <Field>\n    <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n    <Input id=\"email\" />\n  </Field>\n</FieldGroup>\n\n// Validation: data-invalid on Field, aria-invalid on the control.\n<Field data-invalid>\n  <FieldLabel>Email</FieldLabel>\n  <Input aria-invalid />\n  <FieldDescription>Invalid email.</FieldDescription>\n</Field>\n\n// Icons in buttons: data-icon, no sizing classes.\n<Button>\n  <SearchIcon data-icon=\"inline-start\" />\n  Search\n</Button>\n\n// Spacing: gap-*, not space-y-*.\n<div className=\"flex flex-col gap-4\">  // correct\n<div className=\"space-y-4\">           // wrong\n\n// Equal dimensions: size-*, not w-* h-*.\n<Avatar className=\"size-10\">   // correct\n<Avatar className=\"w-10 h-10\"> // wrong\n\n// Status colors: Badge variants or semantic tokens, not raw colors.\n<Badge variant=\"secondary\">+20.1%</Badge>    // correct\n<span className=\"text-emerald-600\">+20.1%</span> // wrong\n```\n\n## Component Selection\n\n| Need | Use |\n| --- | --- |\n| Button/action | `Button` with appropriate variant |\n| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` |\n| Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` |\n| Data display | `Table`, `Card`, `Badge`, `Avatar` |\n| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |\n| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |\n| Feedback | `toast` (Base UI), `sonner` (Radix/Aria), `Alert`, `Progress`, `Skeleton`, `Spinner` |\n| Command palette | `Command` inside `Dialog` |\n| Charts | `Chart` (wraps Recharts) |\n| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |\n| Empty states | `Empty` |\n| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` |\n| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` |\n| Chat / conversation UI | `MessageScroller`, `Message`, `Bubble`, `Attachment`, `Marker` |\n\n## Key Fields\n\nThe injected project context contains these key fields:\n\n- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode.\n- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `\"use client\"` at the top of the file. Always reference this field when advising on the directive.\n- **`tailwindVersion`** → `\"v4\"` uses `@theme inline` blocks; `\"v3\"` uses `tailwind.config.js`.\n- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one.\n- **`style`** → component visual treatment (e.g. `nova`, `vega`).\n- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props.\n- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`.\n- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc.\n- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA).\n- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`).\n- **`preset`** → resolved preset code and values for the current project. Use `npx shadcn@latest preset resolve --json` when you only need preset information.\n\nSee [cli.md — `info` command](./cli.md) for the full field reference.\n\n## Component Docs, Examples, and Usage\n\nRun `npx shadcn@latest docs <component>` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content.\n\n```bash\nnpx shadcn@latest docs button dialog select\n```\n\n**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing.\n\n## Workflow\n\n1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh.\n2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed.\n3. **Find components** — `npx shadcn@latest search`.\n4. **Get docs and examples** — run `npx shadcn@latest docs <component>` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`.\n5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below).\n6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.\n7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on.\n8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says \"add a login block\" without specifying `@shadcn`, `@tailark`, `owner/repo`, etc.), ask which registry to use. Never default to a registry on behalf of the user.\n9. **Switching presets** — Ask the user first: **overwrite**, **partial**, **merge**, or **skip**?\n   - **Inspect current preset**: `npx shadcn@latest preset resolve`. Use `--json` when you need structured values.\n   - **Inspect incoming preset**: `npx shadcn@latest preset decode <code>`. Use `preset url <code>` or `preset open <code>` to share or open the preset builder.\n   - **Overwrite**: `npx shadcn@latest apply <code>`. Overwrites detected components, fonts, and CSS variables.\n   - **Partial**: `npx shadcn@latest apply <code> --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms.\n   - **Merge**: `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually.\n   - **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.\n   - **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.\n\n## Updating Components\n\nWhen the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**\n\n1. Run `npx shadcn@latest add <component> --dry-run` to see all files that would be affected.\n2. For each file, run `npx shadcn@latest add <component> --diff <file>` to see what changed upstream vs local.\n3. Decide per file based on the diff:\n   - No local changes → safe to overwrite.\n   - Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications.\n   - User says \"just update everything\" → use `--overwrite`, but confirm first.\n4. **Never use `--overwrite` without the user's explicit approval.**\n\n## Quick Reference\n\n```bash\n# Create a new project.\nnpx shadcn@latest init --name my-app --preset base-nova\nnpx shadcn@latest init --name my-app --preset a2r6bw --template vite\n\n# Create a monorepo project.\nnpx shadcn@latest init --name my-app --preset base-nova --monorepo\nnpx shadcn@latest init --name my-app --preset base-nova --template next --monorepo\n\n# Initialize existing project.\nnpx shadcn@latest init --preset base-nova\nnpx shadcn@latest init --defaults  # shortcut: --template=next --preset=nova (base style implied)\n\n# Apply a preset to an existing project.\nnpx shadcn@latest apply a2r6bw\nnpx shadcn@latest apply a2r6bw --only theme\nnpx shadcn@latest apply a2r6bw --only font\nnpx shadcn@latest apply a2r6bw --only theme,font\n\n# Inspect preset codes and project preset state.\nnpx shadcn@latest preset decode a2r6bw\nnpx shadcn@latest preset url a2r6bw\nnpx shadcn@latest preset open a2r6bw\nnpx shadcn@latest preset resolve\nnpx shadcn@latest preset resolve --json\n\n# Add components.\nnpx shadcn@latest add button card dialog\nnpx shadcn@latest add @magicui/shimmer-button\nnpx shadcn@latest add owner/repo/item\nnpx shadcn@latest add --all\n\n# Preview changes before adding/updating.\nnpx shadcn@latest add button --dry-run\nnpx shadcn@latest add button --diff button.tsx\nnpx shadcn@latest add @acme/form --view button.tsx\nnpx shadcn@latest add owner/repo/item --dry-run\n\n# Search registries.\nnpx shadcn@latest search @shadcn -q \"sidebar\"\nnpx shadcn@latest search @tailark -q \"stats\"\nnpx shadcn@latest search owner/repo -q \"login\"\nnpx shadcn@latest search                          # all configured registries\nnpx shadcn@latest search @shadcn -q \"menu\" -t ui  # filter by item type\n\n# Get component docs and example URLs.\nnpx shadcn@latest docs button dialog select\n\n# View registry item details (for items not yet installed).\nnpx shadcn@latest view @shadcn/button\nnpx shadcn@latest view owner/repo/item\n```\n\n**Named presets:** `nova`, `vega`, `maia`, `lyra`, `mira`, `luma` **Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo) **Preset codes:** Version-prefixed base62 strings (e.g. `a2r6bw` or `b0`), from [ui.shadcn.com](https://ui.shadcn.com).\n\n## Detailed References\n\n- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states\n- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading\n- [rules/chat.md](./rules/chat.md) — MessageScroller, Message, Bubble, Attachment, Marker; streaming, anchoring, jump-to-latest\n- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects\n- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index\n- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion\n- [cli.md](./cli.md) — Commands, flags, presets, templates\n- [registry.md](./registry.md) — Authoring source registries, `include`, item definitions, dependencies, GitHub registry rules\n- [customization.md](./customization.md) — Theming, CSS variables, extending components\n",".agents/skills/shadcn/cli.md":"# shadcn CLI Reference\n\nConfiguration is read from `components.json`.\n\n> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.\n\n> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag.\n\n## Contents\n\n- Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build\n- Templates: next, vite, start, react-router, astro\n- Presets: named, code, URL formats and fields\n- Switching presets\n\n---\n\n## Commands\n\n### `init` — Initialize or create a project\n\n```bash\nnpx shadcn@latest init [components...] [options]\n```\n\nInitializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--template <template>` | `-t` | Template (next, start, vite, next-monorepo, react-router) | — |\n| `--preset [name]` | `-p` | Preset configuration (named, code, or URL) | — |\n| `--yes` | `-y` | Skip confirmation prompt | `true` |\n| `--defaults` | `-d` | Use defaults (`--template=next --preset=base-nova`) | `false` |\n| `--force` | `-f` | Force overwrite existing configuration | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n| `--name <name>` | `-n` | Name for new project | — |\n| `--silent` | `-s` | Mute output | `false` |\n| `--rtl` |  | Enable RTL support | — |\n| `--reinstall` |  | Re-install existing UI components | `false` |\n| `--monorepo` |  | Scaffold a monorepo project | — |\n| `--no-monorepo` |  | Skip the monorepo prompt | — |\n\n`npx shadcn@latest create` is an alias for `npx shadcn@latest init`.\n\n### `apply` — Apply a preset to an existing project\n\n```bash\nnpx shadcn@latest apply [preset] [options]\n```\n\nApplies a preset to an existing project, overwriting preset-driven config, fonts, CSS variables, and detected UI components.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--preset <preset>` | — | Preset configuration (named, code, or URL) | — |\n| `--yes` | `-y` | Skip confirmation prompt | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n| `--silent` | `-s` | Mute output | `false` |\n\n`[preset]` is a shorthand for `--preset <preset>`. If both are provided, they must match. If no preset is provided, the CLI offers to open the custom preset builder on `ui.shadcn.com/create`.\n\n### `add` — Add components\n\n> **IMPORTANT:** To compare local components against upstream or to preview changes, ALWAYS use `npx shadcn@latest add <component> --dry-run`, `--diff`, or `--view`. NEVER fetch raw files from GitHub or other sources manually. The CLI handles registry resolution, file paths, and CSS diffing automatically.\n\n```bash\nnpx shadcn@latest add [components...] [options]\n```\n\nAccepts component names, registry-prefixed names (`@magicui/shimmer-button`), GitHub item addresses (`owner/repo/item`), URLs, or local paths.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--yes` | `-y` | Skip confirmation prompt | `false` |\n| `--overwrite` | `-o` | Overwrite existing files | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n| `--all` | `-a` | Add all available components | `false` |\n| `--path <path>` | `-p` | Target path for the component | — |\n| `--silent` | `-s` | Mute output | `false` |\n| `--dry-run` |  | Preview all changes without writing files | `false` |\n| `--diff [path]` |  | Show diffs. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |\n| `--view [path]` |  | Show file contents. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |\n\n#### Dry-Run Mode\n\nUse `--dry-run` to preview what `add` would do without writing any files. `--diff` and `--view` both imply `--dry-run`.\n\n```bash\n# Preview all changes.\nnpx shadcn@latest add button --dry-run\n\n# Show diffs for all files (top 5).\nnpx shadcn@latest add button --diff\n\n# Show the diff for a specific file.\nnpx shadcn@latest add button --diff button.tsx\n\n# Show contents for all files (top 5).\nnpx shadcn@latest add button --view\n\n# Show the full content of a specific file.\nnpx shadcn@latest add button --view button.tsx\n\n# Works with URLs too.\nnpx shadcn@latest add https://api.npoint.io/abc123 --dry-run\n\n# Works with public GitHub registries too.\nnpx shadcn@latest add owner/repo/item --dry-run\n\n# CSS diffs.\nnpx shadcn@latest add button --diff globals.css\n```\n\n**When to use dry-run:**\n\n- When the user asks \"what files will this add?\" or \"what will this change?\" — use `--dry-run`.\n- Before overwriting existing components — use `--diff` to preview the changes first.\n- When the user wants to inspect component source code without installing — use `--view`.\n- When checking what CSS changes would be made to `globals.css` — use `--diff globals.css`.\n- When the user asks to review or audit third-party registry code before installing — use `--view` to inspect the source.\n\n> **`npx shadcn@latest add --dry-run` vs `npx shadcn@latest view`:** Prefer `npx shadcn@latest add --dry-run/--diff/--view` over `npx shadcn@latest view` when the user wants to preview changes to their project. `npx shadcn@latest view` only shows raw registry metadata. `npx shadcn@latest add --dry-run` shows exactly what would happen in the user's project: resolved file paths, diffs against existing files, and CSS updates. Use `npx shadcn@latest view` only when the user wants to browse registry info without a project context.\n\n#### Smart Merge from Upstream\n\nSee [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full workflow.\n\n### `search` — Search registries\n\n```bash\nnpx shadcn@latest search [registries...] [options]\n```\n\nFuzzy search across registries. Also aliased as `npx shadcn@latest list`. Supports namespaces (`@acme`), public GitHub registry sources (`owner/repo`), and registry catalog URLs. Without `-q`, lists all items. When no registries are passed, searches every registry configured in `components.json`.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--query <query>` | `-q` | Search query | — |\n| `--type <type>` | `-t` | Filter by item type (e.g. `ui`, `block`, `hook`); comma-separated | — |\n| `--limit <number>` | `-l` | Max items to display | `100` |\n| `--offset <number>` | `-o` | Items to skip | `0` |\n| `--json` |  | Output as JSON | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n\n### `view` — View item details\n\n```bash\nnpx shadcn@latest view <items...> [options]\n```\n\nDisplays item info including file contents. Examples: `npx shadcn@latest view @shadcn/button`, `npx shadcn@latest view owner/repo/item`.\n\n### `docs` — Get component documentation URLs\n\n```bash\nnpx shadcn@latest docs <components...> [options]\n```\n\nOutputs resolved URLs for component documentation, examples, and API references. Accepts one or more component names. Fetch the URLs to get the actual content.\n\nExample output for `npx shadcn@latest docs input button`:\n\n```\nbase  radix\n\ninput\n  docs      https://ui.shadcn.com/docs/components/radix/input\n  examples  https://raw.githubusercontent.com/.../examples/input-example.tsx\n\nbutton\n  docs      https://ui.shadcn.com/docs/components/radix/button\n  examples  https://raw.githubusercontent.com/.../examples/button-example.tsx\n```\n\nSome components include an `api` link to the underlying library (e.g. `cmdk` for the command component).\n\n### `diff` — Check for updates\n\nDo not use this command. Use `npx shadcn@latest add --diff` instead.\n\n### `info` — Project information\n\n```bash\nnpx shadcn@latest info [options]\n```\n\nDisplays project info and `components.json` configuration. Run this first to discover the project's framework, aliases, Tailwind version, and resolved paths.\n\n| Flag          | Short | Description       | Default |\n| ------------- | ----- | ----------------- | ------- |\n| `--cwd <cwd>` | `-c`  | Working directory | current |\n\n**Project Info fields:**\n\n| Field | Type | Meaning |\n| --- | --- | --- |\n| `framework` | `string` | Detected framework (`next`, `vite`, `react-router`, `start`, etc.) |\n| `frameworkVersion` | `string` | Framework version (e.g. `15.2.4`) |\n| `isSrcDir` | `boolean` | Whether the project uses a `src/` directory |\n| `isRSC` | `boolean` | Whether React Server Components are enabled |\n| `isTsx` | `boolean` | Whether the project uses TypeScript |\n| `tailwindVersion` | `string` | `\"v3\"` or `\"v4\"` |\n| `tailwindConfigFile` | `string` | Path to the Tailwind config file |\n| `tailwindCssFile` | `string` | Path to the global CSS file |\n| `aliasPrefix` | `string` | Import alias prefix (e.g. `@`, `~`, `@/`) |\n| `packageManager` | `string` | Detected package manager (`npm`, `pnpm`, `yarn`, `bun`) |\n\n**Components.json fields:**\n\n| Field | Type | Meaning |\n| --- | --- | --- |\n| `base` | `string` | Primitive library (`radix` or `base`) — determines component APIs and available props |\n| `style` | `string` | Visual style (e.g. `nova`, `vega`) |\n| `rsc` | `boolean` | RSC flag from config |\n| `tsx` | `boolean` | TypeScript flag |\n| `tailwind.config` | `string` | Tailwind config path |\n| `tailwind.css` | `string` | Global CSS path — this is where custom CSS variables go |\n| `iconLibrary` | `string` | Icon library — determines icon import package (e.g. `lucide-react`, `@tabler/icons-react`) |\n| `aliases.components` | `string` | Component import alias (e.g. `@/components`) |\n| `aliases.utils` | `string` | Utils import alias (e.g. `@/lib/utils`) |\n| `aliases.ui` | `string` | UI component alias (e.g. `@/components/ui`) |\n| `aliases.lib` | `string` | Lib alias (e.g. `@/lib`) |\n| `aliases.hooks` | `string` | Hooks alias (e.g. `@/hooks`) |\n| `resolvedPaths` | `object` | Absolute file-system paths for each alias |\n| `registries` | `object` | Configured custom registries |\n\n**Links fields:**\n\nThe `info` output includes a **Links** section with templated URLs for component docs, source, and examples. For resolved URLs, use `npx shadcn@latest docs <component>` instead.\n\n### `build` — Build a custom registry\n\n```bash\nnpx shadcn@latest build [registry] [options]\n```\n\nBuilds `registry.json` into individual JSON files for distribution. Default input: `./registry.json`, default output: `./public/r`.\n\nFor authoring rules, `include`, item definitions, `registryDependencies`, and GitHub registry behavior, see [registry.md](./registry.md).\n\n| Flag              | Short | Description       | Default      |\n| ----------------- | ----- | ----------------- | ------------ |\n| `--output <path>` | `-o`  | Output directory  | `./public/r` |\n| `--cwd <cwd>`     | `-c`  | Working directory | current      |\n\n---\n\n## Templates\n\n| Value          | Framework      | Monorepo support |\n| -------------- | -------------- | ---------------- |\n| `next`         | Next.js        | Yes              |\n| `vite`         | Vite           | Yes              |\n| `start`        | TanStack Start | Yes              |\n| `react-router` | React Router   | Yes              |\n| `astro`        | Astro          | Yes              |\n| `laravel`      | Laravel        | No               |\n\nAll templates support monorepo scaffolding via the `--monorepo` flag. When passed, the CLI uses a monorepo-specific template directory (e.g. `next-monorepo`, `vite-monorepo`). When neither `--monorepo` nor `--no-monorepo` is passed, the CLI prompts interactively. Laravel does not support monorepo scaffolding.\n\n---\n\n## Presets\n\nThree ways to specify a preset via `--preset`:\n\n1. **Named:** `--preset nova` or `--preset lyra`\n2. **Code:** `--preset a2r6bw` (version-prefixed base62 string, e.g. `a2r6bw` or `b0`)\n3. **URL:** `--preset \"https://ui.shadcn.com/init?base=radix&style=nova&...\"`\n\n> **IMPORTANT:** Never try to decode, fetch, or resolve preset codes manually. Preset codes are opaque — pass them directly to `npx shadcn@latest init --preset <code>` and let the CLI handle resolution. Use `npx shadcn@latest apply --preset <code>` when overwriting an existing project's preset.\n\n## Switching Presets\n\nAsk the user first: **overwrite**, **merge**, or **skip** existing components?\n\n- **Overwrite / Re-install** → `npx shadcn@latest apply --preset <code>`. Overwrites all detected component files with the new preset styles. Use when the user hasn't customized components.\n- **Merge** → `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to get the list of installed components and use the [smart merge workflow](./SKILL.md#updating-components) to update them one by one, preserving local changes. Use when the user has customized components.\n- **Skip** → `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS variables, leaves existing components as-is.\n\nAlways run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.\n",".agents/skills/shadcn/customization.md":"# Customization & Theming\n\nComponents reference semantic CSS variable tokens. Change the variables to change every component.\n\n## Contents\n\n- How it works (CSS variables → Tailwind utilities → components)\n- Color variables and OKLCH format\n- Dark mode setup\n- Changing the theme (presets, CSS variables)\n- Adding custom colors (Tailwind v3 and v4)\n- Border radius\n- Customizing components (variants, className, wrappers)\n- Checking for updates\n\n---\n\n## How It Works\n\n1. CSS variables defined in `:root` (light) and `.dark` (dark mode).\n2. Tailwind maps them to utilities: `bg-primary`, `text-muted-foreground`, etc.\n3. Components use these utilities — changing a variable changes all components that reference it.\n\n---\n\n## Color Variables\n\nEvery color follows the `name` / `name-foreground` convention. The base variable is for backgrounds, `-foreground` is for text/icons on that background.\n\n| Variable | Purpose |\n| --- | --- |\n| `--background` / `--foreground` | Page background and default text |\n| `--card` / `--card-foreground` | Card surfaces |\n| `--primary` / `--primary-foreground` | Primary buttons and actions |\n| `--secondary` / `--secondary-foreground` | Secondary actions |\n| `--muted` / `--muted-foreground` | Muted/disabled states |\n| `--accent` / `--accent-foreground` | Hover and accent states |\n| `--destructive` / `--destructive-foreground` | Error and destructive actions |\n| `--border` | Default border color |\n| `--input` | Form input borders |\n| `--ring` | Focus ring color |\n| `--chart-1` through `--chart-5` | Chart/data visualization |\n| `--sidebar-*` | Sidebar-specific colors |\n| `--surface` / `--surface-foreground` | Secondary surface |\n\nColors use OKLCH: `--primary: oklch(0.205 0 0)` where values are lightness (0–1), chroma (0 = gray), and hue (0–360).\n\n---\n\n## Dark Mode\n\nClass-based toggle via `.dark` on the root element. In Next.js, use `next-themes`:\n\n```tsx\nimport { ThemeProvider } from \"next-themes\";\n\n<ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n  {children}\n</ThemeProvider>;\n```\n\n---\n\n## Changing the Theme\n\n```bash\n# Apply a preset code from ui.shadcn.com.\nnpx shadcn@latest apply --preset a2r6bw\n\n# Positional shorthand also works.\nnpx shadcn@latest apply a2r6bw\n\n# Switch to a named preset and overwrite existing components.\nnpx shadcn@latest apply --preset nova\n\n# Preserve existing components instead.\nnpx shadcn@latest init --preset nova --force --no-reinstall\n\n# Use a custom theme URL.\nnpx shadcn@latest apply --preset \"https://ui.shadcn.com/init?base=radix&style=nova&theme=blue&...\"\n```\n\nOr edit CSS variables directly in `globals.css`.\n\n---\n\n## Adding Custom Colors\n\nAdd variables to the file at `tailwindCssFile` from `npx shadcn@latest info` (typically `globals.css`). Never create a new CSS file for this.\n\n```css\n/* 1. Define in the global CSS file. */\n:root {\n  --warning: oklch(0.84 0.16 84);\n  --warning-foreground: oklch(0.28 0.07 46);\n}\n.dark {\n  --warning: oklch(0.41 0.11 46);\n  --warning-foreground: oklch(0.99 0.02 95);\n}\n```\n\n```css\n/* 2a. Register with Tailwind v4 (@theme inline). */\n@theme inline {\n  --color-warning: var(--warning);\n  --color-warning-foreground: var(--warning-foreground);\n}\n```\n\nWhen `tailwindVersion` is `\"v3\"` (check via `npx shadcn@latest info`), register in `tailwind.config.js` instead:\n\n```js\n// 2b. Register with Tailwind v3 (tailwind.config.js).\nmodule.exports = {\n  theme: {\n    extend: {\n      colors: {\n        warning: \"oklch(var(--warning) / <alpha-value>)\",\n        \"warning-foreground\":\n          \"oklch(var(--warning-foreground) / <alpha-value>)\",\n      },\n    },\n  },\n};\n```\n\n```tsx\n// 3. Use in components.\n<div className=\"bg-warning text-warning-foreground\">Warning</div>\n```\n\n---\n\n## Border Radius\n\n`--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`).\n\n---\n\n## Customizing Components\n\nSee also: [rules/styling.md](./rules/styling.md) for Incorrect/Correct examples.\n\nPrefer these approaches in order:\n\n### 1. Built-in variants\n\n```tsx\n<Button variant=\"outline\" size=\"sm\">\n  Click\n</Button>\n```\n\n### 2. Tailwind classes via `className`\n\n```tsx\n<Card className=\"mx-auto max-w-md\">...</Card>\n```\n\n### 3. Add a new variant\n\nEdit the component source to add a variant via `cva`:\n\n```tsx\n// components/ui/button.tsx\nwarning: \"bg-warning text-warning-foreground hover:bg-warning/90\",\n```\n\n### 4. Wrapper components\n\nCompose shadcn/ui primitives into higher-level components:\n\n```tsx\nexport function ConfirmDialog({ title, description, onConfirm, children }) {\n  return (\n    <AlertDialog>\n      <AlertDialogTrigger asChild>{children}</AlertDialogTrigger>\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogTitle>{title}</AlertDialogTitle>\n          <AlertDialogDescription>{description}</AlertDialogDescription>\n        </AlertDialogHeader>\n        <AlertDialogFooter>\n          <AlertDialogCancel>Cancel</AlertDialogCancel>\n          <AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n  );\n}\n```\n\n---\n\n## Checking for Updates\n\n```bash\nnpx shadcn@latest add button --diff\n```\n\nTo preview exactly what would change before updating, use `--dry-run` and `--diff`:\n\n```bash\nnpx shadcn@latest add button --dry-run        # see all affected files\nnpx shadcn@latest add button --diff button.tsx # see the diff for a specific file\n```\n\nSee [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full smart merge workflow.\n",".agents/skills/shadcn/evals/evals.json":"{\n  \"skill_name\": \"shadcn\",\n  \"evals\": [\n    {\n      \"id\": 1,\n      \"prompt\": \"I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.\",\n      \"expected_output\": \"A React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses FieldGroup and Field components for form layout instead of raw div with space-y\",\n        \"Uses Switch for independent on/off notification toggles (not looping Button with manual active state)\",\n        \"Uses data-invalid on Field and aria-invalid on the input control for validation states\",\n        \"Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing\",\n        \"Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500\",\n        \"No manual dark: color overrides\"\n      ]\n    },\n    {\n      \"id\": 2,\n      \"prompt\": \"Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn/ui with radix-nova preset and tabler icons.\",\n      \"expected_output\": \"A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Includes DialogTitle for accessibility (visible or with sr-only class)\",\n        \"Avatar component includes AvatarFallback\",\n        \"Icons on buttons use the data-icon attribute (data-icon=\\\"inline-start\\\" or data-icon=\\\"inline-end\\\")\",\n        \"No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)\",\n        \"Uses tabler icons (@tabler/icons-react) instead of lucide-react\",\n        \"Uses asChild for custom triggers (radix preset)\"\n      ]\n    },\n    {\n      \"id\": 3,\n      \"prompt\": \"Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn/ui with base-nova preset and lucide icons.\",\n      \"expected_output\": \"A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)\",\n        \"Uses Skeleton component for loading placeholders instead of custom animate-pulse divs\",\n        \"Uses Badge component for percentage change instead of custom styled spans\",\n        \"Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600\",\n        \"Uses gap-* instead of space-y-* or space-x-* for spacing\",\n        \"Uses size-* when width and height are equal instead of separate w-* h-*\"\n      ]\n    },\n    {\n      \"id\": 4,\n      \"prompt\": \"I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Build a chat conversation view: a scrollable thread of messages from two different people, each with an avatar, sender name, timestamp, and message bubble. A couple of messages include an image attachment and a PDF file attachment, and there's a 'Today' divider separating the days.\",\n      \"expected_output\": \"A React component composing MessageScroller, Message, Bubble, Attachment, and Marker from the registry instead of hand-rolled bubble/divider/attachment markup.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses MessageScroller (MessageScrollerProvider, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem) for the scrollable thread instead of a raw overflow-y-auto div or ScrollArea\",\n        \"Wraps each row in MessageScrollerItem inside MessageScrollerContent\",\n        \"Uses Message with MessageAvatar/MessageContent/MessageHeader for row layout instead of custom flex divs\",\n        \"Uses Bubble + BubbleContent for the message surface instead of a styled div with bg-muted/bg-primary\",\n        \"Uses Attachment (AttachmentMedia, AttachmentContent, AttachmentTitle, AttachmentDescription) for the file and image attachments instead of Item or a custom card\",\n        \"Uses Marker (variant=\\\"separator\\\") for the 'Today' divider instead of Separator plus a centered label\",\n        \"Uses semantic color tokens and gap-* spacing; no raw colors like bg-emerald-500 and no space-y-*\",\n        \"Includes \\\"use client\\\" when the component uses state or event handlers (isRSC)\"\n      ]\n    },\n    {\n      \"id\": 5,\n      \"prompt\": \"Using shadcn/ui (base-nova preset, lucide icons), build a streaming AI chat UI. The assistant's reply streams in while it generates, the view auto-scrolls to follow the latest content but stops following if the user scrolls up to read earlier messages, a 'jump to latest' button appears when the user has scrolled away from the bottom, and a subtle 'thinking…' shimmer shows while the model is generating.\",\n      \"expected_output\": \"A React component that delegates scroll/anchor behavior to MessageScroller and uses MessageScrollerButton for jump-to-latest and the shimmer utility for the thinking indicator — no hand-rolled scroll logic or custom shimmer keyframes.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses MessageScroller with MessageScrollerProvider (autoScroll) and scrollAnchor on message items for the stick-to-bottom/follow behavior instead of a custom useStickToBottom hook or ResizeObserver/scrollTop wiring\",\n        \"Uses MessageScrollerButton for the jump-to-latest control instead of a hand-built conditional button driven by manual scroll-position state\",\n        \"Uses the shimmer utility class for the 'thinking…' indicator instead of a custom @keyframes or bg-clip-text gradient animation\",\n        \"Wraps each message row in MessageScrollerItem inside MessageScrollerContent\",\n        \"Uses Message + Bubble + BubbleContent for the conversation rows instead of hand-rolled bubble divs\",\n        \"Uses semantic color tokens and gap-* spacing; includes \\\"use client\\\" (isRSC)\"\n      ]\n    }\n  ]\n}\n",".agents/skills/shadcn/mcp.md":"# shadcn MCP Server\n\nThe CLI includes an MCP server that lets AI assistants search, browse, view, and install items from registries.\n\n---\n\n## Setup\n\n```bash\nshadcn mcp        # start the MCP server (stdio)\nshadcn mcp init   # write config for your editor\n```\n\nEditor config files:\n\n| Editor      | Config file                     |\n| ----------- | ------------------------------- |\n| Claude Code | `.mcp.json`                     |\n| Cursor      | `.cursor/mcp.json`              |\n| VS Code     | `.vscode/mcp.json`              |\n| OpenCode    | `opencode.json`                 |\n| Codex       | `~/.codex/config.toml` (manual) |\n\n---\n\n## Tools\n\n> **Tip:** MCP tools handle registry operations (search, view, install). For project configuration (aliases, framework, Tailwind version), use `npx shadcn@latest info` — there is no MCP equivalent.\n\n### `shadcn:get_project_registries`\n\nReturns registry names from `components.json`. Errors if no `components.json` exists.\n\n**Input:** none\n\n### `shadcn:list_items_in_registries`\n\nLists all items from one or more registries. Registries can be configured namespaces such as `@acme`, public GitHub sources such as `owner/repo`, or registry catalog URLs. Omit `registries` to list from every registry configured in `components.json`.\n\n**Input:** `registries` (string[], optional — omit for all configured), `types` (string[], optional — e.g. `[\"ui\", \"block\"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)\n\n### `shadcn:search_items_in_registries`\n\nFuzzy search across registries. Registries can be configured namespaces, public GitHub sources, or registry catalog URLs. Omit `registries` to search every registry configured in `components.json` — e.g. \"find me a hero\" across all configured registries.\n\n**Input:** `registries` (string[], optional — omit for all configured), `query` (string), `types` (string[], optional — e.g. `[\"ui\", \"block\"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)\n\n### `shadcn:view_items_in_registries`\n\nView item details including full file contents.\n\n**Input:** `items` (string[]) — e.g. `[\"@shadcn/button\", \"@shadcn/card\", \"owner/repo/item\"]`\n\n### `shadcn:get_item_examples_from_registries`\n\nFind usage examples and demos with source code. Omit `registries` to search every registry configured in `components.json`.\n\n**Input:** `registries` (string[], optional — omit for all configured), `query` (string) — e.g. `\"accordion-demo\"`, `\"button example\"`\n\n### `shadcn:get_add_command_for_items`\n\nReturns the CLI install command.\n\n**Input:** `items` (string[]) — e.g. `[\"@shadcn/button\"]`\n\n### `shadcn:get_audit_checklist`\n\nReturns a checklist for verifying components (imports, deps, lint, TypeScript).\n\n**Input:** none\n\n---\n\n## Configuring Registries\n\nNamespaced and authenticated registries are set in `components.json`. The `@shadcn` registry is always built-in. Public GitHub registries can also be used directly as `owner/repo` registry sources when the repository has a root `registry.json`; they do not need `components.json` configuration.\n\n```json\n{\n  \"registries\": {\n    \"@acme\": \"https://acme.com/r/{name}.json\",\n    \"@private\": {\n      \"url\": \"https://private.com/r/{name}.json\",\n      \"headers\": { \"Authorization\": \"Bearer ${MY_TOKEN}\" }\n    }\n  }\n}\n```\n\n- Names must start with `@`.\n- URLs must contain `{name}`.\n- `${VAR}` references are resolved from environment variables.\n\nCommunity registry index: `https://ui.shadcn.com/r/registries.json`\n",".agents/skills/shadcn/registry.md":"# Registry Authoring and Addresses\n\nUse this reference when the user wants to create, fix, publish, or reason about a shadcn registry.\n\n## Mental Model\n\nA registry has two forms:\n\n- **Source registry**: an authored `registry.json` in a project or repository. It may use `include` and file paths that point at source files.\n- **Built registry**: generated JSON files served to CLI consumers, usually from `public/r`. Use `npx shadcn@latest build` to create this form.\n\nThe CLI installer consumes registry item payloads. A source registry is a way to author those payloads from real files.\n\nRegistry items are not limited to React components. They can distribute components, hooks, utilities, design tokens, pages, config files, docs, rules, workflows, templates, MCP files, and other project files.\n\n## Root `registry.json`\n\nThe root registry file should define registry metadata and either `items` or `include`.\n\n```json\n{\n  \"$schema\": \"https://ui.shadcn.com/schema/registry.json\",\n  \"name\": \"acme\",\n  \"homepage\": \"https://acme.com\",\n  \"items\": [\n    {\n      \"name\": \"absolute-url\",\n      \"type\": \"registry:lib\",\n      \"title\": \"Absolute URL\",\n      \"description\": \"A utility to turn any path into an absolute URL.\",\n      \"files\": [\n        {\n          \"path\": \"lib/absolute-url.ts\",\n          \"type\": \"registry:lib\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nRoot registry rules:\n\n- Root `registry.json` must include `name` and `homepage`.\n- `items` is an array of registry item definitions.\n- `include` may be used to split the source registry into multiple files.\n- Included registry files may omit `name` and `homepage`.\n\n## Include\n\nUse `include` to keep large registries modular.\n\n```json\n{\n  \"$schema\": \"https://ui.shadcn.com/schema/registry.json\",\n  \"name\": \"acme\",\n  \"homepage\": \"https://acme.com\",\n  \"include\": [\"registry/ui/registry.json\", \"registry/blocks/registry.json\"]\n}\n```\n\nInclude rules:\n\n- Include paths are relative to the `registry.json` that declares them.\n- Include paths must explicitly point to a `registry.json` file.\n- Do not use remote URLs, absolute paths, or parent traversal (`..`).\n- Item file paths are relative to the registry file that declares the item.\n- Duplicate item names fail across the resolved registry.\n\nExample included file:\n\n```json\n{\n  \"items\": [\n    {\n      \"name\": \"button\",\n      \"type\": \"registry:ui\",\n      \"files\": [\n        {\n          \"path\": \"button.tsx\",\n          \"type\": \"registry:ui\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nIf this file is at `registry/ui/registry.json`, then `button.tsx` is read from `registry/ui/button.tsx`, and the built item path is emitted relative to the root registry.\n\n## Item Definitions\n\nCommon item fields:\n\n```json\n{\n  \"name\": \"login-form\",\n  \"type\": \"registry:block\",\n  \"title\": \"Login Form\",\n  \"description\": \"A login form with email and password fields.\",\n  \"dependencies\": [\"zod\"],\n  \"registryDependencies\": [\"button\", \"input\", \"label\"],\n  \"files\": [\n    {\n      \"path\": \"blocks/login-form.tsx\",\n      \"type\": \"registry:block\"\n    }\n  ],\n  \"cssVars\": {\n    \"light\": {\n      \"brand\": \"oklch(0.62 0.18 250)\"\n    },\n    \"dark\": {\n      \"brand\": \"oklch(0.72 0.16 250)\"\n    }\n  }\n}\n```\n\nImportant fields:\n\n- `name`: the installable item name. It is not necessarily a file path.\n- `type`: one of the registry item types, such as `registry:ui`, `registry:block`, `registry:lib`, `registry:hook`, `registry:file`, `registry:page`, `registry:theme`, `registry:style`, `registry:font`, or `registry:item`.\n- `files`: source files copied or generated by the item.\n- `dependencies`: npm runtime dependencies.\n- `devDependencies`: npm development dependencies.\n- `registryDependencies`: other registry items required by this item.\n- `cssVars`, `css`, `tailwind`, `envVars`, and `docs`: optional install-time additions.\n\nFile rules:\n\n- File paths are relative to the declaring `registry.json`.\n- `registry:file` and `registry:page` files require a `target`.\n- Do not use remote file URLs in source registry file paths.\n- Keep source files copy-pasteable: no hidden app-only imports.\n\n## Registry Dependencies\n\n`registryDependencies` entries are item addresses, not file paths.\n\n```json\n{\n  \"name\": \"login-form\",\n  \"type\": \"registry:block\",\n  \"registryDependencies\": [\"button\", \"@acme/input\", \"acme/ui/card#v1.2.0\"],\n  \"files\": [\n    {\n      \"path\": \"blocks/login-form.tsx\",\n      \"type\": \"registry:block\"\n    }\n  ]\n}\n```\n\nDependency rules:\n\n- Bare names such as `\"button\"` mean official shadcn items.\n- Bare names never mean same-registry or same-repository items.\n- Namespaced dependencies use `@namespace/item-name`.\n- GitHub dependencies use `owner/repo/item-name`.\n- Pin GitHub dependencies with `owner/repo/item-name#ref` when needed.\n- Refs are not inherited. If `owner/repo/foo#v2` depends on `bar` from the same repo at `v2`, write `owner/repo/bar#v2`.\n- Do not use relative dependencies such as `\"./bar\"`.\n\n## Address Schemes\n\nWhen reasoning about a registry item string, classify it first.\n\n| Address | Scheme | Meaning |\n| --- | --- | --- |\n| `button` | shadcn | Official shadcn item named `button`. |\n| `@acme/button` | namespace | Item `button` from configured registry `@acme`. |\n| `@acme/ui/button` | namespace | Item `ui/button` from configured registry `@acme`. |\n| `https://example.com/r/button.json` | url | Built registry item JSON at that URL. |\n| `./button.json` | file | Built registry item JSON on disk. |\n| `acme/ui/button` | github | Item `button` from GitHub repo `acme/ui`. |\n| `acme/ui/forms/login#main` | github | Item `forms/login` from GitHub repo `acme/ui` at ref `main`. |\n\nFor namespace and GitHub addresses, slashful item names are allowed and are item names, not file paths. Addresses ending in `.json` keep file-address precedence, so `acme/ui/data/schema.json` is treated as a file path, not a GitHub item address.\n\n## GitHub Registries\n\nA public GitHub repository can act as a source registry when it has a root `registry.json`.\n\n```txt\nowner/repo/item-name[#ref]\n```\n\nRules:\n\n- The first two path segments are GitHub owner and repo.\n- All remaining path segments are the registry item name.\n- The source entrypoint is always root `registry.json`.\n- GitHub registries are source registries consumed directly by the CLI. They do not require `shadcn build` or generated item JSON files.\n- `include` follows the same source-registry rules as local registries.\n- Currently, GitHub addresses support public `github.com` repositories only.\n- Private repos and GitHub Enterprise require explicit product decisions.\n\nWhen implementing GitHub registry fetching, resolve refs to a commit SHA before reading source files. Do not read moving refs directly from `raw.githubusercontent.com`, because branch-like refs can be cached for several minutes.\n\nPreferred flow:\n\n```txt\nowner/repo[#ref]\n  -> resolve ref with git ls-remote\n  -> commit SHA\n  -> read https://raw.githubusercontent.com/{owner}/{repo}/{sha}/registry.json\n  -> read includes and item files from the same SHA\n```\n\nThis keeps a command on one consistent repository snapshot.\n\nFull 40-character commit SHAs are already stable and can be used directly. Branches, tags, and short refs require Git so the CLI can resolve them to a commit SHA first.\n\n## Build and Verify\n\nUse the CLI to build source registries:\n\n```bash\nnpx shadcn@latest build\nnpx shadcn@latest build registry.json --output public/r\n```\n\nUse CLI commands to inspect the result:\n\n```bash\nnpx shadcn@latest list @acme\nnpx shadcn@latest search @acme -q \"login\"\nnpx shadcn@latest view @acme/login-form\nnpx shadcn@latest add @acme/login-form --dry-run\nnpx shadcn@latest registry validate ./registry.json\n```\n\nUse GitHub addresses directly for public GitHub registries:\n\n```bash\nnpx shadcn@latest list owner/repo\nnpx shadcn@latest search owner/repo -q \"login\"\nnpx shadcn@latest view owner/repo/item\nnpx shadcn@latest add owner/repo/item --dry-run\nnpx shadcn@latest registry validate owner/repo\n```\n\nWhen working on registry implementation in the shadcn/ui codebase:\n\n- Keep address parsing pure and testable.\n- Do not add side effects to validators.\n- Preserve existing behavior for official shadcn, namespace, URL, and file schemes.\n- Add tests for address parsing, source loading, dependency resolution, list, search, view, and add paths.\n- Prefer small source-reader abstractions over a plugin system until there are multiple real providers.\n",".agents/skills/shadcn/rules/base-vs-radix.md":"# Base vs Radix\n\nAPI differences between `base` and `radix`. Check the `base` field from `npx shadcn@latest info`.\n\n## Contents\n\n- Composition: asChild vs render\n- Button / trigger as non-button element\n- Select (items prop, placeholder, positioning, multiple, object values)\n- ToggleGroup (type vs multiple)\n- Slider (scalar vs array)\n- Accordion (type and defaultValue)\n\n---\n\n## Composition: asChild (radix) vs render (base)\n\nRadix uses `asChild` to replace the default element. Base uses `render`. Don't wrap triggers in extra elements.\n\n**Incorrect:**\n\n```tsx\n<DialogTrigger>\n  <div>\n    <Button>Open</Button>\n  </div>\n</DialogTrigger>\n```\n\n**Correct (radix):**\n\n```tsx\n<DialogTrigger asChild>\n  <Button>Open</Button>\n</DialogTrigger>\n```\n\n**Correct (base):**\n\n```tsx\n<DialogTrigger render={<Button />}>Open</DialogTrigger>\n```\n\nThis applies to all trigger and close components: `DialogTrigger`, `SheetTrigger`, `AlertDialogTrigger`, `DropdownMenuTrigger`, `PopoverTrigger`, `TooltipTrigger`, `CollapsibleTrigger`, `DialogClose`, `SheetClose`, `NavigationMenuLink`, `BreadcrumbLink`, `SidebarMenuButton`, `Badge`, `Item`.\n\n---\n\n## Button / trigger as non-button element (base only)\n\nWhen `render` changes an element to a non-button (`<a>`, `<span>`), add `nativeButton={false}`.\n\n**Incorrect (base):** missing `nativeButton={false}`.\n\n```tsx\n<Button render={<a href=\"/docs\" />}>Read the docs</Button>\n```\n\n**Correct (base):**\n\n```tsx\n<Button render={<a href=\"/docs\" />} nativeButton={false}>\n  Read the docs\n</Button>\n```\n\n**Correct (radix):**\n\n```tsx\n<Button asChild>\n  <a href=\"/docs\">Read the docs</a>\n</Button>\n```\n\nSame for triggers whose `render` is not a `Button`:\n\n```tsx\n// base.\n<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>\n  Pick date\n</PopoverTrigger>\n```\n\n---\n\n## Select\n\n**items prop (base only).** Base requires an `items` prop on the root. Radix uses inline JSX only.\n\n**Incorrect (base):**\n\n```tsx\n<Select>\n  <SelectTrigger>\n    <SelectValue placeholder=\"Select a fruit\" />\n  </SelectTrigger>\n</Select>\n```\n\n**Correct (base):**\n\n```tsx\nconst items = [\n  { label: \"Select a fruit\", value: null },\n  { label: \"Apple\", value: \"apple\" },\n  { label: \"Banana\", value: \"banana\" },\n]\n\n<Select items={items}>\n  <SelectTrigger>\n    <SelectValue />\n  </SelectTrigger>\n  <SelectContent>\n    <SelectGroup>\n      {items.map((item) => (\n        <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>\n      ))}\n    </SelectGroup>\n  </SelectContent>\n</Select>\n```\n\n**Correct (radix):**\n\n```tsx\n<Select>\n  <SelectTrigger>\n    <SelectValue placeholder=\"Select a fruit\" />\n  </SelectTrigger>\n  <SelectContent>\n    <SelectGroup>\n      <SelectItem value=\"apple\">Apple</SelectItem>\n      <SelectItem value=\"banana\">Banana</SelectItem>\n    </SelectGroup>\n  </SelectContent>\n</Select>\n```\n\n**Placeholder.** Base uses a `{ value: null }` item in the items array. Radix uses `<SelectValue placeholder=\"...\">`.\n\n**Content positioning.** Base uses `alignItemWithTrigger`. Radix uses `position`.\n\n```tsx\n// base.\n<SelectContent alignItemWithTrigger={false} side=\"bottom\">\n\n// radix.\n<SelectContent position=\"popper\">\n```\n\n---\n\n## Select — multiple selection and object values (base only)\n\nBase supports `multiple`, render-function children on `SelectValue`, and object values with `itemToStringValue`. Radix is single-select with string values only.\n\n**Correct (base — multiple selection):**\n\n```tsx\n<Select items={items} multiple defaultValue={[]}>\n  <SelectTrigger>\n    <SelectValue>\n      {(value: string[]) =>\n        value.length === 0 ? \"Select fruits\" : `${value.length} selected`\n      }\n    </SelectValue>\n  </SelectTrigger>\n  ...\n</Select>\n```\n\n**Correct (base — object values):**\n\n```tsx\n<Select defaultValue={plans[0]} itemToStringValue={(plan) => plan.name}>\n  <SelectTrigger>\n    <SelectValue>{(value) => value.name}</SelectValue>\n  </SelectTrigger>\n  ...\n</Select>\n```\n\n---\n\n## ToggleGroup\n\nBase uses a `multiple` boolean prop. Radix uses `type=\"single\"` or `type=\"multiple\"`.\n\n**Incorrect (base):**\n\n```tsx\n<ToggleGroup type=\"single\" defaultValue=\"daily\">\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n</ToggleGroup>\n```\n\n**Correct (base):**\n\n```tsx\n// Single (no prop needed), defaultValue is always an array.\n<ToggleGroup defaultValue={[\"daily\"]} spacing={2}>\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n  <ToggleGroupItem value=\"weekly\">Weekly</ToggleGroupItem>\n</ToggleGroup>\n\n// Multi-selection.\n<ToggleGroup multiple>\n  <ToggleGroupItem value=\"bold\">Bold</ToggleGroupItem>\n  <ToggleGroupItem value=\"italic\">Italic</ToggleGroupItem>\n</ToggleGroup>\n```\n\n**Correct (radix):**\n\n```tsx\n// Single, defaultValue is a string.\n<ToggleGroup type=\"single\" defaultValue=\"daily\" spacing={2}>\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n  <ToggleGroupItem value=\"weekly\">Weekly</ToggleGroupItem>\n</ToggleGroup>\n\n// Multi-selection.\n<ToggleGroup type=\"multiple\">\n  <ToggleGroupItem value=\"bold\">Bold</ToggleGroupItem>\n  <ToggleGroupItem value=\"italic\">Italic</ToggleGroupItem>\n</ToggleGroup>\n```\n\n**Controlled single value:**\n\n```tsx\n// base — wrap/unwrap arrays.\nconst [value, setValue] = React.useState(\"normal\")\n<ToggleGroup value={[value]} onValueChange={(v) => setValue(v[0])}>\n\n// radix — plain string.\nconst [value, setValue] = React.useState(\"normal\")\n<ToggleGroup type=\"single\" value={value} onValueChange={setValue}>\n```\n\n---\n\n## Slider\n\nBase accepts a plain number for a single thumb. Radix always requires an array.\n\n**Incorrect (base):**\n\n```tsx\n<Slider defaultValue={[50]} max={100} step={1} />\n```\n\n**Correct (base):**\n\n```tsx\n<Slider defaultValue={50} max={100} step={1} />\n```\n\n**Correct (radix):**\n\n```tsx\n<Slider defaultValue={[50]} max={100} step={1} />\n```\n\nBoth use arrays for range sliders. Controlled `onValueChange` in base may need a cast:\n\n```tsx\n// base.\nconst [value, setValue] = React.useState([0.3, 0.7])\n<Slider value={value} onValueChange={(v) => setValue(v as number[])} />\n\n// radix.\nconst [value, setValue] = React.useState([0.3, 0.7])\n<Slider value={value} onValueChange={setValue} />\n```\n\n---\n\n## Accordion\n\nRadix requires `type=\"single\"` or `type=\"multiple\"` and supports `collapsible`. `defaultValue` is a string. Base uses no `type` prop, uses `multiple` boolean, and `defaultValue` is always an array.\n\n**Incorrect (base):**\n\n```tsx\n<Accordion type=\"single\" collapsible defaultValue=\"item-1\">\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n</Accordion>\n```\n\n**Correct (base):**\n\n```tsx\n<Accordion defaultValue={[\"item-1\"]}>\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n</Accordion>\n\n// Multi-select.\n<Accordion multiple defaultValue={[\"item-1\", \"item-2\"]}>\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n  <AccordionItem value=\"item-2\">...</AccordionItem>\n</Accordion>\n```\n\n**Correct (radix):**\n\n```tsx\n<Accordion type=\"single\" collapsible defaultValue=\"item-1\">\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n</Accordion>\n```\n",".agents/skills/shadcn/rules/chat.md":"# Chat & Messaging\n\nComponents for conversation and chat UI. Compose these instead of hand-rolling bubbles, scroll containers, dividers, or attachment cards.\n\nInstall: `npx shadcn@latest add message-scroller message bubble attachment marker`\n\nThe same component names and props ship for both `base` and `radix`; only composition differs (`render` vs `asChild`). See [base-vs-radix.md](./base-vs-radix.md).\n\n## Contents\n\n- Scrollable threads use MessageScroller\n- Message rows use Message\n- Message surfaces use Bubble\n- Attachments use Attachment\n- System notes and dividers use Marker\n- Streaming, anchoring, and jump-to-latest are built in\n- Escape hatch: the scroller hooks\n\n---\n\n## Scrollable threads use MessageScroller\n\nA conversation that scrolls, follows new messages, restores position, or jumps to a message uses `MessageScroller`. Don't build a raw overflow container with manual scroll wiring, and don't reach for `ScrollArea`.\n\nThe parts nest in a fixed order. Every direct child of the content is wrapped in a `MessageScrollerItem` so the scroller can measure, anchor, preserve position, track visibility, and jump to it. `MessageScrollerButton` sits inside `MessageScroller`, after the viewport.\n\n**Incorrect:**\n\n```tsx\n// Hand-rolled scroll container with manual stick-to-bottom logic.\n<div ref={scrollRef} onScroll={handleScroll} className=\"flex-1 overflow-y-auto\">\n  <div className=\"flex flex-col gap-6 p-4\">\n    {messages.map((m) => (\n      <ChatMessage key={m.id} message={m} />\n    ))}\n  </div>\n</div>\n```\n\n**Correct:**\n\n```tsx\n<MessageScrollerProvider autoScroll>\n  <MessageScroller>\n    <MessageScrollerViewport>\n      <MessageScrollerContent>\n        {messages.map((message) => (\n          <MessageScrollerItem\n            key={message.id}\n            messageId={message.id}\n            scrollAnchor={message.role === \"user\"}\n          >\n            <Message align={message.role === \"user\" ? \"end\" : \"start\"}>\n              {/* ...message content... */}\n            </Message>\n          </MessageScrollerItem>\n        ))}\n      </MessageScrollerContent>\n    </MessageScrollerViewport>\n    <MessageScrollerButton />\n  </MessageScroller>\n</MessageScrollerProvider>\n```\n\n---\n\n## Message rows use Message\n\n`Message` lays out a single row: avatar, header, content, footer, with alignment. Group consecutive rows from one sender with `MessageGroup`. Don't rebuild the row from flex divs.\n\n`align=\"end\"` is the current user's side; `align=\"start\"` is everyone else.\n\n```tsx\n<Message align=\"start\">\n  <MessageAvatar>\n    <Avatar>\n      <AvatarImage src={sender.avatar} alt={sender.name} />\n      <AvatarFallback>{initials}</AvatarFallback>\n    </Avatar>\n  </MessageAvatar>\n  <MessageContent>\n    <MessageHeader>{sender.name}</MessageHeader>\n    <Bubble>\n      <BubbleContent>{text}</BubbleContent>\n    </Bubble>\n    <MessageFooter>{time}</MessageFooter>\n  </MessageContent>\n</Message>\n```\n\n---\n\n## Message surfaces use Bubble\n\nThe colored message surface is `Bubble` + `BubbleContent`, never a styled `div` with `bg-muted` / `bg-primary` and hand-managed corners.\n\n- `variant`: `default`, `secondary`, `muted`, `tinted`, `outline`, `ghost`, `destructive`.\n- `align`: `start` or `end` (matches the `Message` side).\n\n`BubbleReactions` renders the reaction cluster. `side` (`top` | `bottom`) and `align` (`start` | `end`) position it against the bubble. Don't lay reactions out with absolutely-positioned `Badge`s.\n\n**Incorrect:**\n\n```tsx\n<div className=\"w-fit rounded-2xl bg-primary px-3 py-2 text-primary-foreground\">\n  {text}\n</div>\n```\n\n**Correct:**\n\n```tsx\n<Bubble variant=\"default\" align=\"end\">\n  <BubbleContent>{text}</BubbleContent>\n  <BubbleReactions side=\"bottom\" align=\"end\">\n    <Badge variant=\"secondary\">👍 2</Badge>\n  </BubbleReactions>\n</Bubble>\n```\n\n---\n\n## Attachments use Attachment\n\nFile and image attachments use `Attachment`, not `Item` or a custom card. It carries upload state, so wire `state` to the real status rather than rendering a separate spinner.\n\n- `state`: `idle`, `uploading`, `processing`, `error`, `done`. `uploading` and `processing` apply the `shimmer` animation to the title automatically.\n- `size`: `default`, `sm`, `xs`. `orientation`: `horizontal`, `vertical`.\n- Use `AttachmentGroup` to lay out several attachments in a scrolling row.\n\n```tsx\n<Attachment state=\"done\">\n  <AttachmentMedia variant=\"icon\">\n    <FileTextIcon />\n  </AttachmentMedia>\n  <AttachmentContent>\n    <AttachmentTitle>homepage-feedback.pdf</AttachmentTitle>\n    <AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>\n  </AttachmentContent>\n  <AttachmentActions>\n    <AttachmentAction>\n      <DownloadIcon />\n    </AttachmentAction>\n  </AttachmentActions>\n</Attachment>\n```\n\nFor an image, use `<AttachmentMedia variant=\"image\">` with an `img` child.\n\n---\n\n## System notes and dividers use Marker\n\nStatus lines (\"Sarah joined the conversation\"), date dividers (\"Today\"), and labeled separators are `Marker`, not a `Separator` plus a centered span.\n\n- `variant`: `default` (plain row), `separator` (centered label with rules on each side), `border` (bottom-bordered row).\n- `MarkerIcon` holds a leading icon; `MarkerContent` holds the label.\n\n**Incorrect:**\n\n```tsx\n<div className=\"flex items-center gap-3 py-2\">\n  <Separator className=\"flex-1\" />\n  <span className=\"text-xs text-muted-foreground\">Today</span>\n  <Separator className=\"flex-1\" />\n</div>\n```\n\n**Correct:**\n\n```tsx\n<Marker variant=\"separator\">\n  <MarkerContent>Today</MarkerContent>\n</Marker>\n```\n\n---\n\n## Streaming, anchoring, and jump-to-latest are built in\n\n`MessageScroller` handles the behavior that chat UIs usually reinvent. Don't write a `useStickToBottom` hook, a `ResizeObserver`, or manual `scrollTop` math.\n\n- **Follow the live edge while streaming.** `MessageScrollerProvider` with `autoScroll` keeps the view pinned to new content and yields the moment the user scrolls up. Streaming token updates that grow the last message are followed automatically.\n- **Anchor a turn.** `scrollAnchor` on a `MessageScrollerItem` marks the row to hold in view (typically the user's message that started the turn).\n- **Jump to latest.** `MessageScrollerButton` appears when the user scrolls away and scrolls back on click. `direction=\"end\"` (default) or `direction=\"start\"`. It is a self-managing control, so don't gate it behind your own scroll-position state.\n\nFor a \"thinking…\" indicator while the model generates, apply the `shimmer` utility to text. Don't author a custom keyframe animation. See [styling.md](./styling.md).\n\n---\n\n## Escape hatch: the scroller hooks\n\nFor behavior the parts don't expose, read state from the hooks rather than re-implementing the scroller: `useMessageScroller`, `useMessageScrollerVisibility`, and `useMessageScrollerScrollable`. They come from the auto-installed `@shadcn/react` dependency, so there's nothing extra to install. Reach for them only when composition can't express what you need.\n",".agents/skills/shadcn/rules/composition.md":"# Component Composition\n\n## Contents\n\n- Items always inside their Group component\n- Callouts use Alert\n- Empty states use Empty component\n- Toast notifications follow the project base\n- Choosing between overlay components\n- Dialog, Sheet, and Drawer always need a Title\n- Card structure\n- Button has no isPending or isLoading prop\n- TabsTrigger must be inside TabsList\n- Avatar always needs AvatarFallback\n- Use Separator instead of raw hr or border divs\n- Use Skeleton for loading placeholders\n- Use Badge instead of custom styled spans\n\n---\n\n## Items always inside their Group component\n\nNever render items directly inside the content container.\n\n**Incorrect:**\n\n```tsx\n<SelectContent>\n  <SelectItem value=\"apple\">Apple</SelectItem>\n  <SelectItem value=\"banana\">Banana</SelectItem>\n</SelectContent>\n```\n\n**Correct:**\n\n```tsx\n<SelectContent>\n  <SelectGroup>\n    <SelectItem value=\"apple\">Apple</SelectItem>\n    <SelectItem value=\"banana\">Banana</SelectItem>\n  </SelectGroup>\n</SelectContent>\n```\n\nThis applies to all group-based components:\n\n| Item | Group |\n| --- | --- |\n| `SelectItem`, `SelectLabel` | `SelectGroup` |\n| `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` |\n| `MenubarItem` | `MenubarGroup` |\n| `ContextMenuItem` | `ContextMenuGroup` |\n| `CommandItem` | `CommandGroup` |\n| `MessageScrollerItem` | `MessageScrollerContent` |\n| `Message` (consecutive, same sender) | `MessageGroup` |\n| `Bubble` (stacked) | `BubbleGroup` |\n| `Attachment` (in a row) | `AttachmentGroup` |\n\nChat components nest in a fixed order (`MessageScrollerProvider` → `MessageScroller` → `MessageScrollerViewport` → `MessageScrollerContent` → `MessageScrollerItem`). See [chat.md](./chat.md).\n\n---\n\n## Callouts use Alert\n\n```tsx\n<Alert>\n  <AlertTitle>Warning</AlertTitle>\n  <AlertDescription>Something needs attention.</AlertDescription>\n</Alert>\n```\n\n---\n\n## Empty states use Empty component\n\n```tsx\n<Empty>\n  <EmptyHeader>\n    <EmptyMedia variant=\"icon\">\n      <FolderIcon />\n    </EmptyMedia>\n    <EmptyTitle>No projects yet</EmptyTitle>\n    <EmptyDescription>Get started by creating a new project.</EmptyDescription>\n  </EmptyHeader>\n  <EmptyContent>\n    <Button>Create Project</Button>\n  </EmptyContent>\n</Empty>\n```\n\n---\n\n## Toast notifications follow the project base\n\nFor Base UI projects, use the `toast` component:\n\n```tsx\nimport { toast } from \"@/components/ui/toast\";\n\ntoast.add({\n  title: \"Changes saved.\",\n});\n```\n\nFor Radix and React Aria projects, use Sonner:\n\n```tsx\nimport { toast } from \"sonner\";\n\ntoast.success(\"Changes saved.\");\ntoast.error(\"Something went wrong.\");\ntoast(\"File deleted.\", {\n  action: { label: \"Undo\", onClick: () => undoDelete() },\n});\n```\n\n---\n\n## Choosing between overlay components\n\n| Use case                           | Component     |\n| ---------------------------------- | ------------- |\n| Focused task that requires input   | `Dialog`      |\n| Destructive action confirmation    | `AlertDialog` |\n| Side panel with details or filters | `Sheet`       |\n| Mobile-first bottom panel          | `Drawer`      |\n| Quick info on hover                | `HoverCard`   |\n| Small contextual content on click  | `Popover`     |\n\n---\n\n## Dialog, Sheet, and Drawer always need a Title\n\n`DialogTitle`, `SheetTitle`, `DrawerTitle` are required for accessibility. Use `className=\"sr-only\"` if visually hidden.\n\n```tsx\n<DialogContent>\n  <DialogHeader>\n    <DialogTitle>Edit Profile</DialogTitle>\n    <DialogDescription>Update your profile.</DialogDescription>\n  </DialogHeader>\n  ...\n</DialogContent>\n```\n\n---\n\n## Card structure\n\nUse full composition — don't dump everything into `CardContent`:\n\n```tsx\n<Card>\n  <CardHeader>\n    <CardTitle>Team Members</CardTitle>\n    <CardDescription>Manage your team.</CardDescription>\n  </CardHeader>\n  <CardContent>...</CardContent>\n  <CardFooter>\n    <Button>Invite</Button>\n  </CardFooter>\n</Card>\n```\n\n---\n\n## Button has no isPending or isLoading prop\n\nCompose with `Spinner` + `data-icon` + `disabled`:\n\n```tsx\n<Button disabled>\n  <Spinner data-icon=\"inline-start\" />\n  Saving...\n</Button>\n```\n\n---\n\n## TabsTrigger must be inside TabsList\n\nNever render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`:\n\n```tsx\n<Tabs defaultValue=\"account\">\n  <TabsList>\n    <TabsTrigger value=\"account\">Account</TabsTrigger>\n    <TabsTrigger value=\"password\">Password</TabsTrigger>\n  </TabsList>\n  <TabsContent value=\"account\">...</TabsContent>\n</Tabs>\n```\n\n---\n\n## Avatar always needs AvatarFallback\n\nAlways include `AvatarFallback` for when the image fails to load:\n\n```tsx\n<Avatar>\n  <AvatarImage src=\"/avatar.png\" alt=\"User\" />\n  <AvatarFallback>JD</AvatarFallback>\n</Avatar>\n```\n\n---\n\n## Use existing components instead of custom markup\n\n| Instead of | Use |\n| --- | --- |\n| `<hr>` or `<div className=\"border-t\">` | `<Separator />` |\n| `<div className=\"animate-pulse\">` with styled divs | `<Skeleton className=\"h-4 w-3/4\" />` |\n| `<span className=\"rounded-full bg-green-100 ...\">` | `<Badge variant=\"secondary\">` |\n",".agents/skills/shadcn/rules/forms.md":"# Forms & Inputs\n\n## Contents\n\n- Forms use FieldGroup + Field\n- InputGroup requires InputGroupInput/InputGroupTextarea\n- Buttons inside inputs use InputGroup + InputGroupAddon\n- Option sets (2–7 choices) use ToggleGroup\n- FieldSet + FieldLegend for grouping related fields\n- Field validation and disabled states\n\n---\n\n## Forms use FieldGroup + Field\n\nAlways use `FieldGroup` + `Field` — never raw `div` with `space-y-*`:\n\n```tsx\n<FieldGroup>\n  <Field>\n    <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n    <Input id=\"email\" type=\"email\" />\n  </Field>\n  <Field>\n    <FieldLabel htmlFor=\"password\">Password</FieldLabel>\n    <Input id=\"password\" type=\"password\" />\n  </Field>\n</FieldGroup>\n```\n\nUse `Field orientation=\"horizontal\"` for settings pages. Use `FieldLabel className=\"sr-only\"` for visually hidden labels.\n\n**Choosing form controls:**\n\n- Simple text input → `Input`\n- Dropdown with predefined options → `Select`\n- Searchable dropdown → `Combobox`\n- Native HTML select (no JS) → `native-select`\n- Boolean toggle → `Switch` (for settings) or `Checkbox` (for forms)\n- Single choice from few options → `RadioGroup`\n- Toggle between 2–5 options → `ToggleGroup` + `ToggleGroupItem`\n- OTP/verification code → `InputOTP`\n- Multi-line text → `Textarea`\n\n---\n\n## InputGroup requires InputGroupInput/InputGroupTextarea\n\nNever use raw `Input` or `Textarea` inside an `InputGroup`.\n\n**Incorrect:**\n\n```tsx\n<InputGroup>\n  <Input placeholder=\"Search...\" />\n</InputGroup>\n```\n\n**Correct:**\n\n```tsx\nimport { InputGroup, InputGroupInput } from \"@/components/ui/input-group\";\n\n<InputGroup>\n  <InputGroupInput placeholder=\"Search...\" />\n</InputGroup>;\n```\n\n---\n\n## Buttons inside inputs use InputGroup + InputGroupAddon\n\nNever place a `Button` directly inside or adjacent to an `Input` with custom positioning.\n\n**Incorrect:**\n\n```tsx\n<div className=\"relative\">\n  <Input placeholder=\"Search...\" className=\"pr-10\" />\n  <Button className=\"absolute right-0 top-0\" size=\"icon\">\n    <SearchIcon />\n  </Button>\n</div>\n```\n\n**Correct:**\n\n```tsx\nimport {\n  InputGroup,\n  InputGroupInput,\n  InputGroupAddon,\n} from \"@/components/ui/input-group\";\n\n<InputGroup>\n  <InputGroupInput placeholder=\"Search...\" />\n  <InputGroupAddon>\n    <Button size=\"icon\">\n      <SearchIcon data-icon=\"inline-start\" />\n    </Button>\n  </InputGroupAddon>\n</InputGroup>;\n```\n\n---\n\n## Option sets (2–7 choices) use ToggleGroup\n\nDon't manually loop `Button` components with active state.\n\n**Incorrect:**\n\n```tsx\nconst [selected, setSelected] = useState(\"daily\")\n\n<div className=\"flex gap-2\">\n  {[\"daily\", \"weekly\", \"monthly\"].map((option) => (\n    <Button\n      key={option}\n      variant={selected === option ? \"default\" : \"outline\"}\n      onClick={() => setSelected(option)}\n    >\n      {option}\n    </Button>\n  ))}\n</div>\n```\n\n**Correct:**\n\n```tsx\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\n\n<ToggleGroup spacing={2}>\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n  <ToggleGroupItem value=\"weekly\">Weekly</ToggleGroupItem>\n  <ToggleGroupItem value=\"monthly\">Monthly</ToggleGroupItem>\n</ToggleGroup>;\n```\n\nCombine with `Field` for labelled toggle groups:\n\n```tsx\n<Field orientation=\"horizontal\">\n  <FieldTitle id=\"theme-label\">Theme</FieldTitle>\n  <ToggleGroup aria-labelledby=\"theme-label\" spacing={2}>\n    <ToggleGroupItem value=\"light\">Light</ToggleGroupItem>\n    <ToggleGroupItem value=\"dark\">Dark</ToggleGroupItem>\n    <ToggleGroupItem value=\"system\">System</ToggleGroupItem>\n  </ToggleGroup>\n</Field>\n```\n\n> **Note:** `defaultValue` and `type`/`multiple` props differ between base and radix. See [base-vs-radix.md](./base-vs-radix.md#togglegroup).\n\n---\n\n## FieldSet + FieldLegend for grouping related fields\n\nUse `FieldSet` + `FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading:\n\n```tsx\n<FieldSet>\n  <FieldLegend variant=\"label\">Preferences</FieldLegend>\n  <FieldDescription>Select all that apply.</FieldDescription>\n  <FieldGroup className=\"gap-3\">\n    <Field orientation=\"horizontal\">\n      <Checkbox id=\"dark\" />\n      <FieldLabel htmlFor=\"dark\" className=\"font-normal\">\n        Dark mode\n      </FieldLabel>\n    </Field>\n  </FieldGroup>\n</FieldSet>\n```\n\n---\n\n## Field validation and disabled states\n\nBoth attributes are needed — `data-invalid`/`data-disabled` styles the field (label, description), while `aria-invalid`/`disabled` styles the control.\n\n```tsx\n// Invalid.\n<Field data-invalid>\n  <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n  <Input id=\"email\" aria-invalid />\n  <FieldDescription>Invalid email address.</FieldDescription>\n</Field>\n\n// Disabled.\n<Field data-disabled>\n  <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n  <Input id=\"email\" disabled />\n</Field>\n```\n\nWorks for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`, `NativeSelect`, `InputOTP`.\n",".agents/skills/shadcn/rules/icons.md":"# Icons\n\n**Always use the project's configured `iconLibrary` for imports.** Check the `iconLibrary` field from project context: `lucide` → `lucide-react`, `tabler` → `@tabler/icons-react`, etc. Never assume `lucide-react`.\n\n---\n\n## Icons in Button use data-icon attribute\n\nAdd `data-icon=\"inline-start\"` (prefix) or `data-icon=\"inline-end\"` (suffix) to the icon. No sizing classes on the icon.\n\n**Incorrect:**\n\n```tsx\n<Button>\n  <SearchIcon className=\"mr-2 size-4\" />\n  Search\n</Button>\n```\n\n**Correct:**\n\n```tsx\n<Button>\n  <SearchIcon data-icon=\"inline-start\"/>\n  Search\n</Button>\n\n<Button>\n  Next\n  <ArrowRightIcon data-icon=\"inline-end\"/>\n</Button>\n```\n\n---\n\n## No sizing classes on icons inside components\n\nComponents handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons inside `Button`, `DropdownMenuItem`, `Alert`, `Sidebar*`, or other shadcn components. Unless the user explicitly asks for custom icon sizes.\n\n**Incorrect:**\n\n```tsx\n<Button>\n  <SearchIcon className=\"size-4\" data-icon=\"inline-start\" />\n  Search\n</Button>\n\n<DropdownMenuItem>\n  <SettingsIcon className=\"mr-2 size-4\" />\n  Settings\n</DropdownMenuItem>\n```\n\n**Correct:**\n\n```tsx\n<Button>\n  <SearchIcon data-icon=\"inline-start\" />\n  Search\n</Button>\n\n<DropdownMenuItem>\n  <SettingsIcon />\n  Settings\n</DropdownMenuItem>\n```\n\n---\n\n## Pass icons as component objects, not string keys\n\nUse `icon={CheckIcon}`, not a string key to a lookup map.\n\n**Incorrect:**\n\n```tsx\nconst iconMap = {\n  check: CheckIcon,\n  alert: AlertIcon,\n};\n\nfunction StatusBadge({ icon }: { icon: string }) {\n  const Icon = iconMap[icon];\n  return <Icon />;\n}\n\n<StatusBadge icon=\"check\" />;\n```\n\n**Correct:**\n\n```tsx\n// Import from the project's configured iconLibrary (e.g. lucide-react, @tabler/icons-react).\nimport { CheckIcon } from \"lucide-react\";\n\nfunction StatusBadge({ icon: Icon }: { icon: React.ComponentType }) {\n  return <Icon />;\n}\n\n<StatusBadge icon={CheckIcon} />;\n```\n",".agents/skills/shadcn/rules/styling.md":"# Styling & Customization\n\nSee [customization.md](../customization.md) for theming, CSS variables, and adding custom colors.\n\n## Contents\n\n- Semantic colors\n- Built-in variants first\n- className for layout only\n- No space-x-* / space-y-*\n- Prefer size-* over w-* h-* when equal\n- Prefer truncate shorthand\n- No manual dark: color overrides\n- Use cn() for conditional classes\n- No manual z-index on overlay components\n- Use shimmer / scroll-fade utilities, not custom animations\n\n---\n\n## Semantic colors\n\n**Incorrect:**\n\n```tsx\n<div className=\"bg-blue-500 text-white\">\n  <p className=\"text-gray-600\">Secondary text</p>\n</div>\n```\n\n**Correct:**\n\n```tsx\n<div className=\"bg-primary text-primary-foreground\">\n  <p className=\"text-muted-foreground\">Secondary text</p>\n</div>\n```\n\n---\n\n## No raw color values for status/state indicators\n\nFor positive, negative, or status indicators, use Badge variants, semantic tokens like `text-destructive`, or define custom CSS variables — don't reach for raw Tailwind colors.\n\n**Incorrect:**\n\n```tsx\n<span className=\"text-emerald-600\">+20.1%</span>\n<span className=\"text-green-500\">Active</span>\n<span className=\"text-red-600\">-3.2%</span>\n```\n\n**Correct:**\n\n```tsx\n<Badge variant=\"secondary\">+20.1%</Badge>\n<Badge>Active</Badge>\n<span className=\"text-destructive\">-3.2%</span>\n```\n\nIf you need a success/positive color that doesn't exist as a semantic token, use a Badge variant or ask the user about adding a custom CSS variable to the theme (see [customization.md](../customization.md)).\n\n---\n\n## Built-in variants first\n\n**Incorrect:**\n\n```tsx\n<Button className=\"border border-input bg-transparent hover:bg-accent\">\n  Click me\n</Button>\n```\n\n**Correct:**\n\n```tsx\n<Button variant=\"outline\">Click me</Button>\n```\n\n---\n\n## className for layout only\n\nUse `className` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables.\n\n**Incorrect:**\n\n```tsx\n<Card className=\"bg-blue-100 text-blue-900 font-bold\">\n  <CardContent>Dashboard</CardContent>\n</Card>\n```\n\n**Correct:**\n\n```tsx\n<Card className=\"max-w-md mx-auto\">\n  <CardContent>Dashboard</CardContent>\n</Card>\n```\n\nTo customize a component's appearance, prefer these approaches in order:\n\n1. **Built-in variants** — `variant=\"outline\"`, `variant=\"destructive\"`, etc.\n2. **Semantic color tokens** — `bg-primary`, `text-muted-foreground`.\n3. **CSS variables** — define custom colors in the global CSS file (see [customization.md](../customization.md)).\n\n---\n\n## No space-x-* / space-y-*\n\nUse `gap-*` instead. `space-y-4` → `flex flex-col gap-4`. `space-x-2` → `flex gap-2`.\n\n```tsx\n<div className=\"flex flex-col gap-4\">\n  <Input />\n  <Input />\n  <Button>Submit</Button>\n</div>\n```\n\n---\n\n## Prefer size-* over w-* h-* when equal\n\n`size-10` not `w-10 h-10`. Applies to icons, avatars, skeletons, etc.\n\n---\n\n## Prefer truncate shorthand\n\n`truncate` not `overflow-hidden text-ellipsis whitespace-nowrap`.\n\n---\n\n## No manual dark: color overrides\n\nUse semantic tokens — they handle light/dark via CSS variables. `bg-background text-foreground` not `bg-white dark:bg-gray-950`.\n\n---\n\n## Use cn() for conditional classes\n\nUse the `cn()` utility from the project for conditional or merged class names. Don't write manual ternaries in className strings.\n\n**Incorrect:**\n\n```tsx\n<div className={`flex items-center ${isActive ? \"bg-primary text-primary-foreground\" : \"bg-muted\"}`}>\n```\n\n**Correct:**\n\n```tsx\nimport { cn } from \"@/lib/utils\"\n\n<div className={cn(\"flex items-center\", isActive ? \"bg-primary text-primary-foreground\" : \"bg-muted\")}>\n```\n\n---\n\n## No manual z-index on overlay components\n\n`Dialog`, `Sheet`, `Drawer`, `AlertDialog`, `DropdownMenu`, `Popover`, `Tooltip`, `HoverCard` handle their own stacking. Never add `z-50` or `z-[999]`.\n\n---\n\n## Use shimmer / scroll-fade utilities, not custom animations\n\nFor a live \"thinking…\" or loading-text shimmer, apply the `shimmer` utility. Don't author a custom `@keyframes` or a `bg-clip-text` gradient sweep.\n\nFor scroll-aware edge fading on a scroll container, use `scroll-fade` (and the axis variants `scroll-fade-x` / `scroll-fade-b`). Don't hand-roll mask gradients. The chat components already apply these internally: `Attachment` shimmers its title during upload, and `MessageScrollerViewport` fades its edges.\n\n**Incorrect:**\n\n```tsx\n<span className=\"animate-pulse bg-gradient-to-r from-muted-foreground/40 via-foreground/70 to-muted-foreground/40 bg-clip-text text-transparent [animation:shimmer_1.6s_infinite]\">\n  Thinking…\n</span>\n```\n\n**Correct:**\n\n```tsx\n<span className=\"shimmer\">Thinking…</span>\n```\n",".claude/commands/validate-auth-schema.md":"# Validate Auth Schema\n\nCheck that the Drizzle schema in `db/schema/` still satisfies what Better Auth expects. Better Auth derives its required tables from the enabled plugins, so the generated JSON — not the upstream docs — is the source of truth:\n\n```bash\nbun run db/scripts/generate-auth-schema.ts\n```\n\nThe script builds a real auth instance from `apps/api/lib/auth.ts` with fixed placeholder credentials, every optional integration switched on, so the output is the full set of tables rather than the subset your environment happens to enable. Per field it carries the `type`, the `required`, `unique` and `index` flags, any literal `defaultValue`, and the reference target with its `onDelete`. Per table it carries `indexes`: the composite indexes a plugin declares.\n\nCompare that output against `db/schema/` and report:\n\n- Tables Better Auth expects that are missing, and tables no plugin in `auth.ts` needs any more.\n- Field-level drift: missing or extra columns, mismatched types, and `required` or `unique` flags that disagree.\n- Foreign keys pointing at the wrong table or column.\n- A `true` `index` flag, a table-level entry in `indexes`, a literal `defaultValue`, or an `onDelete` in the output is Better Auth asking for it: report a schema that lacks one. Indexes, defaults and cascades the project adds beyond that list are its own decisions — check those against the conventions in `db/AGENTS.md` and do not report them as drift.\n- Local naming that must stay mapped, not renamed away: Better Auth's `account` is our `identity` table.\n- Project-specific columns, indexes, and constraints are expected and fine on their own. Flag any that constrain a write Better Auth performs: a `NOT NULL` column with no database default breaks every insert into that table, and an extra unique or check constraint can reject a row Better Auth considers valid. Fields that must participate in its models belong in `additionalFields` rather than added to the table behind its back.\n\nReport findings with the specific fix for each. Do not run `db:push`, `db:generate`, or `db:migrate` as part of a validation — schema changes are a separate, deliberate step.\n",".github/copilot-instructions.md":"Read AGENTS.md in the repository root and any subdirectory AGENTS.md files for project context, conventions, and architecture details before making changes.\n","AGENTS.md":"## Monorepo Structure\n\n- `apps/web/` – Astro marketing site, served by an edge worker that also routes traffic to the app/api workers via service bindings\n- `apps/app/` – Main SPA (React, TanStack Router file-based routing)\n- `apps/api/` – API server (Hono + tRPC + Better Auth)\n- `apps/email/` – React Email templates (built before API dev server starts)\n- `packages/ui/` – shadcn/ui components (new-york style)\n- `packages/core/` – Shared utilities\n- `db/` – Drizzle ORM schemas and migrations (Neon PostgreSQL)\n- `infra/` – Terraform (Hyperdrive and optional R2 storage; Wrangler owns Workers and DNS)\n- `docs/` – VitePress docs; `docs/adr/` for architecture decision records\n\n## Tech Stack\n\n- **Toolchain:** Bun >=1.4.0 (package manager, scripts, local API server), TypeScript 7.0 (native compiler), Oxlint + Oxfmt, ESM (`\"type\": \"module\"`)\n- **Production runtime:** Cloudflare Workers (workerd) – not Bun. Code that ships must run on Workers APIs.\n- **Frontend:** React 19, TanStack Router, TanStack Query, Jotai, shadcn/ui (new-york), Tailwind CSS v4\n- **Backend:** Hono, tRPC 11, Better Auth (email OTP, passkey, Google OAuth, organizations)\n- **Database:** Neon PostgreSQL, Drizzle ORM (`snake_case` casing), Cloudflare Hyperdrive\n- **Email:** React Email, Resend\n- **Testing:** Vitest, Happy DOM, PGlite (in-process Postgres, no service to start)\n- **Deployment:** Cloudflare Workers (Wrangler), Terraform\n\n## Commands\n\n```bash\nbun dev                        # Start web + api + app concurrently\nbun run build                  # Build email, web, api, and app workspaces\nbun run test                   # Vitest (watch mode; --run for single run)\nbun lint                       # Oxlint\nbun run format:check           # Oxfmt (bun run format writes)\nbun typecheck                  # tsc --build (builds apps/email for its types)\nbun infra:check                # Terraform fmt + validate, no credentials or state\nbun ui:add <component>         # Add shadcn/ui component to packages/ui\nbun deploy:{staging,production} # Build and deploy api → app → web; no migrations\n\n# Per-app: bun {web,app,api}:{dev,build,deploy}; test for app/api, check for web\n# Database: bun db:{push,generate,migrate,studio,seed,export}\n#   :staging / :production on migrate, studio, export; seed stops at :staging;\n#   generate is local-only, and push refuses a non-local database\n```\n\n## Verification\n\n- Run the narrowest checks covering what you changed, then report what you ran. Never imply verification you did not perform.\n- Do not run migrations, deployments, or Terraform `plan`/`apply` unless explicitly asked – they act on shared environments.\n\n## Architecture\n\n- Three workers: web (marketing site + edge router), app (SPA assets), api (Hono server).\n- API worker has `nodejs_compat` enabled; web and app workers do NOT.\n- Web worker routes: `/api/*` → API worker, app routes → App worker, static → assets.\n- Service bindings connect workers internally (no public cross-worker URLs).\n- Per-workspace conventions live in subdirectory `AGENTS.md` files: `apps/api/`, `apps/app/`, `db/`, `infra/`, `packages/ui/`.\n\n## Agent Tooling\n\n- `AGENTS.md` files are the canonical instructions; per-tool files (`CLAUDE.md`, `.gemini/settings.json`) only point at them.\n- **Every directory with an `AGENTS.md` needs a sibling `CLAUDE.md` containing `@AGENTS.md`.** Codex scopes nested `AGENTS.md` to its directory tree natively; Claude Code's nested lookup matches only `CLAUDE.md`, so without the adapter it never loads the scoped file. Adding a scoped `AGENTS.md` without one is a silent no-op for Claude.\n- **Skills live in `.agents/skills/<name>/SKILL.md`, symlinked into `.claude/skills/<name>/`.** Both vendors build on the Agent Skills format, but only the discovery paths differ: Codex scans `.agents/skills/` from the repo root natively, Claude Code scans `.claude/skills/`. One canonical copy plus a symlink is what makes a skill visible to both. Third-party skills installed by the `skills` CLI are tracked in `skills-lock.json` – do not hand-edit those directories.\n- Skills are for workflows the model should recognise and reach for on its own; their `description` is the trigger. A prompt you invoke deliberately stays a Claude Code command in `.claude/commands/<name>.md`, which has no portable equivalent.\n- `*.local.md` and `*.local.json` are gitignored – personal prompts and settings stay out of the repo.\n\n## Design Philosophy\n\n- Simplest correct solution. No speculative abstractions – add them only when a real second use case exists.\n- No superficial work: no coverage-only tests, no redundant comments, no wrappers that just forward calls.\n- Fail loudly in core logic. Do not silently swallow errors or mask incorrect state.\n- Three similar lines are better than a premature abstraction.\n- Prefer explicit, readable code over clever or compressed patterns.\n- Use precise TypeScript types. Avoid `any` and unnecessary type assertions – let the compiler enforce correctness.\n- Document non-obvious trade-offs and decisions. Explain why, not what – every word must add value.\n\n## Formatting\n\n- Import order belongs to Oxfmt (`sortImports` in `.oxfmtrc.json`): applied by `bun run format`, checked by `bun run format:check` in CI and by lint-staged on commit. Do not reintroduce an editor code action or a lint rule for it.\n\n## Markdown\n\n- Prose is not hard-wrapped: keep each paragraph on one line and use paragraphs, lists and headings for structure. Oxfmt enforces this with `proseWrap: \"never\"`.\n- Keep a blank line after a VitePress container's opening marker and before its closing `:::`. Oxfmt does not recognise `:::`, so an adjacent line gets folded into the marker, turning the body into the container title and swallowing everything up to the next `:::`.\n","CLAUDE.md":"@AGENTS.md\n\n## Claude-Specific Guidance\n\n- Use Plan mode for multi-file or architectural changes.\n- Prefer an existing project skill or command over improvising the same workflow.\n- See \"Agent Tooling\" in `AGENTS.md` before adding a command or skill.\n","db/AGENTS.md":"## Schema Conventions\n\n- Every new table file must be re-exported from `schema/index.ts`. `drizzle.config.ts` points at that barrel rather than the directory, so an unexported table is invisible to `db:generate`, which reports no changes instead of failing.\n- Drizzle `casing: \"snake_case\"` – use camelCase in TypeScript, columns map to snake_case in DB.\n- Primary keys use application-generated prefixed CUID2 IDs: `generateAuthId(model)` for Better Auth models and `generateId(\"xxx\")` for domain tables. See `schema/id.ts` for the prefix map.\n- Timestamps: `timestamp({ withTimezone: true, mode: \"date\" })`. Every table has `createdAt` (`.defaultNow().notNull()`) and `updatedAt` (`.defaultNow().$onUpdate(() => new Date()).notNull()`).\n- `identity` table = Better Auth's `account` table, renamed via `account.modelName: \"identity\"` in auth config.\n- `member.role` (\"owner\", \"admin\", \"member\") and `invitation.status` are free `text`, not pgEnum – avoids fragile coupling with Better Auth's values.\n- `organization.metadata` is `text`, not JSONB – Better Auth handles serialization.\n- Migrations differ by role. A project generated from this kit keeps ordinary migration history – see `docs/database/migrations.md`. The starter kit itself squashes into a single `0000_init.sql`; contributors changing its schema should read `.github/CONTRIBUTING.md` first.\n\n## Extended Fields (beyond Better Auth defaults)\n\n- **Passkey:** `lastUsedAt` (security audits), `deviceName` (user-friendly label), `platform` (\"platform\" | \"cross-platform\").\n- **Invitation:** `acceptedAt`/`rejectedAt` are reserved for application hooks; Better Auth updates `status` but does not populate them.\n\n## Indexes and Constraints\n\n- Every foreign key column gets an index: `{table}_{column}_idx`. All foreign keys use `onDelete: \"cascade\"`.\n- Composite uniques: `member(userId, organizationId)`, `invitation(organizationId, email)`, `identity(providerId, accountId)`.\n- Better Auth keys an identity on `(providerId, accountId)` and throws when a lookup matches two rows. Never add an `issuer` column back – see the note on `identity` in `schema/user.ts`.\n- The `invitation` unique is a starter limitation: Better Auth creates a new row for a later invitation, so remove the constraint before supporting re-invites after acceptance, rejection, or cancellation.\n- `session.activeOrganizationId` has an index but no FK constraint (Better Auth design).\n\n## Seeds\n\n- Use `onConflictDoNothing()` for idempotent seeds (safe to rerun).\n\n## Testing\n\n- `createTestDatabase()` from `@repo/db/testing` boots PGlite and applies `migrations/`, so tests run on the schema production receives. No connection string, no service, no `.env.test` – there is deliberately no `test` environment in `drizzle.config.ts`.\n- It returns `{ db, reset, close }`. One instance at module scope (`await createTestDatabase()`), `reset()` between tests, `close()` in `afterAll`. `reset()` truncates every table in the schema, derived from the barrel, so a new table is covered automatically.\n- Use the test database when behavior depends on query scoping or database constraints. A mocked lookup cannot fail on a wrong `where`. See `docs/testing.md`.\n- `Database` (exported from `@repo/db`) is the driver-agnostic client type – postgres-js in production, PGlite in tests. Type parameters and helpers against it, not `PostgresJsDatabase`. Selected rows, relational queries and `.returning()` stay schema-typed; `execute()` and mutations without `.returning()` resolve to `unknown` (the command result belongs to the driver), and `$client` is not exposed. Add `.returning()` when a write needs to report anything.\n\n## Environment\n\n- `ENVIRONMENT` overrides `NODE_ENV` for env file selection. Database scripts use `production`, `staging`, and `dev`; the API runtime uses `production`, `staging`, and `development`.\n- `migrate`, `studio` and `export` have `:staging` / `:production` variants; `seed` stops at `:staging`; `generate` and `push` have none.\n- **`push` is enforced local, not merely documented as local.** `scripts/guard-push.ts` resolves the same `DATABASE_URL` the command would use and refuses a non-local host; `ALLOW_REMOTE_DB_PUSH=1` is the deliberate way past it. Reshaping in place is an unreviewed migration that can drop a column and its data.\n- `scripts/grant-app-role.sql` creates the least-privilege role Hyperdrive should use in staging and production. It is documentation-and-tooling only – nothing in the repo runs it.\n- Development loads `.env.{envName}.local` → `.env.local` → `.env`, first value wins. Staging and production load only `.env.{envName}.local`, override exported values, and throw if it is absent.\n","db/CLAUDE.md":"@AGENTS.md\n","infra/AGENTS.md":"Terraform for the durable Cloudflare resources the Workers consume. [`README.md`](./README.md) covers setup and usage, [ADR-002](../docs/adr/002-terraform-wrangler-boundary.md) the ownership decision, [ADR-003](../docs/adr/003-hcp-terraform-state.md) the state backend, and [`docs/specs/infra-terraform.md`](../docs/specs/infra-terraform.md) the full specification. Those carry the rationale and the operating procedure; this file is the short list of constraints that matter while editing `infra/`.\n\n## Ownership Boundary\n\n- Terraform provisions what the Workers consume: the two Hyperdrive configurations and the opt-in R2 uploads bucket. Nothing else. Worker names, code, routes, custom domains, bindings, vars, secrets, and assets belong to `apps/*/wrangler.jsonc` – adding a `cloudflare_workers_*` or DNS resource here is the change that breaks the design, and it will not announce itself as a conflict until a deploy overwrites something.\n- The handoff is manual and non-secret: Hyperdrive IDs and the R2 bucket name are pasted into `wrangler.jsonc` once per environment. A change to a handoff output is incomplete without the corresponding `wrangler.jsonc` update.\n\n## Layout and Naming\n\n- The two roots stay structurally parallel: same provider constraints, same module interface, same variable and output names. A structural change to one belongs in the other.\n- Their _values_ may differ on purpose. Enabling an optional resource in staging before production is a supported rollout, not drift – see the [file uploads recipe](../docs/recipes/file-uploads.md). Do not copy an environment-specific value across unless the change is meant for both.\n- Resource logic lives in `modules/cloudflare`. Each root holds its environment identity, provider and `cloud` configuration, root variables and outputs, and the module call.\n- Resource values follow `{project_slug}-{environment}[-role]`. `project_slug` must match the worker name prefix in `apps/*/wrangler.jsonc`, or the bindings point at resources that do not exist.\n- Resource identifiers name the concrete thing (`cloudflare_hyperdrive_config.cached`); module names describe the role (`module.edge`).\n\n## Variables and Outputs\n\n- Encode structural constraints in `validation` blocks so they fail locally rather than mid-apply, and write the `error_message` as the fix rather than the rule – see `uploads_cors_origins` rejecting `\"*\"`.\n- Inputs holding credentials are `sensitive = true` (`database_url`). Outputs are stable, non-secret identifiers only – they are printed into CI summaries and committed to `wrangler.jsonc`.\n\n## Running Terraform\n\nValidate locally with the same credential-free check CI runs:\n\n```bash\nbun infra:check\n```\n\nThat is `terraform fmt -check` plus `init -backend=false` and `validate` for each root. `-backend=false` installs providers and modules without contacting the backend, so it needs no HCP token and reads no state.\n\n- **Never run `plan`, `apply`, `destroy`, `import`, or any state mutation.** `plan` is not the safe half: HCP Terraform executes the configuration it is handed in the workspace's privileged run environment, with that workspace's state and variables, so planning uncommitted work exercises real credentials against real infrastructure. `bun infra:check` is the branch-level signal; applying is a manual dispatch of `.github/workflows/infra.yml` from `main`.\n- Never commit state, `.terraform/`, credentials, or `.tfvars` – state holds the database password. `infra/.gitignore` covers these; do not narrow it.\n","infra/CLAUDE.md":"@AGENTS.md\n","packages/core/README.md":"# Core Package\n\nShared utilities and helpers used across the monorepo.\n","packages/typescript-config/README.md":"# TypeScript Configuration\n\nShared TypeScript configuration for the monorepo.\n\n## Usage\n\nExtend from the appropriate configuration in your `tsconfig.json`:\n\n```jsonc\n// React applications\n{ \"extends\": \"@repo/typescript-config/react.jsonc\" }\n\n// Node.js/Bun applications\n{ \"extends\": \"@repo/typescript-config/node.jsonc\" }\n\n// Cloudflare Workers\n{ \"extends\": \"@repo/typescript-config/cloudflare.jsonc\" }\n```\n\n## Available Configurations\n\n- `base.jsonc` -- Core strict-mode configuration shared by all targets\n- `react.jsonc` -- React applications with DOM types and JSX support\n- `node.jsonc` -- Node.js/Bun backend services\n- `cloudflare.jsonc` -- Cloudflare Workers edge functions\n","packages/ui/AGENTS.md":"shadcn/ui component library (new-york style, Radix primitives, Tailwind v4). Consumed as `@repo/ui`.\n\n## Scope\n\n- Only generic primitives belong here. If a component imports a route, a query, or the session, it belongs in `apps/app/components/` instead.\n- Nothing here imports application or domain code (`apps/`, `db/`). Consumers still have to supply Tailwind `@source` entries and the theme tokens.\n- Imports inside this package go through Node subpath imports — `#lib/utils`, `#components/toggle` — declared in `package.json#imports` and mirrored by the `components.json` aliases, so the CLI generates them natively. They resolve against _this_ package from every workspace, so a consumer needs no matching file of its own. Requires `moduleResolution: \"bundler\"`, which `packages/typescript-config/base.jsonc` already sets.\n- `index.ts` exports the top-level primitives in `components/`. Nested files are implementation details — promote one deliberately if it should be public.\n\n- `scripts/` is intentionally outside the tsconfig `include`. These are Bun CLI tools, while the library's declaration build uses the browser-only React preset (`types: [\"vite/client\"]`). Including them would require Bun/Node types and emit declarations for tooling. Both commands hit the network and overwrite files, so smoke-test changes on a single component (`bun ui:update button`).\n\n## Adding and Updating Components\n\n- Use `bun ui:add <component>`; don't hand-write files the registry already has. The wrappers take item names only and reject flags — they exist for reusable `registry:ui` primitives and always write to `packages/ui`. Blocks, pages, custom registries, and the read-only flags go to the CLI directly — point it at the workspace, or it resolves the wrong `components.json`: `bunx shadcn@latest add button --dry-run --cwd packages/ui`.\n- Both run `scripts/postprocess.ts` afterwards: it strips `\"use client\"`, regenerates `index.ts`, and formats. `index.ts` is generated; add components with the CLI, not by editing it.\n- Components import primitives from the unified `radix-ui` package, not the per-primitive `@radix-ui/react-*` ones. The CLI adds whatever a component needs; review and commit the resulting `package.json` and `bun.lock` changes.\n- `bun ui:update` re-fetches every installed component in one `shadcn add` call, overwriting in place. Review `git diff` before committing; local edits are lost. Pass names (`bun ui:update button card`) to narrow it; unknown names are rejected rather than silently added, though a named component still pulls its registry dependencies.\n- Registry output is not uniform — read what it generated. Postprocessing is mechanical; API changes still need review. Convert React 18 patterns before committing — no lint rule covers any of these, so they are a review step:\n  - `<Context.Provider value={x}>` → `<Context value={x}>`\n  - `React.useContext(C)` → `React.use(C)`\n  - `React.ElementRef<T>` → `React.ComponentRef<T>` — `ElementRef` is a deprecated alias in `@types/react` 19. `bun --cwd apps/web check` reports it as a hint.\n- `components.json` needs `tailwind.config: \"\"`. That empty string is how the CLI detects Tailwind v4; without it the registry silently serves the v3-era components (`forwardRef`, no `data-slot`) into a v4 project.\n\n## Styling\n\n- Every component accepts `className` and passes it through `cn()` last — directly, or via the `className` slot on a `cva` variants call — so callers can override defaults without a specificity fight.\n- Use theme tokens (`bg-primary`, `text-muted-foreground`), never raw colors. Each consumer defines the values in its own `styles/globals.css` — `apps/app` and `apps/web` keep separate copies, so a palette change means editing both. `styles.css` here exists only to satisfy the shadcn CLI.\n- Class names must appear as complete literals — Tailwind scans text, so `` `bg-${color}-500` `` produces nothing.\n- Consuming apps must `@source` every directory here that holds class names, or those classes are stripped from their build.\n- Enter/exit utilities (`animate-in`, `fade-in-0`, `zoom-in-95`, `slide-in-from-*`) come from `tw-animate-css`, which each consumer imports separately — `apps/app` does, `apps/web` does not, because nothing it renders is animated. Adding an animated component to a consumer that lacks the import fails silently: Tailwind does not recognise the class names, drops them, and reports nothing, so the component simply renders without transitions.\n\n## Conventions\n\n- Named exports only — no default exports.\n- Variants via `class-variance-authority`; export the variants object when another component composes it (see `toggle.tsx` → `toggle-group.tsx`).\n- Prefer a Radix primitive over hand-rolled behavior — it brings the ARIA roles and keyboard handling with it. `ToggleGroup type=\"single\"` already renders `role=\"radiogroup\"` with `role=\"radio\"` items and arrow-key navigation, so callers add no keyboard code.\n","packages/ui/CLAUDE.md":"@AGENTS.md\n","packages/ui/README.md":"# UI Components\n\nShared UI component library built on shadcn/ui (new-york style), Radix UI, and Tailwind CSS v4.\n\n[Documentation](https://reactstarter.com/frontend/ui)\n\n## Usage\n\n```typescript\nimport { Button, Card, Input, cn } from \"@repo/ui\";\n```\n\n## Commands\n\n```bash\nbun ui:add <component>    # Add a shadcn/ui component\nbun ui:update             # Re-fetch installed components\n```\n\nSee [AGENTS.md](./AGENTS.md) for the conventions these commands assume.\n\n## Structure\n\n```bash\ncomponents/       # shadcn/ui components\nhooks/            # Custom React hooks\nlib/              # Utilities (cn function)\nscripts/          # ui:add / ui:update and their postprocessing\nindex.ts          # Generated barrel export\n```\n\nConsuming apps must `@source` every directory here that holds class names, or those classes are stripped from their build. See [apps/app/tailwind.config.css](../../apps/app/tailwind.config.css) for an example.\n","packages/ws-protocol/README.md":"# WebSocket Protocol\n\nType-safe WebSocket protocol definitions using [WS-Kit](https://github.com/kriasoft/ws-kit).\n\n[Documentation](https://reactstarter.com/recipes/websockets)\n\n## Quick Start\n\n```bash\nbun run example       # Run example server\n```\n\n## Usage\n\n```typescript\nimport { z, message, rpc } from \"@ws-kit/zod\";\nimport { createRouter, withZod } from \"@ws-kit/zod\";\n\nconst Ping = message(\"PING\", { timestamp: z.number().optional() });\nconst Pong = message(\"PONG\", { timestamp: z.number() });\n\nconst router = createRouter<{ userId?: string }>()\n  .plugin(withZod())\n  .on(Ping, (ctx) => {\n    ctx.send(Pong, { timestamp: Date.now() });\n  });\n```\n\n## Structure\n\n```bash\nmessages.ts   # Message schema definitions\nrouter.ts     # Router factory with handlers\nexample.ts    # Example server\nindex.ts      # Public exports\n```\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"## Monorepo Structure\n\n- `apps/web/` – Astro marketing site, served by an edge worker that also routes traffic to the app/api workers via service bindings\n- `apps/app/` – Main SPA (React, TanStack Router file-based routing)\n- `apps/api/` – API server (Hono + tRPC + Better Auth)\n- `apps/email/` – React Email templates (built before API dev server starts)\n- `packages/ui/` – shadcn/ui components (new-york style)\n- `packages/core/` – Shared utilities\n- `db/` – Drizzle ORM schemas and migrations (Neon PostgreSQL)\n- `infra/` – Terraform (Hyperdrive and optional R2 storage; Wrangler owns Workers and DNS)\n- `docs/` – VitePress docs; `docs/adr/` for architecture decision records\n\n## Tech Stack\n\n- **Toolchain:** Bun >=1.4.0 (package manager, scripts, local API server), TypeScript 7.0 (native compiler), Oxlint + Oxfmt, ESM (`\"type\": \"module\"`)\n- **Production runtime:** Cloudflare Workers (workerd) – not Bun. Code that ships must run on Workers APIs.\n- **Frontend:** React 19, TanStack Router, TanStack Query, Jotai, shadcn/ui (new-york), Tailwind CSS v4\n- **Backend:** Hono, tRPC 11, Better Auth (email OTP, passkey, Google OAuth, organizations)\n- **Database:** Neon PostgreSQL, Drizzle ORM (`snake_case` casing), Cloudflare Hyperdrive\n- **Email:** React Email, Resend\n- **Testing:** Vitest, Happy DOM, PGlite (in-process Postgres, no service to start)\n- **Deployment:** Cloudflare Workers (Wrangler), Terraform\n\n## Commands\n\n```bash\nbun dev                        # Start web + api + app concurrently\nbun run build                  # Build email, web, api, and app workspaces\nbun run test                   # Vitest (watch mode; --run for single run)\nbun lint                       # Oxlint\nbun run format:check           # Oxfmt (bun run format writes)\nbun typecheck                  # tsc --build (builds apps/email for its types)\nbun infra:check                # Terraform fmt + validate, no credentials or state\nbun ui:add <component>         # Add shadcn/ui component to packages/ui\nbun deploy:{staging,production} # Build and deploy api → app → web; no migrations\n\n# Per-app: bun {web,app,api}:{dev,build,deploy}; test for app/api, check for web\n# Database: bun db:{push,generate,migrate,studio,seed,export}\n#   :staging / :production on migrate, studio, export; seed stops at :staging;\n#   generate is local-only, and push refuses a non-local database\n```\n\n## Verification\n\n- Run the narrowest checks covering what you changed, then report what you ran. Never imply verification you did not perform.\n- Do not run migrations, deployments, or Terraform `plan`/`apply` unless explicitly asked – they act on shared environments.\n\n## Architecture\n\n- Three workers: web (marketing site + edge router), app (SPA assets), api (Hono server).\n- API worker has `nodejs_compat` enabled; web and app workers do NOT.\n- Web worker routes: `/api/*` → API worker, app routes → App worker, static → assets.\n- Service bindings connect workers internally (no public cross-worker URLs).\n- Per-workspace conventions live in subdirectory `AGENTS.md` files: `apps/api/`, `apps/app/`, `db/`, `infra/`, `packages/ui/`.\n\n## Agent Tooling\n\n- `AGENTS.md` files are the canonical instructions; per-tool files (`CLAUDE.md`, `.gemini/settings.json`) only point at them.\n- **Every directory with an `AGENTS.md` needs a sibling `CLAUDE.md` containing `@AGENTS.md`.** Codex scopes nested `AGENTS.md` to its directory tree natively; Claude Code's nested lookup matches only `CLAUDE.md`, so without the adapter it never loads the scoped file. Adding a scoped `AGENTS.md` without one is a silent no-op for Claude.\n- **Skills live in `.agents/skills/<name>/SKILL.md`, symlinked into `.claude/skills/<name>/`.** Both vendors build on the Agent Skills format, but only the discovery paths differ: Codex scans `.agents/skills/` from the repo root natively, Claude Code scans `.claude/skills/`. One canonical copy plus a symlink is what makes a skill visible to both. Third-party skills installed by the `skills` CLI are tracked in `skills-lock.json` – do not hand-edit those directories.\n- Skills are for workflows the model should recognise and reach for on its own; their `description` is the trigger. A prompt you invoke deliberately stays a Claude Code command in `.claude/commands/<name>.md`, which has no portable equivalent.\n- `*.local.md` and `*.local.json` are gitignored – personal prompts and settings stay out of the repo.\n\n## Design Philosophy\n\n- Simplest correct solution. No speculative abstractions – add them only when a real second use case exists.\n- No superficial work: no coverage-only tests, no redundant comments, no wrappers that just forward calls.\n- Fail loudly in core logic. Do not silently swallow errors or mask incorrect state.\n- Three similar lines are better than a premature abstraction.\n- Prefer explicit, readable code over clever or compressed patterns.\n- Use precise TypeScript types. Avoid `any` and unnecessary type assertions – let the compiler enforce correctness.\n- Document non-obvious trade-offs and decisions. Explain why, not what – every word must add value.\n\n## Formatting\n\n- Import order belongs to Oxfmt (`sortImports` in `.oxfmtrc.json`): applied by `bun run format`, checked by `bun run format:check` in CI and by lint-staged on commit. Do not reintroduce an editor code action or a lint rule for it.\n\n## Markdown\n\n- Prose is not hard-wrapped: keep each paragraph on one line and use paragraphs, lists and headings for structure. Oxfmt enforces this with `proseWrap: \"never\"`.\n- Keep a blank line after a VitePress container's opening marker and before its closing `:::`. Oxfmt does not recognise `:::`, so an adjacent line gets folded into the marker, turning the body into the container title and swallowing everything up to the next `:::`.\n","isInternal":false,"tokens":1420,"sizeBytes":5807},{"name":"AGENTS.md","path":"db/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/db/AGENTS.md","title":"db - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"## Schema Conventions\n\n- Every new table file must be re-exported from `schema/index.ts`. `drizzle.config.ts` points at that barrel rather than the directory, so an unexported table is invisible to `db:generate`, which reports no changes instead of failing.\n- Drizzle `casing: \"snake_case\"` – use camelCase in TypeScript, columns map to snake_case in DB.\n- Primary keys use application-generated prefixed CUID2 IDs: `generateAuthId(model)` for Better Auth models and `generateId(\"xxx\")` for domain tables. See `schema/id.ts` for the prefix map.\n- Timestamps: `timestamp({ withTimezone: true, mode: \"date\" })`. Every table has `createdAt` (`.defaultNow().notNull()`) and `updatedAt` (`.defaultNow().$onUpdate(() => new Date()).notNull()`).\n- `identity` table = Better Auth's `account` table, renamed via `account.modelName: \"identity\"` in auth config.\n- `member.role` (\"owner\", \"admin\", \"member\") and `invitation.status` are free `text`, not pgEnum – avoids fragile coupling with Better Auth's values.\n- `organization.metadata` is `text`, not JSONB – Better Auth handles serialization.\n- Migrations differ by role. A project generated from this kit keeps ordinary migration history – see `docs/database/migrations.md`. The starter kit itself squashes into a single `0000_init.sql`; contributors changing its schema should read `.github/CONTRIBUTING.md` first.\n\n## Extended Fields (beyond Better Auth defaults)\n\n- **Passkey:** `lastUsedAt` (security audits), `deviceName` (user-friendly label), `platform` (\"platform\" | \"cross-platform\").\n- **Invitation:** `acceptedAt`/`rejectedAt` are reserved for application hooks; Better Auth updates `status` but does not populate them.\n\n## Indexes and Constraints\n\n- Every foreign key column gets an index: `{table}_{column}_idx`. All foreign keys use `onDelete: \"cascade\"`.\n- Composite uniques: `member(userId, organizationId)`, `invitation(organizationId, email)`, `identity(providerId, accountId)`.\n- Better Auth keys an identity on `(providerId, accountId)` and throws when a lookup matches two rows. Never add an `issuer` column back – see the note on `identity` in `schema/user.ts`.\n- The `invitation` unique is a starter limitation: Better Auth creates a new row for a later invitation, so remove the constraint before supporting re-invites after acceptance, rejection, or cancellation.\n- `session.activeOrganizationId` has an index but no FK constraint (Better Auth design).\n\n## Seeds\n\n- Use `onConflictDoNothing()` for idempotent seeds (safe to rerun).\n\n## Testing\n\n- `createTestDatabase()` from `@repo/db/testing` boots PGlite and applies `migrations/`, so tests run on the schema production receives. No connection string, no service, no `.env.test` – there is deliberately no `test` environment in `drizzle.config.ts`.\n- It returns `{ db, reset, close }`. One instance at module scope (`await createTestDatabase()`), `reset()` between tests, `close()` in `afterAll`. `reset()` truncates every table in the schema, derived from the barrel, so a new table is covered automatically.\n- Use the test database when behavior depends on query scoping or database constraints. A mocked lookup cannot fail on a wrong `where`. See `docs/testing.md`.\n- `Database` (exported from `@repo/db`) is the driver-agnostic client type – postgres-js in production, PGlite in tests. Type parameters and helpers against it, not `PostgresJsDatabase`. Selected rows, relational queries and `.returning()` stay schema-typed; `execute()` and mutations without `.returning()` resolve to `unknown` (the command result belongs to the driver), and `$client` is not exposed. Add `.returning()` when a write needs to report anything.\n\n## Environment\n\n- `ENVIRONMENT` overrides `NODE_ENV` for env file selection. Database scripts use `production`, `staging`, and `dev`; the API runtime uses `production`, `staging`, and `development`.\n- `migrate`, `studio` and `export` have `:staging` / `:production` variants; `seed` stops at `:staging`; `generate` and `push` have none.\n- **`push` is enforced local, not merely documented as local.** `scripts/guard-push.ts` resolves the same `DATABASE_URL` the command would use and refuses a non-local host; `ALLOW_REMOTE_DB_PUSH=1` is the deliberate way past it. Reshaping in place is an unreviewed migration that can drop a column and its data.\n- `scripts/grant-app-role.sql` creates the least-privilege role Hyperdrive should use in staging and production. It is documentation-and-tooling only – nothing in the repo runs it.\n- Development loads `.env.{envName}.local` → `.env.local` → `.env`, first value wins. Staging and production load only `.env.{envName}.local`, override exported values, and throw if it is absent.\n","isInternal":false,"tokens":1108,"sizeBytes":4697},{"name":"AGENTS.md","path":"infra/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/infra/AGENTS.md","title":"infra - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"Terraform for the durable Cloudflare resources the Workers consume. [`README.md`](./README.md) covers setup and usage, [ADR-002](../docs/adr/002-terraform-wrangler-boundary.md) the ownership decision, [ADR-003](../docs/adr/003-hcp-terraform-state.md) the state backend, and [`docs/specs/infra-terraform.md`](../docs/specs/infra-terraform.md) the full specification. Those carry the rationale and the operating procedure; this file is the short list of constraints that matter while editing `infra/`.\n\n## Ownership Boundary\n\n- Terraform provisions what the Workers consume: the two Hyperdrive configurations and the opt-in R2 uploads bucket. Nothing else. Worker names, code, routes, custom domains, bindings, vars, secrets, and assets belong to `apps/*/wrangler.jsonc` – adding a `cloudflare_workers_*` or DNS resource here is the change that breaks the design, and it will not announce itself as a conflict until a deploy overwrites something.\n- The handoff is manual and non-secret: Hyperdrive IDs and the R2 bucket name are pasted into `wrangler.jsonc` once per environment. A change to a handoff output is incomplete without the corresponding `wrangler.jsonc` update.\n\n## Layout and Naming\n\n- The two roots stay structurally parallel: same provider constraints, same module interface, same variable and output names. A structural change to one belongs in the other.\n- Their _values_ may differ on purpose. Enabling an optional resource in staging before production is a supported rollout, not drift – see the [file uploads recipe](../docs/recipes/file-uploads.md). Do not copy an environment-specific value across unless the change is meant for both.\n- Resource logic lives in `modules/cloudflare`. Each root holds its environment identity, provider and `cloud` configuration, root variables and outputs, and the module call.\n- Resource values follow `{project_slug}-{environment}[-role]`. `project_slug` must match the worker name prefix in `apps/*/wrangler.jsonc`, or the bindings point at resources that do not exist.\n- Resource identifiers name the concrete thing (`cloudflare_hyperdrive_config.cached`); module names describe the role (`module.edge`).\n\n## Variables and Outputs\n\n- Encode structural constraints in `validation` blocks so they fail locally rather than mid-apply, and write the `error_message` as the fix rather than the rule – see `uploads_cors_origins` rejecting `\"*\"`.\n- Inputs holding credentials are `sensitive = true` (`database_url`). Outputs are stable, non-secret identifiers only – they are printed into CI summaries and committed to `wrangler.jsonc`.\n\n## Running Terraform\n\nValidate locally with the same credential-free check CI runs:\n\n```bash\nbun infra:check\n```\n\nThat is `terraform fmt -check` plus `init -backend=false` and `validate` for each root. `-backend=false` installs providers and modules without contacting the backend, so it needs no HCP token and reads no state.\n\n- **Never run `plan`, `apply`, `destroy`, `import`, or any state mutation.** `plan` is not the safe half: HCP Terraform executes the configuration it is handed in the workspace's privileged run environment, with that workspace's state and variables, so planning uncommitted work exercises real credentials against real infrastructure. `bun infra:check` is the branch-level signal; applying is a manual dispatch of `.github/workflows/infra.yml` from `main`.\n- Never commit state, `.terraform/`, credentials, or `.tfvars` – state holds the database password. `infra/.gitignore` covers these; do not narrow it.\n","isInternal":false,"tokens":774,"sizeBytes":3532},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"@AGENTS.md\n\n## Claude-Specific Guidance\n\n- Use Plan mode for multi-file or architectural changes.\n- Prefer an existing project skill or command over improvising the same workflow.\n- See \"Agent Tooling\" in `AGENTS.md` before adding a command or skill.\n","isInternal":false,"tokens":57,"sizeBytes":251},{"name":"CLAUDE.md","path":"db/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/db/CLAUDE.md","title":"db - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"@AGENTS.md\n","isInternal":false,"tokens":5,"sizeBytes":11},{"name":"CLAUDE.md","path":"infra/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/infra/CLAUDE.md","title":"infra - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"@AGENTS.md\n","isInternal":false,"tokens":5,"sizeBytes":11},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.github/copilot-instructions.md","title":"GitHub Copilot Instructions","category":"copilot-instructions","format":"markdown","content":"Read AGENTS.md in the repository root and any subdirectory AGENTS.md files for project context, conventions, and architecture details before making changes.\n","isInternal":false,"tokens":29,"sizeBytes":157},{"name":"SKILL.md","path":".agents/skills/merge-seed/SKILL.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.agents/skills/merge-seed/SKILL.md","title":"merge-seed","category":"anthropic-skill","format":"markdown","content":"---\nname: merge-seed\ndescription: Merge upstream React Starter Kit updates (the `seed` remote) into main, preserving this project's identity, scope, and behavior. Use when asked to sync, pull, or merge the seed / starter kit / upstream template.\n---\n\n# Merge seed updates\n\nThis project is a fork of React Starter Kit. `seed/main` is upstream; `main` is upstream plus this project's divergence. Syncing is a recurring operation.\n\n## Core rule\n\n**Upstream owns mechanism. This project owns identity and scope.**\n\nAdopt from upstream: dependency and toolchain versions, build and config structure, framework API updates, refactors, newly required bindings and variables.\n\nKeep local: names, domains, routes, worker and service identifiers; product copy and positioning; plans, features, scope; project-only integrations and config; deliberate additions and deletions.\n\nWhen a hunk mixes both, take upstream's structure and reapply local values. **Never take a whole side merely to clear a conflict** — whole-file resolution is valid only where a rule below makes that side authoritative. The repository defines what counts as local identity and scope: `AGENTS.md`, `docs/adr/`, `docs/product/`, and the current config on `main`.\n\n## Merge\n\n```bash\ngit status --short   # must be clean\n\ngit remote get-url seed || git remote add seed https://github.com/kriasoft/react-starter-kit.git\ngit fetch seed\n\nMB=$(git merge-base main seed/main)\ngit log --oneline \"$MB..seed/main\"   # read before resolving anything\n\ngit switch -c \"chore/merge-seed-$(git rev-parse --short seed/main)\" main\ngit -c merge.conflictStyle=zdiff3 merge --no-ff --no-commit --no-rerere-autoupdate seed/main\ngit diff --name-only --diff-filter=U   # unresolved conflicts\n```\n\n`--no-commit` stops git auto-committing a clean merge, so the commit and its subject are always ours to write. `--no-rerere-autoupdate` keeps every conflict in that list even when `rerere` has a recorded resolution to replay. If the branch already exists, an earlier sync was left unfinished — inspect it and resume or delete it deliberately, never force-reset over it.\n\n`rerere` writes its replayed resolution straight into the working tree, so a conflicted file can arrive already looking finished. Treat pre-filled content as unreviewed; `git rerere forget <path>` to redo it.\n\nIn ordinary text conflicts `zdiff3` includes the merge base, which is how to tell deliberate local divergence from a line upstream simply changed. Where there is no usable inline base — binary, add/add, modify/delete — read the index stages instead: `git ls-files -u <path>` lists which exist, then `git show :1:<path>` base, `:2:` ours, `:3:` theirs. Only the sides that still have content are present, so add/add has no base and a modify/delete has no stage for the side that deleted. For an ambiguous hunk, read the commit behind it: `git log -p \"$MB..seed/main\" -- <path>`.\n\nResolve, stage each resolved path, then commit with the subject in **Land**.\n\n## Resolution rules\n\n- **Infra, Wrangler, Terraform** — keep local names, hostnames, zones, routes, IDs, and values; adopt upstream compatibility dates, new bindings, and newly required variables, then fill in local values.\n- **Env files** — union: keep local values and project-only keys, add new upstream keys, and drop upstream-deleted keys only when the mechanism behind them is also gone.\n- **Manifests** — adopt upstream versions and upstream's own scripts; keep project-only dependencies and scripts still in use; drop local entries an upstream migration made obsolete.\n- **Lockfile** — never hand-merge: resolve every manifest first, take upstream's lockfile, then `bun install` and stage the regenerated result.\n- **Product copy — marketing pages, docs** — local copy and positioning are authoritative; port only technical changes.\n- **App and API code** — prefer upstream's refactor, then reapply local behavior on the new structure; a rewritten config or key list must still carry project-only keys.\n- **Migrations** — a migration that may have run is immutable. Never resolve changes to one in place: if upstream modified it, escalate; otherwise keep local migration history and express incoming schema changes as new migrations.\n- **ADRs** — local numbering is stable; renumber a colliding incoming ADR and fix its links.\n- **Local deletion vs upstream edit, and upstream renames** — deliberate deletions stay deleted; follow renames, carrying local content to the new path.\n\nDon't bring in upstream features that conflict with documented product scope.\n\n## Verify\n\n```bash\nbun install --frozen-lockfile && bun typecheck && bun lint && bun run test -- --run && bun run build\n```\n\n`--frozen-lockfile` fails if the manifests and lockfile disagree, which is what verification should prove. Green checks are still necessary, not sufficient — they cannot see lost identity or copy. Compare the result against each endpoint, per conflicted file and overall:\n\n```bash\ngit diff main..HEAD -- <path>        # upstream's mechanism, plus adaptations it forced\ngit diff seed/main..HEAD -- <path>   # the fork's divergence as it now stands\n```\n\nThese are endpoint comparisons, not per-side attribution: a local value may legitimately move or change shape in the first diff when upstream restructures around it, and the second carries the fork's whole history, not just this merge. What matters is that every difference is explainable — upstream mechanism adopted, or local intent kept. An unexplainable one is a bad resolution.\n\nThen confirm:\n\n- no leftover conflict markers: `git diff --check main..HEAD | grep 'conflict marker'` — they survive in files no build step reads, such as env and docs\n- no upstream branding, placeholder values, or template copy in changed files\n- no local config or key silently dropped by an upstream refactor\n- no stale paths or call sites left by an upstream rename\n- `AGENTS.md` invariants still hold\n- nothing unexpected in files that auto-merged (`git diff main..HEAD --stat`)\n\n## Land\n\nCommit subject `chore: merge seed up to <short-sha>`, notable resolutions in the body. Then, once verification passes:\n\n```bash\ngit switch main && git merge --ff-only <branch> && git branch -d <branch>\n```\n\nIf `--ff-only` is refused, `main` advanced during the merge: integrate the new `main` into the branch and re-verify before landing.\n\n## Escalate\n\nStop and report — file, upstream intent, local intent, the open choice — instead of guessing, when upstream modifies a migration that may have run or changes live-data semantics, ships a feature against documented product scope, or when preserving both mechanism and local behavior is genuinely ambiguous.\n","frontmatter":{"name":"merge-seed","description":"Merge upstream React Starter Kit updates (the `seed` remote) into main, preserving this project's identity, scope, and behavior. Use when asked to sync, pull, or merge the seed / starter kit / upstream template."},"isInternal":false,"tokens":1489,"sizeBytes":6694},{"name":"cli.md","path":".agents/skills/shadcn/cli.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.agents/skills/shadcn/cli.md","title":"Shadcn Skill","category":"anthropic-skill","format":"markdown","content":"# shadcn CLI Reference\n\nConfiguration is read from `components.json`.\n\n> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.\n\n> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag.\n\n## Contents\n\n- Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build\n- Templates: next, vite, start, react-router, astro\n- Presets: named, code, URL formats and fields\n- Switching presets\n\n---\n\n## Commands\n\n### `init` — Initialize or create a project\n\n```bash\nnpx shadcn@latest init [components...] [options]\n```\n\nInitializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--template <template>` | `-t` | Template (next, start, vite, next-monorepo, react-router) | — |\n| `--preset [name]` | `-p` | Preset configuration (named, code, or URL) | — |\n| `--yes` | `-y` | Skip confirmation prompt | `true` |\n| `--defaults` | `-d` | Use defaults (`--template=next --preset=base-nova`) | `false` |\n| `--force` | `-f` | Force overwrite existing configuration | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n| `--name <name>` | `-n` | Name for new project | — |\n| `--silent` | `-s` | Mute output | `false` |\n| `--rtl` |  | Enable RTL support | — |\n| `--reinstall` |  | Re-install existing UI components | `false` |\n| `--monorepo` |  | Scaffold a monorepo project | — |\n| `--no-monorepo` |  | Skip the monorepo prompt | — |\n\n`npx shadcn@latest create` is an alias for `npx shadcn@latest init`.\n\n### `apply` — Apply a preset to an existing project\n\n```bash\nnpx shadcn@latest apply [preset] [options]\n```\n\nApplies a preset to an existing project, overwriting preset-driven config, fonts, CSS variables, and detected UI components.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--preset <preset>` | — | Preset configuration (named, code, or URL) | — |\n| `--yes` | `-y` | Skip confirmation prompt | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n| `--silent` | `-s` | Mute output | `false` |\n\n`[preset]` is a shorthand for `--preset <preset>`. If both are provided, they must match. If no preset is provided, the CLI offers to open the custom preset builder on `ui.shadcn.com/create`.\n\n### `add` — Add components\n\n> **IMPORTANT:** To compare local components against upstream or to preview changes, ALWAYS use `npx shadcn@latest add <component> --dry-run`, `--diff`, or `--view`. NEVER fetch raw files from GitHub or other sources manually. The CLI handles registry resolution, file paths, and CSS diffing automatically.\n\n```bash\nnpx shadcn@latest add [components...] [options]\n```\n\nAccepts component names, registry-prefixed names (`@magicui/shimmer-button`), GitHub item addresses (`owner/repo/item`), URLs, or local paths.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--yes` | `-y` | Skip confirmation prompt | `false` |\n| `--overwrite` | `-o` | Overwrite existing files | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n| `--all` | `-a` | Add all available components | `false` |\n| `--path <path>` | `-p` | Target path for the component | — |\n| `--silent` | `-s` | Mute output | `false` |\n| `--dry-run` |  | Preview all changes without writing files | `false` |\n| `--diff [path]` |  | Show diffs. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |\n| `--view [path]` |  | Show file contents. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |\n\n#### Dry-Run Mode\n\nUse `--dry-run` to preview what `add` would do without writing any files. `--diff` and `--view` both imply `--dry-run`.\n\n```bash\n# Preview all changes.\nnpx shadcn@latest add button --dry-run\n\n# Show diffs for all files (top 5).\nnpx shadcn@latest add button --diff\n\n# Show the diff for a specific file.\nnpx shadcn@latest add button --diff button.tsx\n\n# Show contents for all files (top 5).\nnpx shadcn@latest add button --view\n\n# Show the full content of a specific file.\nnpx shadcn@latest add button --view button.tsx\n\n# Works with URLs too.\nnpx shadcn@latest add https://api.npoint.io/abc123 --dry-run\n\n# Works with public GitHub registries too.\nnpx shadcn@latest add owner/repo/item --dry-run\n\n# CSS diffs.\nnpx shadcn@latest add button --diff globals.css\n```\n\n**When to use dry-run:**\n\n- When the user asks \"what files will this add?\" or \"what will this change?\" — use `--dry-run`.\n- Before overwriting existing components — use `--diff` to preview the changes first.\n- When the user wants to inspect component source code without installing — use `--view`.\n- When checking what CSS changes would be made to `globals.css` — use `--diff globals.css`.\n- When the user asks to review or audit third-party registry code before installing — use `--view` to inspect the source.\n\n> **`npx shadcn@latest add --dry-run` vs `npx shadcn@latest view`:** Prefer `npx shadcn@latest add --dry-run/--diff/--view` over `npx shadcn@latest view` when the user wants to preview changes to their project. `npx shadcn@latest view` only shows raw registry metadata. `npx shadcn@latest add --dry-run` shows exactly what would happen in the user's project: resolved file paths, diffs against existing files, and CSS updates. Use `npx shadcn@latest view` only when the user wants to browse registry info without a project context.\n\n#### Smart Merge from Upstream\n\nSee [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full workflow.\n\n### `search` — Search registries\n\n```bash\nnpx shadcn@latest search [registries...] [options]\n```\n\nFuzzy search across registries. Also aliased as `npx shadcn@latest list`. Supports namespaces (`@acme`), public GitHub registry sources (`owner/repo`), and registry catalog URLs. Without `-q`, lists all items. When no registries are passed, searches every registry configured in `components.json`.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--query <query>` | `-q` | Search query | — |\n| `--type <type>` | `-t` | Filter by item type (e.g. `ui`, `block`, `hook`); comma-separated | — |\n| `--limit <number>` | `-l` | Max items to display | `100` |\n| `--offset <number>` | `-o` | Items to skip | `0` |\n| `--json` |  | Output as JSON | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n\n### `view` — View item details\n\n```bash\nnpx shadcn@latest view <items...> [options]\n```\n\nDisplays item info including file contents. Examples: `npx shadcn@latest view @shadcn/button`, `npx shadcn@latest view owner/repo/item`.\n\n### `docs` — Get component documentation URLs\n\n```bash\nnpx shadcn@latest docs <components...> [options]\n```\n\nOutputs resolved URLs for component documentation, examples, and API references. Accepts one or more component names. Fetch the URLs to get the actual content.\n\nExample output for `npx shadcn@latest docs input button`:\n\n```\nbase  radix\n\ninput\n  docs      https://ui.shadcn.com/docs/components/radix/input\n  examples  https://raw.githubusercontent.com/.../examples/input-example.tsx\n\nbutton\n  docs      https://ui.shadcn.com/docs/components/radix/button\n  examples  https://raw.githubusercontent.com/.../examples/button-example.tsx\n```\n\nSome components include an `api` link to the underlying library (e.g. `cmdk` for the command component).\n\n### `diff` — Check for updates\n\nDo not use this command. Use `npx shadcn@latest add --diff` instead.\n\n### `info` — Project information\n\n```bash\nnpx shadcn@latest info [options]\n```\n\nDisplays project info and `components.json` configuration. Run this first to discover the project's framework, aliases, Tailwind version, and resolved paths.\n\n| Flag          | Short | Description       | Default |\n| ------------- | ----- | ----------------- | ------- |\n| `--cwd <cwd>` | `-c`  | Working directory | current |\n\n**Project Info fields:**\n\n| Field | Type | Meaning |\n| --- | --- | --- |\n| `framework` | `string` | Detected framework (`next`, `vite`, `react-router`, `start`, etc.) |\n| `frameworkVersion` | `string` | Framework version (e.g. `15.2.4`) |\n| `isSrcDir` | `boolean` | Whether the project uses a `src/` directory |\n| `isRSC` | `boolean` | Whether React Server Components are enabled |\n| `isTsx` | `boolean` | Whether the project uses TypeScript |\n| `tailwindVersion` | `string` | `\"v3\"` or `\"v4\"` |\n| `tailwindConfigFile` | `string` | Path to the Tailwind config file |\n| `tailwindCssFile` | `string` | Path to the global CSS file |\n| `aliasPrefix` | `string` | Import alias prefix (e.g. `@`, `~`, `@/`) |\n| `packageManager` | `string` | Detected package manager (`npm`, `pnpm`, `yarn`, `bun`) |\n\n**Components.json fields:**\n\n| Field | Type | Meaning |\n| --- | --- | --- |\n| `base` | `string` | Primitive library (`radix` or `base`) — determines component APIs and available props |\n| `style` | `string` | Visual style (e.g. `nova`, `vega`) |\n| `rsc` | `boolean` | RSC flag from config |\n| `tsx` | `boolean` | TypeScript flag |\n| `tailwind.config` | `string` | Tailwind config path |\n| `tailwind.css` | `string` | Global CSS path — this is where custom CSS variables go |\n| `iconLibrary` | `string` | Icon library — determines icon import package (e.g. `lucide-react`, `@tabler/icons-react`) |\n| `aliases.components` | `string` | Component import alias (e.g. `@/components`) |\n| `aliases.utils` | `string` | Utils import alias (e.g. `@/lib/utils`) |\n| `aliases.ui` | `string` | UI component alias (e.g. `@/components/ui`) |\n| `aliases.lib` | `string` | Lib alias (e.g. `@/lib`) |\n| `aliases.hooks` | `string` | Hooks alias (e.g. `@/hooks`) |\n| `resolvedPaths` | `object` | Absolute file-system paths for each alias |\n| `registries` | `object` | Configured custom registries |\n\n**Links fields:**\n\nThe `info` output includes a **Links** section with templated URLs for component docs, source, and examples. For resolved URLs, use `npx shadcn@latest docs <component>` instead.\n\n### `build` — Build a custom registry\n\n```bash\nnpx shadcn@latest build [registry] [options]\n```\n\nBuilds `registry.json` into individual JSON files for distribution. Default input: `./registry.json`, default output: `./public/r`.\n\nFor authoring rules, `include`, item definitions, `registryDependencies`, and GitHub registry behavior, see [registry.md](./registry.md).\n\n| Flag              | Short | Description       | Default      |\n| ----------------- | ----- | ----------------- | ------------ |\n| `--output <path>` | `-o`  | Output directory  | `./public/r` |\n| `--cwd <cwd>`     | `-c`  | Working directory | current      |\n\n---\n\n## Templates\n\n| Value          | Framework      | Monorepo support |\n| -------------- | -------------- | ---------------- |\n| `next`         | Next.js        | Yes              |\n| `vite`         | Vite           | Yes              |\n| `start`        | TanStack Start | Yes              |\n| `react-router` | React Router   | Yes              |\n| `astro`        | Astro          | Yes              |\n| `laravel`      | Laravel        | No               |\n\nAll templates support monorepo scaffolding via the `--monorepo` flag. When passed, the CLI uses a monorepo-specific template directory (e.g. `next-monorepo`, `vite-monorepo`). When neither `--monorepo` nor `--no-monorepo` is passed, the CLI prompts interactively. Laravel does not support monorepo scaffolding.\n\n---\n\n## Presets\n\nThree ways to specify a preset via `--preset`:\n\n1. **Named:** `--preset nova` or `--preset lyra`\n2. **Code:** `--preset a2r6bw` (version-prefixed base62 string, e.g. `a2r6bw` or `b0`)\n3. **URL:** `--preset \"https://ui.shadcn.com/init?base=radix&style=nova&...\"`\n\n> **IMPORTANT:** Never try to decode, fetch, or resolve preset codes manually. Preset codes are opaque — pass them directly to `npx shadcn@latest init --preset <code>` and let the CLI handle resolution. Use `npx shadcn@latest apply --preset <code>` when overwriting an existing project's preset.\n\n## Switching Presets\n\nAsk the user first: **overwrite**, **merge**, or **skip** existing components?\n\n- **Overwrite / Re-install** → `npx shadcn@latest apply --preset <code>`. Overwrites all detected component files with the new preset styles. Use when the user hasn't customized components.\n- **Merge** → `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to get the list of installed components and use the [smart merge workflow](./SKILL.md#updating-components) to update them one by one, preserving local changes. Use when the user has customized components.\n- **Skip** → `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS variables, leaves existing components as-is.\n\nAlways run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.\n","isInternal":false,"tokens":3677,"sizeBytes":13588},{"name":"customization.md","path":".agents/skills/shadcn/customization.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.agents/skills/shadcn/customization.md","title":"Shadcn Skill","category":"anthropic-skill","format":"markdown","content":"# Customization & Theming\n\nComponents reference semantic CSS variable tokens. Change the variables to change every component.\n\n## Contents\n\n- How it works (CSS variables → Tailwind utilities → components)\n- Color variables and OKLCH format\n- Dark mode setup\n- Changing the theme (presets, CSS variables)\n- Adding custom colors (Tailwind v3 and v4)\n- Border radius\n- Customizing components (variants, className, wrappers)\n- Checking for updates\n\n---\n\n## How It Works\n\n1. CSS variables defined in `:root` (light) and `.dark` (dark mode).\n2. Tailwind maps them to utilities: `bg-primary`, `text-muted-foreground`, etc.\n3. Components use these utilities — changing a variable changes all components that reference it.\n\n---\n\n## Color Variables\n\nEvery color follows the `name` / `name-foreground` convention. The base variable is for backgrounds, `-foreground` is for text/icons on that background.\n\n| Variable | Purpose |\n| --- | --- |\n| `--background` / `--foreground` | Page background and default text |\n| `--card` / `--card-foreground` | Card surfaces |\n| `--primary` / `--primary-foreground` | Primary buttons and actions |\n| `--secondary` / `--secondary-foreground` | Secondary actions |\n| `--muted` / `--muted-foreground` | Muted/disabled states |\n| `--accent` / `--accent-foreground` | Hover and accent states |\n| `--destructive` / `--destructive-foreground` | Error and destructive actions |\n| `--border` | Default border color |\n| `--input` | Form input borders |\n| `--ring` | Focus ring color |\n| `--chart-1` through `--chart-5` | Chart/data visualization |\n| `--sidebar-*` | Sidebar-specific colors |\n| `--surface` / `--surface-foreground` | Secondary surface |\n\nColors use OKLCH: `--primary: oklch(0.205 0 0)` where values are lightness (0–1), chroma (0 = gray), and hue (0–360).\n\n---\n\n## Dark Mode\n\nClass-based toggle via `.dark` on the root element. In Next.js, use `next-themes`:\n\n```tsx\nimport { ThemeProvider } from \"next-themes\";\n\n<ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n  {children}\n</ThemeProvider>;\n```\n\n---\n\n## Changing the Theme\n\n```bash\n# Apply a preset code from ui.shadcn.com.\nnpx shadcn@latest apply --preset a2r6bw\n\n# Positional shorthand also works.\nnpx shadcn@latest apply a2r6bw\n\n# Switch to a named preset and overwrite existing components.\nnpx shadcn@latest apply --preset nova\n\n# Preserve existing components instead.\nnpx shadcn@latest init --preset nova --force --no-reinstall\n\n# Use a custom theme URL.\nnpx shadcn@latest apply --preset \"https://ui.shadcn.com/init?base=radix&style=nova&theme=blue&...\"\n```\n\nOr edit CSS variables directly in `globals.css`.\n\n---\n\n## Adding Custom Colors\n\nAdd variables to the file at `tailwindCssFile` from `npx shadcn@latest info` (typically `globals.css`). Never create a new CSS file for this.\n\n```css\n/* 1. Define in the global CSS file. */\n:root {\n  --warning: oklch(0.84 0.16 84);\n  --warning-foreground: oklch(0.28 0.07 46);\n}\n.dark {\n  --warning: oklch(0.41 0.11 46);\n  --warning-foreground: oklch(0.99 0.02 95);\n}\n```\n\n```css\n/* 2a. Register with Tailwind v4 (@theme inline). */\n@theme inline {\n  --color-warning: var(--warning);\n  --color-warning-foreground: var(--warning-foreground);\n}\n```\n\nWhen `tailwindVersion` is `\"v3\"` (check via `npx shadcn@latest info`), register in `tailwind.config.js` instead:\n\n```js\n// 2b. Register with Tailwind v3 (tailwind.config.js).\nmodule.exports = {\n  theme: {\n    extend: {\n      colors: {\n        warning: \"oklch(var(--warning) / <alpha-value>)\",\n        \"warning-foreground\":\n          \"oklch(var(--warning-foreground) / <alpha-value>)\",\n      },\n    },\n  },\n};\n```\n\n```tsx\n// 3. Use in components.\n<div className=\"bg-warning text-warning-foreground\">Warning</div>\n```\n\n---\n\n## Border Radius\n\n`--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`).\n\n---\n\n## Customizing Components\n\nSee also: [rules/styling.md](./rules/styling.md) for Incorrect/Correct examples.\n\nPrefer these approaches in order:\n\n### 1. Built-in variants\n\n```tsx\n<Button variant=\"outline\" size=\"sm\">\n  Click\n</Button>\n```\n\n### 2. Tailwind classes via `className`\n\n```tsx\n<Card className=\"mx-auto max-w-md\">...</Card>\n```\n\n### 3. Add a new variant\n\nEdit the component source to add a variant via `cva`:\n\n```tsx\n// components/ui/button.tsx\nwarning: \"bg-warning text-warning-foreground hover:bg-warning/90\",\n```\n\n### 4. Wrapper components\n\nCompose shadcn/ui primitives into higher-level components:\n\n```tsx\nexport function ConfirmDialog({ title, description, onConfirm, children }) {\n  return (\n    <AlertDialog>\n      <AlertDialogTrigger asChild>{children}</AlertDialogTrigger>\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogTitle>{title}</AlertDialogTitle>\n          <AlertDialogDescription>{description}</AlertDialogDescription>\n        </AlertDialogHeader>\n        <AlertDialogFooter>\n          <AlertDialogCancel>Cancel</AlertDialogCancel>\n          <AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n  );\n}\n```\n\n---\n\n## Checking for Updates\n\n```bash\nnpx shadcn@latest add button --diff\n```\n\nTo preview exactly what would change before updating, use `--dry-run` and `--diff`:\n\n```bash\nnpx shadcn@latest add button --dry-run        # see all affected files\nnpx shadcn@latest add button --diff button.tsx # see the diff for a specific file\n```\n\nSee [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full smart merge workflow.\n","isInternal":false,"tokens":1460,"sizeBytes":5593},{"name":"evals.json","path":".agents/skills/shadcn/evals/evals.json","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.agents/skills/shadcn/evals/evals.json","title":"Evals Skill","category":"anthropic-skill","format":"json","content":"{\n  \"skill_name\": \"shadcn\",\n  \"evals\": [\n    {\n      \"id\": 1,\n      \"prompt\": \"I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.\",\n      \"expected_output\": \"A React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses FieldGroup and Field components for form layout instead of raw div with space-y\",\n        \"Uses Switch for independent on/off notification toggles (not looping Button with manual active state)\",\n        \"Uses data-invalid on Field and aria-invalid on the input control for validation states\",\n        \"Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing\",\n        \"Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500\",\n        \"No manual dark: color overrides\"\n      ]\n    },\n    {\n      \"id\": 2,\n      \"prompt\": \"Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn/ui with radix-nova preset and tabler icons.\",\n      \"expected_output\": \"A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Includes DialogTitle for accessibility (visible or with sr-only class)\",\n        \"Avatar component includes AvatarFallback\",\n        \"Icons on buttons use the data-icon attribute (data-icon=\\\"inline-start\\\" or data-icon=\\\"inline-end\\\")\",\n        \"No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)\",\n        \"Uses tabler icons (@tabler/icons-react) instead of lucide-react\",\n        \"Uses asChild for custom triggers (radix preset)\"\n      ]\n    },\n    {\n      \"id\": 3,\n      \"prompt\": \"Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn/ui with base-nova preset and lucide icons.\",\n      \"expected_output\": \"A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)\",\n        \"Uses Skeleton component for loading placeholders instead of custom animate-pulse divs\",\n        \"Uses Badge component for percentage change instead of custom styled spans\",\n        \"Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600\",\n        \"Uses gap-* instead of space-y-* or space-x-* for spacing\",\n        \"Uses size-* when width and height are equal instead of separate w-* h-*\"\n      ]\n    },\n    {\n      \"id\": 4,\n      \"prompt\": \"I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Build a chat conversation view: a scrollable thread of messages from two different people, each with an avatar, sender name, timestamp, and message bubble. A couple of messages include an image attachment and a PDF file attachment, and there's a 'Today' divider separating the days.\",\n      \"expected_output\": \"A React component composing MessageScroller, Message, Bubble, Attachment, and Marker from the registry instead of hand-rolled bubble/divider/attachment markup.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses MessageScroller (MessageScrollerProvider, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem) for the scrollable thread instead of a raw overflow-y-auto div or ScrollArea\",\n        \"Wraps each row in MessageScrollerItem inside MessageScrollerContent\",\n        \"Uses Message with MessageAvatar/MessageContent/MessageHeader for row layout instead of custom flex divs\",\n        \"Uses Bubble + BubbleContent for the message surface instead of a styled div with bg-muted/bg-primary\",\n        \"Uses Attachment (AttachmentMedia, AttachmentContent, AttachmentTitle, AttachmentDescription) for the file and image attachments instead of Item or a custom card\",\n        \"Uses Marker (variant=\\\"separator\\\") for the 'Today' divider instead of Separator plus a centered label\",\n        \"Uses semantic color tokens and gap-* spacing; no raw colors like bg-emerald-500 and no space-y-*\",\n        \"Includes \\\"use client\\\" when the component uses state or event handlers (isRSC)\"\n      ]\n    },\n    {\n      \"id\": 5,\n      \"prompt\": \"Using shadcn/ui (base-nova preset, lucide icons), build a streaming AI chat UI. The assistant's reply streams in while it generates, the view auto-scrolls to follow the latest content but stops following if the user scrolls up to read earlier messages, a 'jump to latest' button appears when the user has scrolled away from the bottom, and a subtle 'thinking…' shimmer shows while the model is generating.\",\n      \"expected_output\": \"A React component that delegates scroll/anchor behavior to MessageScroller and uses MessageScrollerButton for jump-to-latest and the shimmer utility for the thinking indicator — no hand-rolled scroll logic or custom shimmer keyframes.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses MessageScroller with MessageScrollerProvider (autoScroll) and scrollAnchor on message items for the stick-to-bottom/follow behavior instead of a custom useStickToBottom hook or ResizeObserver/scrollTop wiring\",\n        \"Uses MessageScrollerButton for the jump-to-latest control instead of a hand-built conditional button driven by manual scroll-position state\",\n        \"Uses the shimmer utility class for the 'thinking…' indicator instead of a custom @keyframes or bg-clip-text gradient animation\",\n        \"Wraps each message row in MessageScrollerItem inside MessageScrollerContent\",\n        \"Uses Message + Bubble + BubbleContent for the conversation rows instead of hand-rolled bubble divs\",\n        \"Uses semantic color tokens and gap-* spacing; includes \\\"use client\\\" (isRSC)\"\n      ]\n    }\n  ]\n}\n","frontmatter":{"skill_name":"shadcn","evals":[{"id":1,"prompt":"I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.","expected_output":"A React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.","files":[],"expectations":["Uses FieldGroup and Field components for form layout instead of raw div with space-y","Uses Switch for independent on/off notification toggles (not looping Button with manual active state)","Uses data-invalid on Field and aria-invalid on the input control for validation states","Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing","Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500","No manual dark: color overrides"]},{"id":2,"prompt":"Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn/ui with radix-nova preset and tabler icons.","expected_output":"A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.","files":[],"expectations":["Includes DialogTitle for accessibility (visible or with sr-only class)","Avatar component includes AvatarFallback","Icons on buttons use the data-icon attribute (data-icon=\"inline-start\" or data-icon=\"inline-end\")","No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)","Uses tabler icons (@tabler/icons-react) instead of lucide-react","Uses asChild for custom triggers (radix preset)"]},{"id":3,"prompt":"Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn/ui with base-nova preset and lucide icons.","expected_output":"A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.","files":[],"expectations":["Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)","Uses Skeleton component for loading placeholders instead of custom animate-pulse divs","Uses Badge component for percentage change instead of custom styled spans","Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600","Uses gap-* instead of space-y-* or space-x-* for spacing","Uses size-* when width and height are equal instead of separate w-* h-*"]},{"id":4,"prompt":"I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Build a chat conversation view: a scrollable thread of messages from two different people, each with an avatar, sender name, timestamp, and message bubble. A couple of messages include an image attachment and a PDF file attachment, and there's a 'Today' divider separating the days.","expected_output":"A React component composing MessageScroller, Message, Bubble, Attachment, and Marker from the registry instead of hand-rolled bubble/divider/attachment markup.","files":[],"expectations":["Uses MessageScroller (MessageScrollerProvider, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem) for the scrollable thread instead of a raw overflow-y-auto div or ScrollArea","Wraps each row in MessageScrollerItem inside MessageScrollerContent","Uses Message with MessageAvatar/MessageContent/MessageHeader for row layout instead of custom flex divs","Uses Bubble + BubbleContent for the message surface instead of a styled div with bg-muted/bg-primary","Uses Attachment (AttachmentMedia, AttachmentContent, AttachmentTitle, AttachmentDescription) for the file and image attachments instead of Item or a custom card","Uses Marker (variant=\"separator\") for the 'Today' divider instead of Separator plus a centered label","Uses semantic color tokens and gap-* spacing; no raw colors like bg-emerald-500 and no space-y-*","Includes \"use client\" when the component uses state or event handlers (isRSC)"]},{"id":5,"prompt":"Using shadcn/ui (base-nova preset, lucide icons), build a streaming AI chat UI. The assistant's reply streams in while it generates, the view auto-scrolls to follow the latest content but stops following if the user scrolls up to read earlier messages, a 'jump to latest' button appears when the user has scrolled away from the bottom, and a subtle 'thinking…' shimmer shows while the model is generating.","expected_output":"A React component that delegates scroll/anchor behavior to MessageScroller and uses MessageScrollerButton for jump-to-latest and the shimmer utility for the thinking indicator — no hand-rolled scroll logic or custom shimmer keyframes.","files":[],"expectations":["Uses MessageScroller with MessageScrollerProvider (autoScroll) and scrollAnchor on message items for the stick-to-bottom/follow behavior instead of a custom useStickToBottom hook or ResizeObserver/scrollTop wiring","Uses MessageScrollerButton for the jump-to-latest control instead of a hand-built conditional button driven by manual scroll-position state","Uses the shimmer utility class for the 'thinking…' indicator instead of a custom @keyframes or bg-clip-text gradient animation","Wraps each message row in MessageScrollerItem inside MessageScrollerContent","Uses Message + Bubble + BubbleContent for the conversation rows instead of hand-rolled bubble divs","Uses semantic color tokens and gap-* spacing; includes \"use client\" (isRSC)"]}]},"isInternal":false,"tokens":1387,"sizeBytes":6330},{"name":"mcp.md","path":".agents/skills/shadcn/mcp.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.agents/skills/shadcn/mcp.md","title":"Shadcn Skill","category":"anthropic-skill","format":"markdown","content":"# shadcn MCP Server\n\nThe CLI includes an MCP server that lets AI assistants search, browse, view, and install items from registries.\n\n---\n\n## Setup\n\n```bash\nshadcn mcp        # start the MCP server (stdio)\nshadcn mcp init   # write config for your editor\n```\n\nEditor config files:\n\n| Editor      | Config file                     |\n| ----------- | ------------------------------- |\n| Claude Code | `.mcp.json`                     |\n| Cursor      | `.cursor/mcp.json`              |\n| VS Code     | `.vscode/mcp.json`              |\n| OpenCode    | `opencode.json`                 |\n| Codex       | `~/.codex/config.toml` (manual) |\n\n---\n\n## Tools\n\n> **Tip:** MCP tools handle registry operations (search, view, install). For project configuration (aliases, framework, Tailwind version), use `npx shadcn@latest info` — there is no MCP equivalent.\n\n### `shadcn:get_project_registries`\n\nReturns registry names from `components.json`. Errors if no `components.json` exists.\n\n**Input:** none\n\n### `shadcn:list_items_in_registries`\n\nLists all items from one or more registries. Registries can be configured namespaces such as `@acme`, public GitHub sources such as `owner/repo`, or registry catalog URLs. Omit `registries` to list from every registry configured in `components.json`.\n\n**Input:** `registries` (string[], optional — omit for all configured), `types` (string[], optional — e.g. `[\"ui\", \"block\"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)\n\n### `shadcn:search_items_in_registries`\n\nFuzzy search across registries. Registries can be configured namespaces, public GitHub sources, or registry catalog URLs. Omit `registries` to search every registry configured in `components.json` — e.g. \"find me a hero\" across all configured registries.\n\n**Input:** `registries` (string[], optional — omit for all configured), `query` (string), `types` (string[], optional — e.g. `[\"ui\", \"block\"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)\n\n### `shadcn:view_items_in_registries`\n\nView item details including full file contents.\n\n**Input:** `items` (string[]) — e.g. `[\"@shadcn/button\", \"@shadcn/card\", \"owner/repo/item\"]`\n\n### `shadcn:get_item_examples_from_registries`\n\nFind usage examples and demos with source code. Omit `registries` to search every registry configured in `components.json`.\n\n**Input:** `registries` (string[], optional — omit for all configured), `query` (string) — e.g. `\"accordion-demo\"`, `\"button example\"`\n\n### `shadcn:get_add_command_for_items`\n\nReturns the CLI install command.\n\n**Input:** `items` (string[]) — e.g. `[\"@shadcn/button\"]`\n\n### `shadcn:get_audit_checklist`\n\nReturns a checklist for verifying components (imports, deps, lint, TypeScript).\n\n**Input:** none\n\n---\n\n## Configuring Registries\n\nNamespaced and authenticated registries are set in `components.json`. The `@shadcn` registry is always built-in. Public GitHub registries can also be used directly as `owner/repo` registry sources when the repository has a root `registry.json`; they do not need `components.json` configuration.\n\n```json\n{\n  \"registries\": {\n    \"@acme\": \"https://acme.com/r/{name}.json\",\n    \"@private\": {\n      \"url\": \"https://private.com/r/{name}.json\",\n      \"headers\": { \"Authorization\": \"Bearer ${MY_TOKEN}\" }\n    }\n  }\n}\n```\n\n- Names must start with `@`.\n- URLs must contain `{name}`.\n- `${VAR}` references are resolved from environment variables.\n\nCommunity registry index: `https://ui.shadcn.com/r/registries.json`\n","isInternal":false,"tokens":874,"sizeBytes":3510},{"name":"registry.md","path":".agents/skills/shadcn/registry.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.agents/skills/shadcn/registry.md","title":"Shadcn Skill","category":"anthropic-skill","format":"markdown","content":"# Registry Authoring and Addresses\n\nUse this reference when the user wants to create, fix, publish, or reason about a shadcn registry.\n\n## Mental Model\n\nA registry has two forms:\n\n- **Source registry**: an authored `registry.json` in a project or repository. It may use `include` and file paths that point at source files.\n- **Built registry**: generated JSON files served to CLI consumers, usually from `public/r`. Use `npx shadcn@latest build` to create this form.\n\nThe CLI installer consumes registry item payloads. A source registry is a way to author those payloads from real files.\n\nRegistry items are not limited to React components. They can distribute components, hooks, utilities, design tokens, pages, config files, docs, rules, workflows, templates, MCP files, and other project files.\n\n## Root `registry.json`\n\nThe root registry file should define registry metadata and either `items` or `include`.\n\n```json\n{\n  \"$schema\": \"https://ui.shadcn.com/schema/registry.json\",\n  \"name\": \"acme\",\n  \"homepage\": \"https://acme.com\",\n  \"items\": [\n    {\n      \"name\": \"absolute-url\",\n      \"type\": \"registry:lib\",\n      \"title\": \"Absolute URL\",\n      \"description\": \"A utility to turn any path into an absolute URL.\",\n      \"files\": [\n        {\n          \"path\": \"lib/absolute-url.ts\",\n          \"type\": \"registry:lib\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nRoot registry rules:\n\n- Root `registry.json` must include `name` and `homepage`.\n- `items` is an array of registry item definitions.\n- `include` may be used to split the source registry into multiple files.\n- Included registry files may omit `name` and `homepage`.\n\n## Include\n\nUse `include` to keep large registries modular.\n\n```json\n{\n  \"$schema\": \"https://ui.shadcn.com/schema/registry.json\",\n  \"name\": \"acme\",\n  \"homepage\": \"https://acme.com\",\n  \"include\": [\"registry/ui/registry.json\", \"registry/blocks/registry.json\"]\n}\n```\n\nInclude rules:\n\n- Include paths are relative to the `registry.json` that declares them.\n- Include paths must explicitly point to a `registry.json` file.\n- Do not use remote URLs, absolute paths, or parent traversal (`..`).\n- Item file paths are relative to the registry file that declares the item.\n- Duplicate item names fail across the resolved registry.\n\nExample included file:\n\n```json\n{\n  \"items\": [\n    {\n      \"name\": \"button\",\n      \"type\": \"registry:ui\",\n      \"files\": [\n        {\n          \"path\": \"button.tsx\",\n          \"type\": \"registry:ui\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nIf this file is at `registry/ui/registry.json`, then `button.tsx` is read from `registry/ui/button.tsx`, and the built item path is emitted relative to the root registry.\n\n## Item Definitions\n\nCommon item fields:\n\n```json\n{\n  \"name\": \"login-form\",\n  \"type\": \"registry:block\",\n  \"title\": \"Login Form\",\n  \"description\": \"A login form with email and password fields.\",\n  \"dependencies\": [\"zod\"],\n  \"registryDependencies\": [\"button\", \"input\", \"label\"],\n  \"files\": [\n    {\n      \"path\": \"blocks/login-form.tsx\",\n      \"type\": \"registry:block\"\n    }\n  ],\n  \"cssVars\": {\n    \"light\": {\n      \"brand\": \"oklch(0.62 0.18 250)\"\n    },\n    \"dark\": {\n      \"brand\": \"oklch(0.72 0.16 250)\"\n    }\n  }\n}\n```\n\nImportant fields:\n\n- `name`: the installable item name. It is not necessarily a file path.\n- `type`: one of the registry item types, such as `registry:ui`, `registry:block`, `registry:lib`, `registry:hook`, `registry:file`, `registry:page`, `registry:theme`, `registry:style`, `registry:font`, or `registry:item`.\n- `files`: source files copied or generated by the item.\n- `dependencies`: npm runtime dependencies.\n- `devDependencies`: npm development dependencies.\n- `registryDependencies`: other registry items required by this item.\n- `cssVars`, `css`, `tailwind`, `envVars`, and `docs`: optional install-time additions.\n\nFile rules:\n\n- File paths are relative to the declaring `registry.json`.\n- `registry:file` and `registry:page` files require a `target`.\n- Do not use remote file URLs in source registry file paths.\n- Keep source files copy-pasteable: no hidden app-only imports.\n\n## Registry Dependencies\n\n`registryDependencies` entries are item addresses, not file paths.\n\n```json\n{\n  \"name\": \"login-form\",\n  \"type\": \"registry:block\",\n  \"registryDependencies\": [\"button\", \"@acme/input\", \"acme/ui/card#v1.2.0\"],\n  \"files\": [\n    {\n      \"path\": \"blocks/login-form.tsx\",\n      \"type\": \"registry:block\"\n    }\n  ]\n}\n```\n\nDependency rules:\n\n- Bare names such as `\"button\"` mean official shadcn items.\n- Bare names never mean same-registry or same-repository items.\n- Namespaced dependencies use `@namespace/item-name`.\n- GitHub dependencies use `owner/repo/item-name`.\n- Pin GitHub dependencies with `owner/repo/item-name#ref` when needed.\n- Refs are not inherited. If `owner/repo/foo#v2` depends on `bar` from the same repo at `v2`, write `owner/repo/bar#v2`.\n- Do not use relative dependencies such as `\"./bar\"`.\n\n## Address Schemes\n\nWhen reasoning about a registry item string, classify it first.\n\n| Address | Scheme | Meaning |\n| --- | --- | --- |\n| `button` | shadcn | Official shadcn item named `button`. |\n| `@acme/button` | namespace | Item `button` from configured registry `@acme`. |\n| `@acme/ui/button` | namespace | Item `ui/button` from configured registry `@acme`. |\n| `https://example.com/r/button.json` | url | Built registry item JSON at that URL. |\n| `./button.json` | file | Built registry item JSON on disk. |\n| `acme/ui/button` | github | Item `button` from GitHub repo `acme/ui`. |\n| `acme/ui/forms/login#main` | github | Item `forms/login` from GitHub repo `acme/ui` at ref `main`. |\n\nFor namespace and GitHub addresses, slashful item names are allowed and are item names, not file paths. Addresses ending in `.json` keep file-address precedence, so `acme/ui/data/schema.json` is treated as a file path, not a GitHub item address.\n\n## GitHub Registries\n\nA public GitHub repository can act as a source registry when it has a root `registry.json`.\n\n```txt\nowner/repo/item-name[#ref]\n```\n\nRules:\n\n- The first two path segments are GitHub owner and repo.\n- All remaining path segments are the registry item name.\n- The source entrypoint is always root `registry.json`.\n- GitHub registries are source registries consumed directly by the CLI. They do not require `shadcn build` or generated item JSON files.\n- `include` follows the same source-registry rules as local registries.\n- Currently, GitHub addresses support public `github.com` repositories only.\n- Private repos and GitHub Enterprise require explicit product decisions.\n\nWhen implementing GitHub registry fetching, resolve refs to a commit SHA before reading source files. Do not read moving refs directly from `raw.githubusercontent.com`, because branch-like refs can be cached for several minutes.\n\nPreferred flow:\n\n```txt\nowner/repo[#ref]\n  -> resolve ref with git ls-remote\n  -> commit SHA\n  -> read https://raw.githubusercontent.com/{owner}/{repo}/{sha}/registry.json\n  -> read includes and item files from the same SHA\n```\n\nThis keeps a command on one consistent repository snapshot.\n\nFull 40-character commit SHAs are already stable and can be used directly. Branches, tags, and short refs require Git so the CLI can resolve them to a commit SHA first.\n\n## Build and Verify\n\nUse the CLI to build source registries:\n\n```bash\nnpx shadcn@latest build\nnpx shadcn@latest build registry.json --output public/r\n```\n\nUse CLI commands to inspect the result:\n\n```bash\nnpx shadcn@latest list @acme\nnpx shadcn@latest search @acme -q \"login\"\nnpx shadcn@latest view @acme/login-form\nnpx shadcn@latest add @acme/login-form --dry-run\nnpx shadcn@latest registry validate ./registry.json\n```\n\nUse GitHub addresses directly for public GitHub registries:\n\n```bash\nnpx shadcn@latest list owner/repo\nnpx shadcn@latest search owner/repo -q \"login\"\nnpx shadcn@latest view owner/repo/item\nnpx shadcn@latest add owner/repo/item --dry-run\nnpx shadcn@latest registry validate owner/repo\n```\n\nWhen working on registry implementation in the shadcn/ui codebase:\n\n- Keep address parsing pure and testable.\n- Do not add side effects to validators.\n- Preserve existing behavior for official shadcn, namespace, URL, and file schemes.\n- Add tests for address parsing, source loading, dependency resolution, list, search, view, and add paths.\n- Prefer small source-reader abstractions over a plugin system until there are multiple real providers.\n","isInternal":false,"tokens":2079,"sizeBytes":8366},{"name":"base-vs-radix.md","path":".agents/skills/shadcn/rules/base-vs-radix.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.agents/skills/shadcn/rules/base-vs-radix.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Base vs Radix\n\nAPI differences between `base` and `radix`. Check the `base` field from `npx shadcn@latest info`.\n\n## Contents\n\n- Composition: asChild vs render\n- Button / trigger as non-button element\n- Select (items prop, placeholder, positioning, multiple, object values)\n- ToggleGroup (type vs multiple)\n- Slider (scalar vs array)\n- Accordion (type and defaultValue)\n\n---\n\n## Composition: asChild (radix) vs render (base)\n\nRadix uses `asChild` to replace the default element. Base uses `render`. Don't wrap triggers in extra elements.\n\n**Incorrect:**\n\n```tsx\n<DialogTrigger>\n  <div>\n    <Button>Open</Button>\n  </div>\n</DialogTrigger>\n```\n\n**Correct (radix):**\n\n```tsx\n<DialogTrigger asChild>\n  <Button>Open</Button>\n</DialogTrigger>\n```\n\n**Correct (base):**\n\n```tsx\n<DialogTrigger render={<Button />}>Open</DialogTrigger>\n```\n\nThis applies to all trigger and close components: `DialogTrigger`, `SheetTrigger`, `AlertDialogTrigger`, `DropdownMenuTrigger`, `PopoverTrigger`, `TooltipTrigger`, `CollapsibleTrigger`, `DialogClose`, `SheetClose`, `NavigationMenuLink`, `BreadcrumbLink`, `SidebarMenuButton`, `Badge`, `Item`.\n\n---\n\n## Button / trigger as non-button element (base only)\n\nWhen `render` changes an element to a non-button (`<a>`, `<span>`), add `nativeButton={false}`.\n\n**Incorrect (base):** missing `nativeButton={false}`.\n\n```tsx\n<Button render={<a href=\"/docs\" />}>Read the docs</Button>\n```\n\n**Correct (base):**\n\n```tsx\n<Button render={<a href=\"/docs\" />} nativeButton={false}>\n  Read the docs\n</Button>\n```\n\n**Correct (radix):**\n\n```tsx\n<Button asChild>\n  <a href=\"/docs\">Read the docs</a>\n</Button>\n```\n\nSame for triggers whose `render` is not a `Button`:\n\n```tsx\n// base.\n<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>\n  Pick date\n</PopoverTrigger>\n```\n\n---\n\n## Select\n\n**items prop (base only).** Base requires an `items` prop on the root. Radix uses inline JSX only.\n\n**Incorrect (base):**\n\n```tsx\n<Select>\n  <SelectTrigger>\n    <SelectValue placeholder=\"Select a fruit\" />\n  </SelectTrigger>\n</Select>\n```\n\n**Correct (base):**\n\n```tsx\nconst items = [\n  { label: \"Select a fruit\", value: null },\n  { label: \"Apple\", value: \"apple\" },\n  { label: \"Banana\", value: \"banana\" },\n]\n\n<Select items={items}>\n  <SelectTrigger>\n    <SelectValue />\n  </SelectTrigger>\n  <SelectContent>\n    <SelectGroup>\n      {items.map((item) => (\n        <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>\n      ))}\n    </SelectGroup>\n  </SelectContent>\n</Select>\n```\n\n**Correct (radix):**\n\n```tsx\n<Select>\n  <SelectTrigger>\n    <SelectValue placeholder=\"Select a fruit\" />\n  </SelectTrigger>\n  <SelectContent>\n    <SelectGroup>\n      <SelectItem value=\"apple\">Apple</SelectItem>\n      <SelectItem value=\"banana\">Banana</SelectItem>\n    </SelectGroup>\n  </SelectContent>\n</Select>\n```\n\n**Placeholder.** Base uses a `{ value: null }` item in the items array. Radix uses `<SelectValue placeholder=\"...\">`.\n\n**Content positioning.** Base uses `alignItemWithTrigger`. Radix uses `position`.\n\n```tsx\n// base.\n<SelectContent alignItemWithTrigger={false} side=\"bottom\">\n\n// radix.\n<SelectContent position=\"popper\">\n```\n\n---\n\n## Select — multiple selection and object values (base only)\n\nBase supports `multiple`, render-function children on `SelectValue`, and object values with `itemToStringValue`. Radix is single-select with string values only.\n\n**Correct (base — multiple selection):**\n\n```tsx\n<Select items={items} multiple defaultValue={[]}>\n  <SelectTrigger>\n    <SelectValue>\n      {(value: string[]) =>\n        value.length === 0 ? \"Select fruits\" : `${value.length} selected`\n      }\n    </SelectValue>\n  </SelectTrigger>\n  ...\n</Select>\n```\n\n**Correct (base — object values):**\n\n```tsx\n<Select defaultValue={plans[0]} itemToStringValue={(plan) => plan.name}>\n  <SelectTrigger>\n    <SelectValue>{(value) => value.name}</SelectValue>\n  </SelectTrigger>\n  ...\n</Select>\n```\n\n---\n\n## ToggleGroup\n\nBase uses a `multiple` boolean prop. Radix uses `type=\"single\"` or `type=\"multiple\"`.\n\n**Incorrect (base):**\n\n```tsx\n<ToggleGroup type=\"single\" defaultValue=\"daily\">\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n</ToggleGroup>\n```\n\n**Correct (base):**\n\n```tsx\n// Single (no prop needed), defaultValue is always an array.\n<ToggleGroup defaultValue={[\"daily\"]} spacing={2}>\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n  <ToggleGroupItem value=\"weekly\">Weekly</ToggleGroupItem>\n</ToggleGroup>\n\n// Multi-selection.\n<ToggleGroup multiple>\n  <ToggleGroupItem value=\"bold\">Bold</ToggleGroupItem>\n  <ToggleGroupItem value=\"italic\">Italic</ToggleGroupItem>\n</ToggleGroup>\n```\n\n**Correct (radix):**\n\n```tsx\n// Single, defaultValue is a string.\n<ToggleGroup type=\"single\" defaultValue=\"daily\" spacing={2}>\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n  <ToggleGroupItem value=\"weekly\">Weekly</ToggleGroupItem>\n</ToggleGroup>\n\n// Multi-selection.\n<ToggleGroup type=\"multiple\">\n  <ToggleGroupItem value=\"bold\">Bold</ToggleGroupItem>\n  <ToggleGroupItem value=\"italic\">Italic</ToggleGroupItem>\n</ToggleGroup>\n```\n\n**Controlled single value:**\n\n```tsx\n// base — wrap/unwrap arrays.\nconst [value, setValue] = React.useState(\"normal\")\n<ToggleGroup value={[value]} onValueChange={(v) => setValue(v[0])}>\n\n// radix — plain string.\nconst [value, setValue] = React.useState(\"normal\")\n<ToggleGroup type=\"single\" value={value} onValueChange={setValue}>\n```\n\n---\n\n## Slider\n\nBase accepts a plain number for a single thumb. Radix always requires an array.\n\n**Incorrect (base):**\n\n```tsx\n<Slider defaultValue={[50]} max={100} step={1} />\n```\n\n**Correct (base):**\n\n```tsx\n<Slider defaultValue={50} max={100} step={1} />\n```\n\n**Correct (radix):**\n\n```tsx\n<Slider defaultValue={[50]} max={100} step={1} />\n```\n\nBoth use arrays for range sliders. Controlled `onValueChange` in base may need a cast:\n\n```tsx\n// base.\nconst [value, setValue] = React.useState([0.3, 0.7])\n<Slider value={value} onValueChange={(v) => setValue(v as number[])} />\n\n// radix.\nconst [value, setValue] = React.useState([0.3, 0.7])\n<Slider value={value} onValueChange={setValue} />\n```\n\n---\n\n## Accordion\n\nRadix requires `type=\"single\"` or `type=\"multiple\"` and supports `collapsible`. `defaultValue` is a string. Base uses no `type` prop, uses `multiple` boolean, and `defaultValue` is always an array.\n\n**Incorrect (base):**\n\n```tsx\n<Accordion type=\"single\" collapsible defaultValue=\"item-1\">\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n</Accordion>\n```\n\n**Correct (base):**\n\n```tsx\n<Accordion defaultValue={[\"item-1\"]}>\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n</Accordion>\n\n// Multi-select.\n<Accordion multiple defaultValue={[\"item-1\", \"item-2\"]}>\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n  <AccordionItem value=\"item-2\">...</AccordionItem>\n</Accordion>\n```\n\n**Correct (radix):**\n\n```tsx\n<Accordion type=\"single\" collapsible defaultValue=\"item-1\">\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n</Accordion>\n```\n","isInternal":false,"tokens":1831,"sizeBytes":6977},{"name":"chat.md","path":".agents/skills/shadcn/rules/chat.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.agents/skills/shadcn/rules/chat.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Chat & Messaging\n\nComponents for conversation and chat UI. Compose these instead of hand-rolling bubbles, scroll containers, dividers, or attachment cards.\n\nInstall: `npx shadcn@latest add message-scroller message bubble attachment marker`\n\nThe same component names and props ship for both `base` and `radix`; only composition differs (`render` vs `asChild`). See [base-vs-radix.md](./base-vs-radix.md).\n\n## Contents\n\n- Scrollable threads use MessageScroller\n- Message rows use Message\n- Message surfaces use Bubble\n- Attachments use Attachment\n- System notes and dividers use Marker\n- Streaming, anchoring, and jump-to-latest are built in\n- Escape hatch: the scroller hooks\n\n---\n\n## Scrollable threads use MessageScroller\n\nA conversation that scrolls, follows new messages, restores position, or jumps to a message uses `MessageScroller`. Don't build a raw overflow container with manual scroll wiring, and don't reach for `ScrollArea`.\n\nThe parts nest in a fixed order. Every direct child of the content is wrapped in a `MessageScrollerItem` so the scroller can measure, anchor, preserve position, track visibility, and jump to it. `MessageScrollerButton` sits inside `MessageScroller`, after the viewport.\n\n**Incorrect:**\n\n```tsx\n// Hand-rolled scroll container with manual stick-to-bottom logic.\n<div ref={scrollRef} onScroll={handleScroll} className=\"flex-1 overflow-y-auto\">\n  <div className=\"flex flex-col gap-6 p-4\">\n    {messages.map((m) => (\n      <ChatMessage key={m.id} message={m} />\n    ))}\n  </div>\n</div>\n```\n\n**Correct:**\n\n```tsx\n<MessageScrollerProvider autoScroll>\n  <MessageScroller>\n    <MessageScrollerViewport>\n      <MessageScrollerContent>\n        {messages.map((message) => (\n          <MessageScrollerItem\n            key={message.id}\n            messageId={message.id}\n            scrollAnchor={message.role === \"user\"}\n          >\n            <Message align={message.role === \"user\" ? \"end\" : \"start\"}>\n              {/* ...message content... */}\n            </Message>\n          </MessageScrollerItem>\n        ))}\n      </MessageScrollerContent>\n    </MessageScrollerViewport>\n    <MessageScrollerButton />\n  </MessageScroller>\n</MessageScrollerProvider>\n```\n\n---\n\n## Message rows use Message\n\n`Message` lays out a single row: avatar, header, content, footer, with alignment. Group consecutive rows from one sender with `MessageGroup`. Don't rebuild the row from flex divs.\n\n`align=\"end\"` is the current user's side; `align=\"start\"` is everyone else.\n\n```tsx\n<Message align=\"start\">\n  <MessageAvatar>\n    <Avatar>\n      <AvatarImage src={sender.avatar} alt={sender.name} />\n      <AvatarFallback>{initials}</AvatarFallback>\n    </Avatar>\n  </MessageAvatar>\n  <MessageContent>\n    <MessageHeader>{sender.name}</MessageHeader>\n    <Bubble>\n      <BubbleContent>{text}</BubbleContent>\n    </Bubble>\n    <MessageFooter>{time}</MessageFooter>\n  </MessageContent>\n</Message>\n```\n\n---\n\n## Message surfaces use Bubble\n\nThe colored message surface is `Bubble` + `BubbleContent`, never a styled `div` with `bg-muted` / `bg-primary` and hand-managed corners.\n\n- `variant`: `default`, `secondary`, `muted`, `tinted`, `outline`, `ghost`, `destructive`.\n- `align`: `start` or `end` (matches the `Message` side).\n\n`BubbleReactions` renders the reaction cluster. `side` (`top` | `bottom`) and `align` (`start` | `end`) position it against the bubble. Don't lay reactions out with absolutely-positioned `Badge`s.\n\n**Incorrect:**\n\n```tsx\n<div className=\"w-fit rounded-2xl bg-primary px-3 py-2 text-primary-foreground\">\n  {text}\n</div>\n```\n\n**Correct:**\n\n```tsx\n<Bubble variant=\"default\" align=\"end\">\n  <BubbleContent>{text}</BubbleContent>\n  <BubbleReactions side=\"bottom\" align=\"end\">\n    <Badge variant=\"secondary\">👍 2</Badge>\n  </BubbleReactions>\n</Bubble>\n```\n\n---\n\n## Attachments use Attachment\n\nFile and image attachments use `Attachment`, not `Item` or a custom card. It carries upload state, so wire `state` to the real status rather than rendering a separate spinner.\n\n- `state`: `idle`, `uploading`, `processing`, `error`, `done`. `uploading` and `processing` apply the `shimmer` animation to the title automatically.\n- `size`: `default`, `sm`, `xs`. `orientation`: `horizontal`, `vertical`.\n- Use `AttachmentGroup` to lay out several attachments in a scrolling row.\n\n```tsx\n<Attachment state=\"done\">\n  <AttachmentMedia variant=\"icon\">\n    <FileTextIcon />\n  </AttachmentMedia>\n  <AttachmentContent>\n    <AttachmentTitle>homepage-feedback.pdf</AttachmentTitle>\n    <AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>\n  </AttachmentContent>\n  <AttachmentActions>\n    <AttachmentAction>\n      <DownloadIcon />\n    </AttachmentAction>\n  </AttachmentActions>\n</Attachment>\n```\n\nFor an image, use `<AttachmentMedia variant=\"image\">` with an `img` child.\n\n---\n\n## System notes and dividers use Marker\n\nStatus lines (\"Sarah joined the conversation\"), date dividers (\"Today\"), and labeled separators are `Marker`, not a `Separator` plus a centered span.\n\n- `variant`: `default` (plain row), `separator` (centered label with rules on each side), `border` (bottom-bordered row).\n- `MarkerIcon` holds a leading icon; `MarkerContent` holds the label.\n\n**Incorrect:**\n\n```tsx\n<div className=\"flex items-center gap-3 py-2\">\n  <Separator className=\"flex-1\" />\n  <span className=\"text-xs text-muted-foreground\">Today</span>\n  <Separator className=\"flex-1\" />\n</div>\n```\n\n**Correct:**\n\n```tsx\n<Marker variant=\"separator\">\n  <MarkerContent>Today</MarkerContent>\n</Marker>\n```\n\n---\n\n## Streaming, anchoring, and jump-to-latest are built in\n\n`MessageScroller` handles the behavior that chat UIs usually reinvent. Don't write a `useStickToBottom` hook, a `ResizeObserver`, or manual `scrollTop` math.\n\n- **Follow the live edge while streaming.** `MessageScrollerProvider` with `autoScroll` keeps the view pinned to new content and yields the moment the user scrolls up. Streaming token updates that grow the last message are followed automatically.\n- **Anchor a turn.** `scrollAnchor` on a `MessageScrollerItem` marks the row to hold in view (typically the user's message that started the turn).\n- **Jump to latest.** `MessageScrollerButton` appears when the user scrolls away and scrolls back on click. `direction=\"end\"` (default) or `direction=\"start\"`. It is a self-managing control, so don't gate it behind your own scroll-position state.\n\nFor a \"thinking…\" indicator while the model generates, apply the `shimmer` utility to text. Don't author a custom keyframe animation. See [styling.md](./styling.md).\n\n---\n\n## Escape hatch: the scroller hooks\n\nFor behavior the parts don't expose, read state from the hooks rather than re-implementing the scroller: `useMessageScroller`, `useMessageScrollerVisibility`, and `useMessageScrollerScrollable`. They come from the auto-installed `@shadcn/react` dependency, so there's nothing extra to install. Reach for them only when composition can't express what you need.\n","isInternal":false,"tokens":1683,"sizeBytes":6930},{"name":"composition.md","path":".agents/skills/shadcn/rules/composition.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.agents/skills/shadcn/rules/composition.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Component Composition\n\n## Contents\n\n- Items always inside their Group component\n- Callouts use Alert\n- Empty states use Empty component\n- Toast notifications follow the project base\n- Choosing between overlay components\n- Dialog, Sheet, and Drawer always need a Title\n- Card structure\n- Button has no isPending or isLoading prop\n- TabsTrigger must be inside TabsList\n- Avatar always needs AvatarFallback\n- Use Separator instead of raw hr or border divs\n- Use Skeleton for loading placeholders\n- Use Badge instead of custom styled spans\n\n---\n\n## Items always inside their Group component\n\nNever render items directly inside the content container.\n\n**Incorrect:**\n\n```tsx\n<SelectContent>\n  <SelectItem value=\"apple\">Apple</SelectItem>\n  <SelectItem value=\"banana\">Banana</SelectItem>\n</SelectContent>\n```\n\n**Correct:**\n\n```tsx\n<SelectContent>\n  <SelectGroup>\n    <SelectItem value=\"apple\">Apple</SelectItem>\n    <SelectItem value=\"banana\">Banana</SelectItem>\n  </SelectGroup>\n</SelectContent>\n```\n\nThis applies to all group-based components:\n\n| Item | Group |\n| --- | --- |\n| `SelectItem`, `SelectLabel` | `SelectGroup` |\n| `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` |\n| `MenubarItem` | `MenubarGroup` |\n| `ContextMenuItem` | `ContextMenuGroup` |\n| `CommandItem` | `CommandGroup` |\n| `MessageScrollerItem` | `MessageScrollerContent` |\n| `Message` (consecutive, same sender) | `MessageGroup` |\n| `Bubble` (stacked) | `BubbleGroup` |\n| `Attachment` (in a row) | `AttachmentGroup` |\n\nChat components nest in a fixed order (`MessageScrollerProvider` → `MessageScroller` → `MessageScrollerViewport` → `MessageScrollerContent` → `MessageScrollerItem`). See [chat.md](./chat.md).\n\n---\n\n## Callouts use Alert\n\n```tsx\n<Alert>\n  <AlertTitle>Warning</AlertTitle>\n  <AlertDescription>Something needs attention.</AlertDescription>\n</Alert>\n```\n\n---\n\n## Empty states use Empty component\n\n```tsx\n<Empty>\n  <EmptyHeader>\n    <EmptyMedia variant=\"icon\">\n      <FolderIcon />\n    </EmptyMedia>\n    <EmptyTitle>No projects yet</EmptyTitle>\n    <EmptyDescription>Get started by creating a new project.</EmptyDescription>\n  </EmptyHeader>\n  <EmptyContent>\n    <Button>Create Project</Button>\n  </EmptyContent>\n</Empty>\n```\n\n---\n\n## Toast notifications follow the project base\n\nFor Base UI projects, use the `toast` component:\n\n```tsx\nimport { toast } from \"@/components/ui/toast\";\n\ntoast.add({\n  title: \"Changes saved.\",\n});\n```\n\nFor Radix and React Aria projects, use Sonner:\n\n```tsx\nimport { toast } from \"sonner\";\n\ntoast.success(\"Changes saved.\");\ntoast.error(\"Something went wrong.\");\ntoast(\"File deleted.\", {\n  action: { label: \"Undo\", onClick: () => undoDelete() },\n});\n```\n\n---\n\n## Choosing between overlay components\n\n| Use case                           | Component     |\n| ---------------------------------- | ------------- |\n| Focused task that requires input   | `Dialog`      |\n| Destructive action confirmation    | `AlertDialog` |\n| Side panel with details or filters | `Sheet`       |\n| Mobile-first bottom panel          | `Drawer`      |\n| Quick info on hover                | `HoverCard`   |\n| Small contextual content on click  | `Popover`     |\n\n---\n\n## Dialog, Sheet, and Drawer always need a Title\n\n`DialogTitle`, `SheetTitle`, `DrawerTitle` are required for accessibility. Use `className=\"sr-only\"` if visually hidden.\n\n```tsx\n<DialogContent>\n  <DialogHeader>\n    <DialogTitle>Edit Profile</DialogTitle>\n    <DialogDescription>Update your profile.</DialogDescription>\n  </DialogHeader>\n  ...\n</DialogContent>\n```\n\n---\n\n## Card structure\n\nUse full composition — don't dump everything into `CardContent`:\n\n```tsx\n<Card>\n  <CardHeader>\n    <CardTitle>Team Members</CardTitle>\n    <CardDescription>Manage your team.</CardDescription>\n  </CardHeader>\n  <CardContent>...</CardContent>\n  <CardFooter>\n    <Button>Invite</Button>\n  </CardFooter>\n</Card>\n```\n\n---\n\n## Button has no isPending or isLoading prop\n\nCompose with `Spinner` + `data-icon` + `disabled`:\n\n```tsx\n<Button disabled>\n  <Spinner data-icon=\"inline-start\" />\n  Saving...\n</Button>\n```\n\n---\n\n## TabsTrigger must be inside TabsList\n\nNever render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`:\n\n```tsx\n<Tabs defaultValue=\"account\">\n  <TabsList>\n    <TabsTrigger value=\"account\">Account</TabsTrigger>\n    <TabsTrigger value=\"password\">Password</TabsTrigger>\n  </TabsList>\n  <TabsContent value=\"account\">...</TabsContent>\n</Tabs>\n```\n\n---\n\n## Avatar always needs AvatarFallback\n\nAlways include `AvatarFallback` for when the image fails to load:\n\n```tsx\n<Avatar>\n  <AvatarImage src=\"/avatar.png\" alt=\"User\" />\n  <AvatarFallback>JD</AvatarFallback>\n</Avatar>\n```\n\n---\n\n## Use existing components instead of custom markup\n\n| Instead of | Use |\n| --- | --- |\n| `<hr>` or `<div className=\"border-t\">` | `<Separator />` |\n| `<div className=\"animate-pulse\">` with styled divs | `<Skeleton className=\"h-4 w-3/4\" />` |\n| `<span className=\"rounded-full bg-green-100 ...\">` | `<Badge variant=\"secondary\">` |\n","isInternal":false,"tokens":1230,"sizeBytes":5023},{"name":"forms.md","path":".agents/skills/shadcn/rules/forms.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.agents/skills/shadcn/rules/forms.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Forms & Inputs\n\n## Contents\n\n- Forms use FieldGroup + Field\n- InputGroup requires InputGroupInput/InputGroupTextarea\n- Buttons inside inputs use InputGroup + InputGroupAddon\n- Option sets (2–7 choices) use ToggleGroup\n- FieldSet + FieldLegend for grouping related fields\n- Field validation and disabled states\n\n---\n\n## Forms use FieldGroup + Field\n\nAlways use `FieldGroup` + `Field` — never raw `div` with `space-y-*`:\n\n```tsx\n<FieldGroup>\n  <Field>\n    <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n    <Input id=\"email\" type=\"email\" />\n  </Field>\n  <Field>\n    <FieldLabel htmlFor=\"password\">Password</FieldLabel>\n    <Input id=\"password\" type=\"password\" />\n  </Field>\n</FieldGroup>\n```\n\nUse `Field orientation=\"horizontal\"` for settings pages. Use `FieldLabel className=\"sr-only\"` for visually hidden labels.\n\n**Choosing form controls:**\n\n- Simple text input → `Input`\n- Dropdown with predefined options → `Select`\n- Searchable dropdown → `Combobox`\n- Native HTML select (no JS) → `native-select`\n- Boolean toggle → `Switch` (for settings) or `Checkbox` (for forms)\n- Single choice from few options → `RadioGroup`\n- Toggle between 2–5 options → `ToggleGroup` + `ToggleGroupItem`\n- OTP/verification code → `InputOTP`\n- Multi-line text → `Textarea`\n\n---\n\n## InputGroup requires InputGroupInput/InputGroupTextarea\n\nNever use raw `Input` or `Textarea` inside an `InputGroup`.\n\n**Incorrect:**\n\n```tsx\n<InputGroup>\n  <Input placeholder=\"Search...\" />\n</InputGroup>\n```\n\n**Correct:**\n\n```tsx\nimport { InputGroup, InputGroupInput } from \"@/components/ui/input-group\";\n\n<InputGroup>\n  <InputGroupInput placeholder=\"Search...\" />\n</InputGroup>;\n```\n\n---\n\n## Buttons inside inputs use InputGroup + InputGroupAddon\n\nNever place a `Button` directly inside or adjacent to an `Input` with custom positioning.\n\n**Incorrect:**\n\n```tsx\n<div className=\"relative\">\n  <Input placeholder=\"Search...\" className=\"pr-10\" />\n  <Button className=\"absolute right-0 top-0\" size=\"icon\">\n    <SearchIcon />\n  </Button>\n</div>\n```\n\n**Correct:**\n\n```tsx\nimport {\n  InputGroup,\n  InputGroupInput,\n  InputGroupAddon,\n} from \"@/components/ui/input-group\";\n\n<InputGroup>\n  <InputGroupInput placeholder=\"Search...\" />\n  <InputGroupAddon>\n    <Button size=\"icon\">\n      <SearchIcon data-icon=\"inline-start\" />\n    </Button>\n  </InputGroupAddon>\n</InputGroup>;\n```\n\n---\n\n## Option sets (2–7 choices) use ToggleGroup\n\nDon't manually loop `Button` components with active state.\n\n**Incorrect:**\n\n```tsx\nconst [selected, setSelected] = useState(\"daily\")\n\n<div className=\"flex gap-2\">\n  {[\"daily\", \"weekly\", \"monthly\"].map((option) => (\n    <Button\n      key={option}\n      variant={selected === option ? \"default\" : \"outline\"}\n      onClick={() => setSelected(option)}\n    >\n      {option}\n    </Button>\n  ))}\n</div>\n```\n\n**Correct:**\n\n```tsx\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\n\n<ToggleGroup spacing={2}>\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n  <ToggleGroupItem value=\"weekly\">Weekly</ToggleGroupItem>\n  <ToggleGroupItem value=\"monthly\">Monthly</ToggleGroupItem>\n</ToggleGroup>;\n```\n\nCombine with `Field` for labelled toggle groups:\n\n```tsx\n<Field orientation=\"horizontal\">\n  <FieldTitle id=\"theme-label\">Theme</FieldTitle>\n  <ToggleGroup aria-labelledby=\"theme-label\" spacing={2}>\n    <ToggleGroupItem value=\"light\">Light</ToggleGroupItem>\n    <ToggleGroupItem value=\"dark\">Dark</ToggleGroupItem>\n    <ToggleGroupItem value=\"system\">System</ToggleGroupItem>\n  </ToggleGroup>\n</Field>\n```\n\n> **Note:** `defaultValue` and `type`/`multiple` props differ between base and radix. See [base-vs-radix.md](./base-vs-radix.md#togglegroup).\n\n---\n\n## FieldSet + FieldLegend for grouping related fields\n\nUse `FieldSet` + `FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading:\n\n```tsx\n<FieldSet>\n  <FieldLegend variant=\"label\">Preferences</FieldLegend>\n  <FieldDescription>Select all that apply.</FieldDescription>\n  <FieldGroup className=\"gap-3\">\n    <Field orientation=\"horizontal\">\n      <Checkbox id=\"dark\" />\n      <FieldLabel htmlFor=\"dark\" className=\"font-normal\">\n        Dark mode\n      </FieldLabel>\n    </Field>\n  </FieldGroup>\n</FieldSet>\n```\n\n---\n\n## Field validation and disabled states\n\nBoth attributes are needed — `data-invalid`/`data-disabled` styles the field (label, description), while `aria-invalid`/`disabled` styles the control.\n\n```tsx\n// Invalid.\n<Field data-invalid>\n  <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n  <Input id=\"email\" aria-invalid />\n  <FieldDescription>Invalid email address.</FieldDescription>\n</Field>\n\n// Disabled.\n<Field data-disabled>\n  <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n  <Input id=\"email\" disabled />\n</Field>\n```\n\nWorks for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`, `NativeSelect`, `InputOTP`.\n","isInternal":false,"tokens":1210,"sizeBytes":4886},{"name":"icons.md","path":".agents/skills/shadcn/rules/icons.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.agents/skills/shadcn/rules/icons.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Icons\n\n**Always use the project's configured `iconLibrary` for imports.** Check the `iconLibrary` field from project context: `lucide` → `lucide-react`, `tabler` → `@tabler/icons-react`, etc. Never assume `lucide-react`.\n\n---\n\n## Icons in Button use data-icon attribute\n\nAdd `data-icon=\"inline-start\"` (prefix) or `data-icon=\"inline-end\"` (suffix) to the icon. No sizing classes on the icon.\n\n**Incorrect:**\n\n```tsx\n<Button>\n  <SearchIcon className=\"mr-2 size-4\" />\n  Search\n</Button>\n```\n\n**Correct:**\n\n```tsx\n<Button>\n  <SearchIcon data-icon=\"inline-start\"/>\n  Search\n</Button>\n\n<Button>\n  Next\n  <ArrowRightIcon data-icon=\"inline-end\"/>\n</Button>\n```\n\n---\n\n## No sizing classes on icons inside components\n\nComponents handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons inside `Button`, `DropdownMenuItem`, `Alert`, `Sidebar*`, or other shadcn components. Unless the user explicitly asks for custom icon sizes.\n\n**Incorrect:**\n\n```tsx\n<Button>\n  <SearchIcon className=\"size-4\" data-icon=\"inline-start\" />\n  Search\n</Button>\n\n<DropdownMenuItem>\n  <SettingsIcon className=\"mr-2 size-4\" />\n  Settings\n</DropdownMenuItem>\n```\n\n**Correct:**\n\n```tsx\n<Button>\n  <SearchIcon data-icon=\"inline-start\" />\n  Search\n</Button>\n\n<DropdownMenuItem>\n  <SettingsIcon />\n  Settings\n</DropdownMenuItem>\n```\n\n---\n\n## Pass icons as component objects, not string keys\n\nUse `icon={CheckIcon}`, not a string key to a lookup map.\n\n**Incorrect:**\n\n```tsx\nconst iconMap = {\n  check: CheckIcon,\n  alert: AlertIcon,\n};\n\nfunction StatusBadge({ icon }: { icon: string }) {\n  const Icon = iconMap[icon];\n  return <Icon />;\n}\n\n<StatusBadge icon=\"check\" />;\n```\n\n**Correct:**\n\n```tsx\n// Import from the project's configured iconLibrary (e.g. lucide-react, @tabler/icons-react).\nimport { CheckIcon } from \"lucide-react\";\n\nfunction StatusBadge({ icon: Icon }: { icon: React.ComponentType }) {\n  return <Icon />;\n}\n\n<StatusBadge icon={CheckIcon} />;\n```\n","isInternal":false,"tokens":522,"sizeBytes":1965},{"name":"styling.md","path":".agents/skills/shadcn/rules/styling.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.agents/skills/shadcn/rules/styling.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Styling & Customization\n\nSee [customization.md](../customization.md) for theming, CSS variables, and adding custom colors.\n\n## Contents\n\n- Semantic colors\n- Built-in variants first\n- className for layout only\n- No space-x-* / space-y-*\n- Prefer size-* over w-* h-* when equal\n- Prefer truncate shorthand\n- No manual dark: color overrides\n- Use cn() for conditional classes\n- No manual z-index on overlay components\n- Use shimmer / scroll-fade utilities, not custom animations\n\n---\n\n## Semantic colors\n\n**Incorrect:**\n\n```tsx\n<div className=\"bg-blue-500 text-white\">\n  <p className=\"text-gray-600\">Secondary text</p>\n</div>\n```\n\n**Correct:**\n\n```tsx\n<div className=\"bg-primary text-primary-foreground\">\n  <p className=\"text-muted-foreground\">Secondary text</p>\n</div>\n```\n\n---\n\n## No raw color values for status/state indicators\n\nFor positive, negative, or status indicators, use Badge variants, semantic tokens like `text-destructive`, or define custom CSS variables — don't reach for raw Tailwind colors.\n\n**Incorrect:**\n\n```tsx\n<span className=\"text-emerald-600\">+20.1%</span>\n<span className=\"text-green-500\">Active</span>\n<span className=\"text-red-600\">-3.2%</span>\n```\n\n**Correct:**\n\n```tsx\n<Badge variant=\"secondary\">+20.1%</Badge>\n<Badge>Active</Badge>\n<span className=\"text-destructive\">-3.2%</span>\n```\n\nIf you need a success/positive color that doesn't exist as a semantic token, use a Badge variant or ask the user about adding a custom CSS variable to the theme (see [customization.md](../customization.md)).\n\n---\n\n## Built-in variants first\n\n**Incorrect:**\n\n```tsx\n<Button className=\"border border-input bg-transparent hover:bg-accent\">\n  Click me\n</Button>\n```\n\n**Correct:**\n\n```tsx\n<Button variant=\"outline\">Click me</Button>\n```\n\n---\n\n## className for layout only\n\nUse `className` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables.\n\n**Incorrect:**\n\n```tsx\n<Card className=\"bg-blue-100 text-blue-900 font-bold\">\n  <CardContent>Dashboard</CardContent>\n</Card>\n```\n\n**Correct:**\n\n```tsx\n<Card className=\"max-w-md mx-auto\">\n  <CardContent>Dashboard</CardContent>\n</Card>\n```\n\nTo customize a component's appearance, prefer these approaches in order:\n\n1. **Built-in variants** — `variant=\"outline\"`, `variant=\"destructive\"`, etc.\n2. **Semantic color tokens** — `bg-primary`, `text-muted-foreground`.\n3. **CSS variables** — define custom colors in the global CSS file (see [customization.md](../customization.md)).\n\n---\n\n## No space-x-* / space-y-*\n\nUse `gap-*` instead. `space-y-4` → `flex flex-col gap-4`. `space-x-2` → `flex gap-2`.\n\n```tsx\n<div className=\"flex flex-col gap-4\">\n  <Input />\n  <Input />\n  <Button>Submit</Button>\n</div>\n```\n\n---\n\n## Prefer size-* over w-* h-* when equal\n\n`size-10` not `w-10 h-10`. Applies to icons, avatars, skeletons, etc.\n\n---\n\n## Prefer truncate shorthand\n\n`truncate` not `overflow-hidden text-ellipsis whitespace-nowrap`.\n\n---\n\n## No manual dark: color overrides\n\nUse semantic tokens — they handle light/dark via CSS variables. `bg-background text-foreground` not `bg-white dark:bg-gray-950`.\n\n---\n\n## Use cn() for conditional classes\n\nUse the `cn()` utility from the project for conditional or merged class names. Don't write manual ternaries in className strings.\n\n**Incorrect:**\n\n```tsx\n<div className={`flex items-center ${isActive ? \"bg-primary text-primary-foreground\" : \"bg-muted\"}`}>\n```\n\n**Correct:**\n\n```tsx\nimport { cn } from \"@/lib/utils\"\n\n<div className={cn(\"flex items-center\", isActive ? \"bg-primary text-primary-foreground\" : \"bg-muted\")}>\n```\n\n---\n\n## No manual z-index on overlay components\n\n`Dialog`, `Sheet`, `Drawer`, `AlertDialog`, `DropdownMenu`, `Popover`, `Tooltip`, `HoverCard` handle their own stacking. Never add `z-50` or `z-[999]`.\n\n---\n\n## Use shimmer / scroll-fade utilities, not custom animations\n\nFor a live \"thinking…\" or loading-text shimmer, apply the `shimmer` utility. Don't author a custom `@keyframes` or a `bg-clip-text` gradient sweep.\n\nFor scroll-aware edge fading on a scroll container, use `scroll-fade` (and the axis variants `scroll-fade-x` / `scroll-fade-b`). Don't hand-roll mask gradients. The chat components already apply these internally: `Attachment` shimmers its title during upload, and `MessageScrollerViewport` fades its edges.\n\n**Incorrect:**\n\n```tsx\n<span className=\"animate-pulse bg-gradient-to-r from-muted-foreground/40 via-foreground/70 to-muted-foreground/40 bg-clip-text text-transparent [animation:shimmer_1.6s_infinite]\">\n  Thinking…\n</span>\n```\n\n**Correct:**\n\n```tsx\n<span className=\"shimmer\">Thinking…</span>\n```\n","isInternal":false,"tokens":1188,"sizeBytes":4685},{"name":"SKILL.md","path":".agents/skills/shadcn/SKILL.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.agents/skills/shadcn/SKILL.md","title":"shadcn","category":"anthropic-skill","format":"markdown","content":"---\nname: shadcn\ndescription: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for \"shadcn init\", \"create an app with --preset\", or \"switch to --preset\".\nuser-invocable: false\nallowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)\n---\n\n# shadcn/ui\n\nA framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI.\n\n> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.\n\n## Current Project Context\n\n```json\n!`npx shadcn@latest info --json`\n```\n\nThe JSON above contains the project config and installed components. Use `npx shadcn@latest docs <component>` to get documentation and example URLs for any component.\n\n## Principles\n\n1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too.\n2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table.\n3. **Use built-in variants before custom styles.** `variant=\"outline\"`, `size=\"sm\"`, etc.\n4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`.\n\n## Critical Rules\n\nThese rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs.\n\n### Styling & Tailwind → [styling.md](./rules/styling.md)\n\n- **`className` for layout, not styling.** Never override component colors or typography.\n- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`.\n- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`.\n- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`.\n- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`).\n- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries.\n- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking.\n\n### Forms & Inputs → [forms.md](./rules/forms.md)\n\n- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.\n- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`.\n- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.**\n- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state.\n- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading.\n- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control.\n\n### Component Structure → [composition.md](./rules/composition.md)\n\n- **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`.\n- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md)\n- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className=\"sr-only\"` if visually hidden.\n- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`.\n- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`.\n- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`.\n- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load.\n\n### Use Components, Not Custom Markup → [composition.md](./rules/composition.md)\n\n- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.\n- **Callouts use `Alert`.** Don't build custom styled divs.\n- **Empty states use `Empty`.** Don't build custom empty state markup.\n- **Toast follows the project base.** Use `toast` from the `toast` component for Base UI projects. Use `toast()` from `sonner` for Radix and React Aria projects.\n- **Use `Separator`** instead of `<hr>` or `<div className=\"border-t\">`.\n- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.\n- **Use `Badge`** instead of custom styled spans.\n\n### Icons → [icons.md](./rules/icons.md)\n\n- **Icons in `Button` use `data-icon`.** `data-icon=\"inline-start\"` or `data-icon=\"inline-end\"` on the icon.\n- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`.\n- **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup.\n\n### Chat & Messaging → [chat.md](./rules/chat.md)\n\n- **Chat UI composes the chat primitives.** Conversations use `MessageScroller`, rows use `Message`, surfaces use `Bubble`. Never hand-rolled bubble `div`s or a raw scroll container.\n- **`MessageScroller` owns scroll behavior.** Streaming follow, anchoring, and jump-to-latest (`MessageScrollerButton`) are built in. Don't write a `useStickToBottom`/`ResizeObserver` hook.\n- **Attachments use `Attachment`; system notes and dividers use `Marker`.** Not `Item` cards or `Separator` + a label.\n\n### CLI\n\n- **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@latest preset decode <code>`, `preset url <code>`, or `preset open <code>`. For project-aware preset detection, use `npx shadcn@latest preset resolve`.\n- **Apply preset codes directly with the CLI.** Use `npx shadcn@latest apply <code>` for existing projects, or `npx shadcn@latest init --preset <code>` when initializing.\n\n## Key Patterns\n\nThese are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above.\n\n```tsx\n// Form layout: FieldGroup + Field, not div + Label.\n<FieldGroup>\n  <Field>\n    <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n    <Input id=\"email\" />\n  </Field>\n</FieldGroup>\n\n// Validation: data-invalid on Field, aria-invalid on the control.\n<Field data-invalid>\n  <FieldLabel>Email</FieldLabel>\n  <Input aria-invalid />\n  <FieldDescription>Invalid email.</FieldDescription>\n</Field>\n\n// Icons in buttons: data-icon, no sizing classes.\n<Button>\n  <SearchIcon data-icon=\"inline-start\" />\n  Search\n</Button>\n\n// Spacing: gap-*, not space-y-*.\n<div className=\"flex flex-col gap-4\">  // correct\n<div className=\"space-y-4\">           // wrong\n\n// Equal dimensions: size-*, not w-* h-*.\n<Avatar className=\"size-10\">   // correct\n<Avatar className=\"w-10 h-10\"> // wrong\n\n// Status colors: Badge variants or semantic tokens, not raw colors.\n<Badge variant=\"secondary\">+20.1%</Badge>    // correct\n<span className=\"text-emerald-600\">+20.1%</span> // wrong\n```\n\n## Component Selection\n\n| Need | Use |\n| --- | --- |\n| Button/action | `Button` with appropriate variant |\n| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` |\n| Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` |\n| Data display | `Table`, `Card`, `Badge`, `Avatar` |\n| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |\n| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |\n| Feedback | `toast` (Base UI), `sonner` (Radix/Aria), `Alert`, `Progress`, `Skeleton`, `Spinner` |\n| Command palette | `Command` inside `Dialog` |\n| Charts | `Chart` (wraps Recharts) |\n| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |\n| Empty states | `Empty` |\n| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` |\n| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` |\n| Chat / conversation UI | `MessageScroller`, `Message`, `Bubble`, `Attachment`, `Marker` |\n\n## Key Fields\n\nThe injected project context contains these key fields:\n\n- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode.\n- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `\"use client\"` at the top of the file. Always reference this field when advising on the directive.\n- **`tailwindVersion`** → `\"v4\"` uses `@theme inline` blocks; `\"v3\"` uses `tailwind.config.js`.\n- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one.\n- **`style`** → component visual treatment (e.g. `nova`, `vega`).\n- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props.\n- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`.\n- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc.\n- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA).\n- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`).\n- **`preset`** → resolved preset code and values for the current project. Use `npx shadcn@latest preset resolve --json` when you only need preset information.\n\nSee [cli.md — `info` command](./cli.md) for the full field reference.\n\n## Component Docs, Examples, and Usage\n\nRun `npx shadcn@latest docs <component>` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content.\n\n```bash\nnpx shadcn@latest docs button dialog select\n```\n\n**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing.\n\n## Workflow\n\n1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh.\n2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed.\n3. **Find components** — `npx shadcn@latest search`.\n4. **Get docs and examples** — run `npx shadcn@latest docs <component>` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`.\n5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below).\n6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.\n7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on.\n8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says \"add a login block\" without specifying `@shadcn`, `@tailark`, `owner/repo`, etc.), ask which registry to use. Never default to a registry on behalf of the user.\n9. **Switching presets** — Ask the user first: **overwrite**, **partial**, **merge**, or **skip**?\n   - **Inspect current preset**: `npx shadcn@latest preset resolve`. Use `--json` when you need structured values.\n   - **Inspect incoming preset**: `npx shadcn@latest preset decode <code>`. Use `preset url <code>` or `preset open <code>` to share or open the preset builder.\n   - **Overwrite**: `npx shadcn@latest apply <code>`. Overwrites detected components, fonts, and CSS variables.\n   - **Partial**: `npx shadcn@latest apply <code> --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms.\n   - **Merge**: `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually.\n   - **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.\n   - **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.\n\n## Updating Components\n\nWhen the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**\n\n1. Run `npx shadcn@latest add <component> --dry-run` to see all files that would be affected.\n2. For each file, run `npx shadcn@latest add <component> --diff <file>` to see what changed upstream vs local.\n3. Decide per file based on the diff:\n   - No local changes → safe to overwrite.\n   - Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications.\n   - User says \"just update everything\" → use `--overwrite`, but confirm first.\n4. **Never use `--overwrite` without the user's explicit approval.**\n\n## Quick Reference\n\n```bash\n# Create a new project.\nnpx shadcn@latest init --name my-app --preset base-nova\nnpx shadcn@latest init --name my-app --preset a2r6bw --template vite\n\n# Create a monorepo project.\nnpx shadcn@latest init --name my-app --preset base-nova --monorepo\nnpx shadcn@latest init --name my-app --preset base-nova --template next --monorepo\n\n# Initialize existing project.\nnpx shadcn@latest init --preset base-nova\nnpx shadcn@latest init --defaults  # shortcut: --template=next --preset=nova (base style implied)\n\n# Apply a preset to an existing project.\nnpx shadcn@latest apply a2r6bw\nnpx shadcn@latest apply a2r6bw --only theme\nnpx shadcn@latest apply a2r6bw --only font\nnpx shadcn@latest apply a2r6bw --only theme,font\n\n# Inspect preset codes and project preset state.\nnpx shadcn@latest preset decode a2r6bw\nnpx shadcn@latest preset url a2r6bw\nnpx shadcn@latest preset open a2r6bw\nnpx shadcn@latest preset resolve\nnpx shadcn@latest preset resolve --json\n\n# Add components.\nnpx shadcn@latest add button card dialog\nnpx shadcn@latest add @magicui/shimmer-button\nnpx shadcn@latest add owner/repo/item\nnpx shadcn@latest add --all\n\n# Preview changes before adding/updating.\nnpx shadcn@latest add button --dry-run\nnpx shadcn@latest add button --diff button.tsx\nnpx shadcn@latest add @acme/form --view button.tsx\nnpx shadcn@latest add owner/repo/item --dry-run\n\n# Search registries.\nnpx shadcn@latest search @shadcn -q \"sidebar\"\nnpx shadcn@latest search @tailark -q \"stats\"\nnpx shadcn@latest search owner/repo -q \"login\"\nnpx shadcn@latest search                          # all configured registries\nnpx shadcn@latest search @shadcn -q \"menu\" -t ui  # filter by item type\n\n# Get component docs and example URLs.\nnpx shadcn@latest docs button dialog select\n\n# View registry item details (for items not yet installed).\nnpx shadcn@latest view @shadcn/button\nnpx shadcn@latest view owner/repo/item\n```\n\n**Named presets:** `nova`, `vega`, `maia`, `lyra`, `mira`, `luma` **Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo) **Preset codes:** Version-prefixed base62 strings (e.g. `a2r6bw` or `b0`), from [ui.shadcn.com](https://ui.shadcn.com).\n\n## Detailed References\n\n- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states\n- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading\n- [rules/chat.md](./rules/chat.md) — MessageScroller, Message, Bubble, Attachment, Marker; streaming, anchoring, jump-to-latest\n- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects\n- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index\n- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion\n- [cli.md](./cli.md) — Commands, flags, presets, templates\n- [registry.md](./registry.md) — Authoring source registries, `include`, item definitions, dependencies, GitHub registry rules\n- [customization.md](./customization.md) — Theming, CSS variables, extending components\n","frontmatter":{"name":"shadcn","description":"Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for \"shadcn init\", \"create an app with --preset\", or \"switch to --preset\".","user-invocable":false,"allowed-tools":"Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)","allowedTools":["Bash(npx","shadcn@latest","*),","Bash(pnpm","dlx","shadcn@latest","*),","Bash(bunx","--bun","shadcn@latest","*)"]},"allowedTools":["Bash(npx","shadcn@latest","*),","Bash(pnpm","dlx","shadcn@latest","*),","Bash(bunx","--bun","shadcn@latest","*)"],"isInternal":false,"tokens":4831,"sizeBytes":18406},{"name":"AGENTS.md","path":"packages/ui/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/packages/ui/AGENTS.md","title":"Ui Skill","category":"anthropic-skill","format":"markdown","content":"shadcn/ui component library (new-york style, Radix primitives, Tailwind v4). Consumed as `@repo/ui`.\n\n## Scope\n\n- Only generic primitives belong here. If a component imports a route, a query, or the session, it belongs in `apps/app/components/` instead.\n- Nothing here imports application or domain code (`apps/`, `db/`). Consumers still have to supply Tailwind `@source` entries and the theme tokens.\n- Imports inside this package go through Node subpath imports — `#lib/utils`, `#components/toggle` — declared in `package.json#imports` and mirrored by the `components.json` aliases, so the CLI generates them natively. They resolve against _this_ package from every workspace, so a consumer needs no matching file of its own. Requires `moduleResolution: \"bundler\"`, which `packages/typescript-config/base.jsonc` already sets.\n- `index.ts` exports the top-level primitives in `components/`. Nested files are implementation details — promote one deliberately if it should be public.\n\n- `scripts/` is intentionally outside the tsconfig `include`. These are Bun CLI tools, while the library's declaration build uses the browser-only React preset (`types: [\"vite/client\"]`). Including them would require Bun/Node types and emit declarations for tooling. Both commands hit the network and overwrite files, so smoke-test changes on a single component (`bun ui:update button`).\n\n## Adding and Updating Components\n\n- Use `bun ui:add <component>`; don't hand-write files the registry already has. The wrappers take item names only and reject flags — they exist for reusable `registry:ui` primitives and always write to `packages/ui`. Blocks, pages, custom registries, and the read-only flags go to the CLI directly — point it at the workspace, or it resolves the wrong `components.json`: `bunx shadcn@latest add button --dry-run --cwd packages/ui`.\n- Both run `scripts/postprocess.ts` afterwards: it strips `\"use client\"`, regenerates `index.ts`, and formats. `index.ts` is generated; add components with the CLI, not by editing it.\n- Components import primitives from the unified `radix-ui` package, not the per-primitive `@radix-ui/react-*` ones. The CLI adds whatever a component needs; review and commit the resulting `package.json` and `bun.lock` changes.\n- `bun ui:update` re-fetches every installed component in one `shadcn add` call, overwriting in place. Review `git diff` before committing; local edits are lost. Pass names (`bun ui:update button card`) to narrow it; unknown names are rejected rather than silently added, though a named component still pulls its registry dependencies.\n- Registry output is not uniform — read what it generated. Postprocessing is mechanical; API changes still need review. Convert React 18 patterns before committing — no lint rule covers any of these, so they are a review step:\n  - `<Context.Provider value={x}>` → `<Context value={x}>`\n  - `React.useContext(C)` → `React.use(C)`\n  - `React.ElementRef<T>` → `React.ComponentRef<T>` — `ElementRef` is a deprecated alias in `@types/react` 19. `bun --cwd apps/web check` reports it as a hint.\n- `components.json` needs `tailwind.config: \"\"`. That empty string is how the CLI detects Tailwind v4; without it the registry silently serves the v3-era components (`forwardRef`, no `data-slot`) into a v4 project.\n\n## Styling\n\n- Every component accepts `className` and passes it through `cn()` last — directly, or via the `className` slot on a `cva` variants call — so callers can override defaults without a specificity fight.\n- Use theme tokens (`bg-primary`, `text-muted-foreground`), never raw colors. Each consumer defines the values in its own `styles/globals.css` — `apps/app` and `apps/web` keep separate copies, so a palette change means editing both. `styles.css` here exists only to satisfy the shadcn CLI.\n- Class names must appear as complete literals — Tailwind scans text, so `` `bg-${color}-500` `` produces nothing.\n- Consuming apps must `@source` every directory here that holds class names, or those classes are stripped from their build.\n- Enter/exit utilities (`animate-in`, `fade-in-0`, `zoom-in-95`, `slide-in-from-*`) come from `tw-animate-css`, which each consumer imports separately — `apps/app` does, `apps/web` does not, because nothing it renders is animated. Adding an animated component to a consumer that lacks the import fails silently: Tailwind does not recognise the class names, drops them, and reports nothing, so the component simply renders without transitions.\n\n## Conventions\n\n- Named exports only — no default exports.\n- Variants via `class-variance-authority`; export the variants object when another component composes it (see `toggle.tsx` → `toggle-group.tsx`).\n- Prefer a Radix primitive over hand-rolled behavior — it brings the ARIA roles and keyboard handling with it. `ToggleGroup type=\"single\"` already renders `role=\"radiogroup\"` with `role=\"radio\"` items and arrow-key navigation, so callers add no keyboard code.\n","isInternal":false,"tokens":1140,"sizeBytes":4984},{"name":"CLAUDE.md","path":"packages/ui/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/packages/ui/CLAUDE.md","title":"Ui Skill","category":"anthropic-skill","format":"markdown","content":"@AGENTS.md\n","isInternal":false,"tokens":5,"sizeBytes":11},{"name":"validate-auth-schema.md","path":".claude/commands/validate-auth-schema.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/.claude/commands/validate-auth-schema.md","title":"Command: /validate-auth-schema","category":"command-prompt","format":"markdown","content":"# Validate Auth Schema\n\nCheck that the Drizzle schema in `db/schema/` still satisfies what Better Auth expects. Better Auth derives its required tables from the enabled plugins, so the generated JSON — not the upstream docs — is the source of truth:\n\n```bash\nbun run db/scripts/generate-auth-schema.ts\n```\n\nThe script builds a real auth instance from `apps/api/lib/auth.ts` with fixed placeholder credentials, every optional integration switched on, so the output is the full set of tables rather than the subset your environment happens to enable. Per field it carries the `type`, the `required`, `unique` and `index` flags, any literal `defaultValue`, and the reference target with its `onDelete`. Per table it carries `indexes`: the composite indexes a plugin declares.\n\nCompare that output against `db/schema/` and report:\n\n- Tables Better Auth expects that are missing, and tables no plugin in `auth.ts` needs any more.\n- Field-level drift: missing or extra columns, mismatched types, and `required` or `unique` flags that disagree.\n- Foreign keys pointing at the wrong table or column.\n- A `true` `index` flag, a table-level entry in `indexes`, a literal `defaultValue`, or an `onDelete` in the output is Better Auth asking for it: report a schema that lacks one. Indexes, defaults and cascades the project adds beyond that list are its own decisions — check those against the conventions in `db/AGENTS.md` and do not report them as drift.\n- Local naming that must stay mapped, not renamed away: Better Auth's `account` is our `identity` table.\n- Project-specific columns, indexes, and constraints are expected and fine on their own. Flag any that constrain a write Better Auth performs: a `NOT NULL` column with no database default breaks every insert into that table, and an extra unique or check constraint can reject a row Better Auth considers valid. Fields that must participate in its models belong in `additionalFields` rather than added to the table behind its back.\n\nReport findings with the specific fix for each. Do not run `db:push`, `db:generate`, or `db:migrate` as part of a validation — schema changes are a separate, deliberate step.\n","isInternal":false,"tokens":467,"sizeBytes":2166},{"name":"README.md","path":"packages/core/README.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/packages/core/README.md","title":"core Documentation","category":"plugin-manifest","format":"markdown","content":"# Core Package\n\nShared utilities and helpers used across the monorepo.\n","isInternal":false,"tokens":15,"sizeBytes":71},{"name":"README.md","path":"packages/typescript-config/README.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/packages/typescript-config/README.md","title":"typescript-config Documentation","category":"plugin-manifest","format":"markdown","content":"# TypeScript Configuration\n\nShared TypeScript configuration for the monorepo.\n\n## Usage\n\nExtend from the appropriate configuration in your `tsconfig.json`:\n\n```jsonc\n// React applications\n{ \"extends\": \"@repo/typescript-config/react.jsonc\" }\n\n// Node.js/Bun applications\n{ \"extends\": \"@repo/typescript-config/node.jsonc\" }\n\n// Cloudflare Workers\n{ \"extends\": \"@repo/typescript-config/cloudflare.jsonc\" }\n```\n\n## Available Configurations\n\n- `base.jsonc` -- Core strict-mode configuration shared by all targets\n- `react.jsonc` -- React applications with DOM types and JSX support\n- `node.jsonc` -- Node.js/Bun backend services\n- `cloudflare.jsonc` -- Cloudflare Workers edge functions\n","isInternal":false,"tokens":159,"sizeBytes":682},{"name":"README.md","path":"packages/ui/README.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/packages/ui/README.md","title":"ui Documentation","category":"plugin-manifest","format":"markdown","content":"# UI Components\n\nShared UI component library built on shadcn/ui (new-york style), Radix UI, and Tailwind CSS v4.\n\n[Documentation](https://reactstarter.com/frontend/ui)\n\n## Usage\n\n```typescript\nimport { Button, Card, Input, cn } from \"@repo/ui\";\n```\n\n## Commands\n\n```bash\nbun ui:add <component>    # Add a shadcn/ui component\nbun ui:update             # Re-fetch installed components\n```\n\nSee [AGENTS.md](./AGENTS.md) for the conventions these commands assume.\n\n## Structure\n\n```bash\ncomponents/       # shadcn/ui components\nhooks/            # Custom React hooks\nlib/              # Utilities (cn function)\nscripts/          # ui:add / ui:update and their postprocessing\nindex.ts          # Generated barrel export\n```\n\nConsuming apps must `@source` every directory here that holds class names, or those classes are stripped from their build. See [apps/app/tailwind.config.css](../../apps/app/tailwind.config.css) for an example.\n","isInternal":false,"tokens":224,"sizeBytes":930},{"name":"README.md","path":"packages/ws-protocol/README.md","rawUrl":"https://raw.githubusercontent.com/kriasoft/react-starter-kit/HEAD/packages/ws-protocol/README.md","title":"ws-protocol Documentation","category":"plugin-manifest","format":"markdown","content":"# WebSocket Protocol\n\nType-safe WebSocket protocol definitions using [WS-Kit](https://github.com/kriasoft/ws-kit).\n\n[Documentation](https://reactstarter.com/recipes/websockets)\n\n## Quick Start\n\n```bash\nbun run example       # Run example server\n```\n\n## Usage\n\n```typescript\nimport { z, message, rpc } from \"@ws-kit/zod\";\nimport { createRouter, withZod } from \"@ws-kit/zod\";\n\nconst Ping = message(\"PING\", { timestamp: z.number().optional() });\nconst Pong = message(\"PONG\", { timestamp: z.number() });\n\nconst router = createRouter<{ userId?: string }>()\n  .plugin(withZod())\n  .on(Ping, (ctx) => {\n    ctx.send(Pong, { timestamp: Date.now() });\n  });\n```\n\n## Structure\n\n```bash\nmessages.ts   # Message schema definitions\nrouter.ts     # Router factory with handlers\nexample.ts    # Example server\nindex.ts      # Public exports\n```\n","isInternal":false,"tokens":211,"sizeBytes":830}],"systemPromptSnippet":"<yakaai_skills repo=\"kriasoft/react-starter-kit\">\n<!-- File: AGENTS.md (Tokens: ~1420 | Category: root-instruction) -->\n## Monorepo Structure\n\n- `apps/web/` – Astro marketing site, served by an edge worker that also routes traffic to the app/api workers via service bindings\n- `apps/app/` – Main SPA (React, TanStack Router file-based routing)\n- `apps/api/` – API server (Hono + tRPC + Better Auth)\n- `apps/email/` – React Email templates (built before API dev server starts)\n- `packages/ui/` – shadcn/ui components (new-york style)\n- `packages/core/` – Shared utilities\n- `db/` – Drizzle ORM schemas and migrations (Neon PostgreSQL)\n- `infra/` – Terraform (Hyperdrive and optional R2 storage; Wrangler owns Workers and DNS)\n- `docs/` – VitePress docs; `docs/adr/` for architecture decision records\n\n## Tech Stack\n\n- **Toolchain:** Bun >=1.4.0 (package manager, scripts, local API server), TypeScript 7.0 (native compiler), Oxlint + Oxfmt, ESM (`\"type\": \"module\"`)\n- **Production runtime:** Cloudflare Workers (workerd) – not Bun. Code that ships must run on Workers APIs.\n- **Frontend:** React 19, TanStack Router, TanStack Query, Jotai, shadcn/ui (new-york), Tailwind CSS v4\n- **Backend:** Hono, tRPC 11, Better Auth (email OTP, passkey, Google OAuth, organizations)\n- **Database:** Neon PostgreSQL, Drizzle ORM (`snake_case` casing), Cloudflare Hyperdrive\n- **Email:** React Email, Resend\n- **Testing:** Vitest, Happy DOM, PGlite (in-process Postgres, no service to start)\n- **Deployment:** Cloudflare Workers (Wrangler), Terraform\n\n## Commands\n\n```bash\nbun dev                        # Start web + api + app concurrently\nbun run build                  # Build email, web, api, and app workspaces\nbun run test                   # Vitest (watch mode; --run for single run)\nbun lint                       # Oxlint\nbun run format:check           # Oxfmt (bun run format writes)\nbun typecheck                  # tsc --build (builds apps/email for its types)\nbun infra:check                # Terraform fmt + validate, no credentials or state\nbun ui:add <component>         # Add shadcn/ui component to packages/ui\nbun deploy:{staging,production} # Build and deploy api → app → web; no migrations\n\n# Per-app: bun {web,app,api}:{dev,build,deploy}; test for app/api, check for web\n# Database: bun db:{push,generate,migrate,studio,seed,export}\n#   :staging / :production on migrate, studio, export; seed stops at :staging;\n#   generate is local-only, and push refuses a non-local database\n```\n\n## Verification\n\n- Run the narrowest checks covering what you changed, then report what you ran. Never imply verification you did not perform.\n- Do not run migrations, deployments, or Terraform `plan`/`apply` unless explicitly asked – they act on shared environments.\n\n## Architecture\n\n- Three workers: web (marketing site + edge router), app (SPA assets), api (Hono server).\n- API worker has `nodejs_compat` enabled; web and app workers do NOT.\n- Web worker routes: `/api/*` → API worker, app routes → App worker, static → assets.\n- Service bindings connect workers internally (no public cross-worker URLs).\n- Per-workspace conventions live in subdirectory `AGENTS.md` files: `apps/api/`, `apps/app/`, `db/`, `infra/`, `packages/ui/`.\n\n## Agent Tooling\n\n- `AGENTS.md` files are the canonical instructions; per-tool files (`CLAUDE.md`, `.gemini/settings.json`) only point at them.\n- **Every directory with an `AGENTS.md` needs a sibling `CLAUDE.md` containing `@AGENTS.md`.** Codex scopes nested `AGENTS.md` to its directory tree natively; Claude Code's nested lookup matches only `CLAUDE.md`, so without the adapter it never loads the scoped file. Adding a scoped `AGENTS.md` without one is a silent no-op for Claude.\n- **Skills live in `.agents/skills/<name>/SKILL.md`, symlinked into `.claude/skills/<name>/`.** Both vendors build on the Agent Skills format, but only the discovery paths differ: Codex scans `.agents/skills/` from the repo root natively, Claude Code scans `.claude/skills/`. One canonical copy plus a symlink is what makes a skill visible to both. Third-party skills installed by the `skills` CLI are tracked in `skills-lock.json` – do not hand-edit those directories.\n- Skills are for workflows the model should recognise and reach for on its own; their `description` is the trigger. A prompt you invoke deliberately stays a Claude Code command in `.claude/commands/<name>.md`, which has no portable equivalent.\n- `*.local.md` and `*.local.json` are gitignored – personal prompts and settings stay out of the repo.\n\n## Design Philosophy\n\n- Simplest correct solution. No speculative abstractions – add them only when a real second use case exists.\n- No superficial work: no coverage-only tests, no redundant comments, no wrappers that just forward calls.\n- Fail loudly in core logic. Do not silently swallow errors or mask incorrect state.\n- Three similar lines are better than a premature abstraction.\n- Prefer explicit, readable code over clever or compressed patterns.\n- Use precise TypeScript types. Avoid `any` and unnecessary type assertions – let the compiler enforce correctness.\n- Document non-obvious trade-offs and decisions. Explain why, not what – every word must add value.\n\n## Formatting\n\n- Import order belongs to Oxfmt (`sortImports` in `.oxfmtrc.json`): applied by `bun run format`, checked by `bun run format:check` in CI and by lint-staged on commit. Do not reintroduce an editor code action or a lint rule for it.\n\n## Markdown\n\n- Prose is not hard-wrapped: keep each paragraph on one line and use paragraphs, lists and headings for structure. Oxfmt enforces this with `proseWrap: \"never\"`.\n- Keep a blank line after a VitePress container's opening marker and before its closing `:::`. Oxfmt does not recognise `:::`, so an adjacent line gets folded into the marker, turning the body into the container title and swallowing everything up to the next `:::`.\n\n<!-- File: db/AGENTS.md (Tokens: ~1108 | Category: root-instruction) -->\n## Schema Conventions\n\n- Every new table file must be re-exported from `schema/index.ts`. `drizzle.config.ts` points at that barrel rather than the directory, so an unexported table is invisible to `db:generate`, which reports no changes instead of failing.\n- Drizzle `casing: \"snake_case\"` – use camelCase in TypeScript, columns map to snake_case in DB.\n- Primary keys use application-generated prefixed CUID2 IDs: `generateAuthId(model)` for Better Auth models and `generateId(\"xxx\")` for domain tables. See `schema/id.ts` for the prefix map.\n- Timestamps: `timestamp({ withTimezone: true, mode: \"date\" })`. Every table has `createdAt` (`.defaultNow().notNull()`) and `updatedAt` (`.defaultNow().$onUpdate(() => new Date()).notNull()`).\n- `identity` table = Better Auth's `account` table, renamed via `account.modelName: \"identity\"` in auth config.\n- `member.role` (\"owner\", \"admin\", \"member\") and `invitation.status` are free `text`, not pgEnum – avoids fragile coupling with Better Auth's values.\n- `organization.metadata` is `text`, not JSONB – Better Auth handles serialization.\n- Migrations differ by role. A project generated from this kit keeps ordinary migration history – see `docs/database/migrations.md`. The starter kit itself squashes into a single `0000_init.sql`; contributors changing its schema should read `.github/CONTRIBUTING.md` first.\n\n## Extended Fields (beyond Better Auth defaults)\n\n- **Passkey:** `lastUsedAt` (security audits), `deviceName` (user-friendly label), `platform` (\"platform\" | \"cross-platform\").\n- **Invitation:** `acceptedAt`/`rejectedAt` are reserved for application hooks; Better Auth updates `status` but does not populate them.\n\n## Indexes and Constraints\n\n- Every foreign key column gets an index: `{table}_{column}_idx`. All foreign keys use `onDelete: \"cascade\"`.\n- Composite uniques: `member(userId, organizationId)`, `invitation(organizationId, email)`, `identity(providerId, accountId)`.\n- Better Auth keys an identity on `(providerId, accountId)` and throws when a lookup matches two rows. Never add an `issuer` column back – see the note on `identity` in `schema/user.ts`.\n- The `invitation` unique is a starter limitation: Better Auth creates a new row for a later invitation, so remove the constraint before supporting re-invites after acceptance, rejection, or cancellation.\n- `session.activeOrganizationId` has an index but no FK constraint (Better Auth design).\n\n## Seeds\n\n- Use `onConflictDoNothing()` for idempotent seeds (safe to rerun).\n\n## Testing\n\n- `createTestDatabase()` from `@repo/db/testing` boots PGlite and applies `migrations/`, so tests run on the schema production receives. No connection string, no service, no `.env.test` – there is deliberately no `test` environment in `drizzle.config.ts`.\n- It returns `{ db, reset, close }`. One instance at module scope (`await createTestDatabase()`), `reset()` between tests, `close()` in `afterAll`. `reset()` truncates every table in the schema, derived from the barrel, so a new table is covered automatically.\n- Use the test database when behavior depends on query scoping or database constraints. A mocked lookup cannot fail on a wrong `where`. See `docs/testing.md`.\n- `Database` (exported from `@repo/db`) is the driver-agnostic client type – postgres-js in production, PGlite in tests. Type parameters and helpers against it, not `PostgresJsDatabase`. Selected rows, relational queries and `.returning()` stay schema-typed; `execute()` and mutations without `.returning()` resolve to `unknown` (the command result belongs to the driver), and `$client` is not exposed. Add `.returning()` when a write needs to report anything.\n\n## Environment\n\n- `ENVIRONMENT` overrides `NODE_ENV` for env file selection. Database scripts use `production`, `staging`, and `dev`; the API runtime uses `production`, `staging`, and `development`.\n- `migrate`, `studio` and `export` have `:staging` / `:production` variants; `seed` stops at `:staging`; `generate` and `push` have none.\n- **`push` is enforced local, not merely documented as local.** `scripts/guard-push.ts` resolves the same `DATABASE_URL` the command would use and refuses a non-local host; `ALLOW_REMOTE_DB_PUSH=1` is the deliberate way past it. Reshaping in place is an unreviewed migration that can drop a column and its data.\n- `scripts/grant-app-role.sql` creates the least-privilege role Hyperdrive should use in staging and production. It is documentation-and-tooling only – nothing in the repo runs it.\n- Development loads `.env.{envName}.local` → `.env.local` → `.env`, first value wins. Staging and production load only `.env.{envName}.local`, override exported values, and throw if it is absent.\n\n<!-- File: infra/AGENTS.md (Tokens: ~774 | Category: root-instruction) -->\nTerraform for the durable Cloudflare resources the Workers consume. [`README.md`](./README.md) covers setup and usage, [ADR-002](../docs/adr/002-terraform-wrangler-boundary.md) the ownership decision, [ADR-003](../docs/adr/003-hcp-terraform-state.md) the state backend, and [`docs/specs/infra-terraform.md`](../docs/specs/infra-terraform.md) the full specification. Those carry the rationale and the operating procedure; this file is the short list of constraints that matter while editing `infra/`.\n\n## Ownership Boundary\n\n- Terraform provisions what the Workers consume: the two Hyperdrive configurations and the opt-in R2 uploads bucket. Nothing else. Worker names, code, routes, custom domains, bindings, vars, secrets, and assets belong to `apps/*/wrangler.jsonc` – adding a `cloudflare_workers_*` or DNS resource here is the change that breaks the design, and it will not announce itself as a conflict until a deploy overwrites something.\n- The handoff is manual and non-secret: Hyperdrive IDs and the R2 bucket name are pasted into `wrangler.jsonc` once per environment. A change to a handoff output is incomplete without the corresponding `wrangler.jsonc` update.\n\n## Layout and Naming\n\n- The two roots stay structurally parallel: same provider constraints, same module interface, same variable and output names. A structural change to one belongs in the other.\n- Their _values_ may differ on purpose. Enabling an optional resource in staging before production is a supported rollout, not drift – see the [file uploads recipe](../docs/recipes/file-uploads.md). Do not copy an environment-specific value across unless the change is meant for both.\n- Resource logic lives in `modules/cloudflare`. Each root holds its environment identity, provider and `cloud` configuration, root variables and outputs, and the module call.\n- Resource values follow `{project_slug}-{environment}[-role]`. `project_slug` must match the worker name prefix in `apps/*/wrangler.jsonc`, or the bindings point at resources that do not exist.\n- Resource identifiers name the concrete thing (`cloudflare_hyperdrive_config.cached`); module names describe the role (`module.edge`).\n\n## Variables and Outputs\n\n- Encode structural constraints in `validation` blocks so they fail locally rather than mid-apply, and write the `error_message` as the fix rather than the rule – see `uploads_cors_origins` rejecting `\"*\"`.\n- Inputs holding credentials are `sensitive = true` (`database_url`). Outputs are stable, non-secret identifiers only – they are printed into CI summaries and committed to `wrangler.jsonc`.\n\n## Running Terraform\n\nValidate locally with the same credential-free check CI runs:\n\n```bash\nbun infra:check\n```\n\nThat is `terraform fmt -check` plus `init -backend=false` and `validate` for each root. `-backend=false` installs providers and modules without contacting the backend, so it needs no HCP token and reads no state.\n\n- **Never run `plan`, `apply`, `destroy`, `import`, or any state mutation.** `plan` is not the safe half: HCP Terraform executes the configuration it is handed in the workspace's privileged run environment, with that workspace's state and variables, so planning uncommitted work exercises real credentials against real infrastructure. `bun infra:check` is the branch-level signal; applying is a manual dispatch of `.github/workflows/infra.yml` from `main`.\n- Never commit state, `.terraform/`, credentials, or `.tfvars` – state holds the database password. `infra/.gitignore` covers these; do not narrow it.\n\n<!-- File: CLAUDE.md (Tokens: ~57 | Category: claude-rule) -->\n@AGENTS.md\n\n## Claude-Specific Guidance\n\n- Use Plan mode for multi-file or architectural changes.\n- Prefer an existing project skill or command over improvising the same workflow.\n- See \"Agent Tooling\" in `AGENTS.md` before adding a command or skill.\n\n<!-- File: db/CLAUDE.md (Tokens: ~5 | Category: claude-rule) -->\n@AGENTS.md\n\n<!-- File: infra/CLAUDE.md (Tokens: ~5 | Category: claude-rule) -->\n@AGENTS.md\n\n<!-- File: .github/copilot-instructions.md (Tokens: ~29 | Category: copilot-instructions) -->\nRead AGENTS.md in the repository root and any subdirectory AGENTS.md files for project context, conventions, and architecture details before making changes.\n\n<!-- File: .agents/skills/merge-seed/SKILL.md (Tokens: ~1489 | Category: anthropic-skill) -->\n---\nname: merge-seed\ndescription: Merge upstream React Starter Kit updates (the `seed` remote) into main, preserving this project's identity, scope, and behavior. Use when asked to sync, pull, or merge the seed / starter kit / upstream template.\n---\n\n# Merge seed updates\n\nThis project is a fork of React Starter Kit. `seed/main` is upstream; `main` is upstream plus this project's divergence. Syncing is a recurring operation.\n\n## Core rule\n\n**Upstream owns mechanism. This project owns identity and scope.**\n\nAdopt from upstream: dependency and toolchain versions, build and config structure, framework API updates, refactors, newly required bindings and variables.\n\nKeep local: names, domains, routes, worker and service identifiers; product copy and positioning; plans, features, scope; project-only integrations and config; deliberate additions and deletions.\n\nWhen a hunk mixes both, take upstream's structure and reapply local values. **Never take a whole side merely to clear a conflict** — whole-file resolution is valid only where a rule below makes that side authoritative. The repository defines what counts as local identity and scope: `AGENTS.md`, `docs/adr/`, `docs/product/`, and the current config on `main`.\n\n## Merge\n\n```bash\ngit status --short   # must be clean\n\ngit remote get-url seed || git remote add seed https://github.com/kriasoft/react-starter-kit.git\ngit fetch seed\n\nMB=$(git merge-base main seed/main)\ngit log --oneline \"$MB..seed/main\"   # read before resolving anything\n\ngit switch -c \"chore/merge-seed-$(git rev-parse --short seed/main)\" main\ngit -c merge.conflictStyle=zdiff3 merge --no-ff --no-commit --no-rerere-autoupdate seed/main\ngit diff --name-only --diff-filter=U   # unresolved conflicts\n```\n\n`--no-commit` stops git auto-committing a clean merge, so the commit and its subject are always ours to write. `--no-rerere-autoupdate` keeps every conflict in that list even when `rerere` has a recorded resolution to replay. If the branch already exists, an earlier sync was left unfinished — inspect it and resume or delete it deliberately, never force-reset over it.\n\n`rerere` writes its replayed resolution straight into the working tree, so a conflicted file can arrive already looking finished. Treat pre-filled content as unreviewed; `git rerere forget <path>` to redo it.\n\nIn ordinary text conflicts `zdiff3` includes the merge base, which is how to tell deliberate local divergence from a line upstream simply changed. Where there is no usable inline base — binary, add/add, modify/delete — read the index stages instead: `git ls-files -u <path>` lists which exist, then `git show :1:<path>` base, `:2:` ours, `:3:` theirs. Only the sides that still have content are present, so add/add has no base and a modify/delete has no stage for the side that deleted. For an ambiguous hunk, read the commit behind it: `git log -p \"$MB..seed/main\" -- <path>`.\n\nResolve, stage each resolved path, then commit with the subject in **Land**.\n\n## Resolution rules\n\n- **Infra, Wrangler, Terraform** — keep local names, hostnames, zones, routes, IDs, and values; adopt upstream compatibility dates, new bindings, and newly required variables, then fill in local values.\n- **Env files** — union: keep local values and project-only keys, add new upstream keys, and drop upstream-deleted keys only when the mechanism behind them is also gone.\n- **Manifests** — adopt upstream versions and upstream's own scripts; keep project-only dependencies and scripts still in use; drop local entries an upstream migration made obsolete.\n- **Lockfile** — never hand-merge: resolve every manifest first, take upstream's lockfile, then `bun install` and stage the regenerated result.\n- **Product copy — marketing pages, docs** — local copy and positioning are authoritative; port only technical changes.\n- **App and API code** — prefer upstream's refactor, then reapply local behavior on the new structure; a rewritten config or key list must still carry project-only keys.\n- **Migrations** — a migration that may have run is immutable. Never resolve changes to one in place: if upstream modified it, escalate; otherwise keep local migration history and express incoming schema changes as new migrations.\n- **ADRs** — local numbering is stable; renumber a colliding incoming ADR and fix its links.\n- **Local deletion vs upstream edit, and upstream renames** — deliberate deletions stay deleted; follow renames, carrying local content to the new path.\n\nDon't bring in upstream features that conflict with documented product scope.\n\n## Verify\n\n```bash\nbun install --frozen-lockfile && bun typecheck && bun lint && bun run test -- --run && bun run build\n```\n\n`--frozen-lockfile` fails if the manifests and lockfile disagree, which is what verification should prove. Green checks are still necessary, not sufficient — they cannot see lost identity or copy. Compare the result against each endpoint, per conflicted file and overall:\n\n```bash\ngit diff main..HEAD -- <path>        # upstream's mechanism, plus adaptations it forced\ngit diff seed/main..HEAD -- <path>   # the fork's divergence as it now stands\n```\n\nThese are endpoint comparisons, not per-side attribution: a local value may legitimately move or change shape in the first diff when upstream restructures around it, and the second carries the fork's whole history, not just this merge. What matters is that every difference is explainable — upstream mechanism adopted, or local intent kept. An unexplainable one is a bad resolution.\n\nThen confirm:\n\n- no leftover conflict markers: `git diff --check main..HEAD | grep 'conflict marker'` — they survive in files no build step reads, such as env and docs\n- no upstream branding, placeholder values, or template copy in changed files\n- no local config or key silently dropped by an upstream refactor\n- no stale paths or call sites left by an upstream rename\n- `AGENTS.md` invariants still hold\n- nothing unexpected in files that auto-merged (`git diff main..HEAD --stat`)\n\n## Land\n\nCommit subject `chore: merge seed up to <short-sha>`, notable resolutions in the body. Then, once verification passes:\n\n```bash\ngit switch main && git merge --ff-only <branch> && git branch -d <branch>\n```\n\nIf `--ff-only` is refused, `main` advanced during the merge: integrate the new `main` into the branch and re-verify before landing.\n\n## Escalate\n\nStop and report — file, upstream intent, local intent, the open choice — instead of guessing, when upstream modifies a migration that may have run or changes live-data semantics, ships a feature against documented product scope, or when preserving both mechanism and local behavior is genuinely ambiguous.\n\n<!-- File: .agents/skills/shadcn/cli.md (Tokens: ~3677 | Category: anthropic-skill) -->\n# shadcn CLI Reference\n\nConfiguration is read from `components.json`.\n\n> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.\n\n> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag.\n\n## Contents\n\n- Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build\n- Templates: next, vite, start, react-router, astro\n- Presets: named, code, URL formats and fields\n- Switching presets\n\n---\n\n## Commands\n\n### `init` — Initialize or create a project\n\n```bash\nnpx shadcn@latest init [components...] [options]\n```\n\nInitializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--template <template>` | `-t` | Template (next, start, vite, next-monorepo, react-router) | — |\n| `--preset [name]` | `-p` | Preset configuration (named, code, or URL) | — |\n| `--yes` | `-y` | Skip confirmation prompt | `true` |\n| `--defaults` | `-d` | Use defaults (`--template=next --preset=base-nova`) | `false` |\n| `--force` | `-f` | Force overwrite existing configuration | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n| `--name <name>` | `-n` | Name for new project | — |\n| `--silent` | `-s` | Mute output | `false` |\n| `--rtl` |  | Enable RTL support | — |\n| `--reinstall` |  | Re-install existing UI components | `false` |\n| `--monorepo` |  | Scaffold a monorepo project | — |\n| `--no-monorepo` |  | Skip the monorepo prompt | — |\n\n`npx shadcn@latest create` is an alias for `npx shadcn@latest init`.\n\n### `apply` — Apply a preset to an existing project\n\n```bash\nnpx shadcn@latest apply [preset] [options]\n```\n\nApplies a preset to an existing project, overwriting preset-driven config, fonts, CSS variables, and detected UI components.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--preset <preset>` | — | Preset configuration (named, code, or URL) | — |\n| `--yes` | `-y` | Skip confirmation prompt | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n| `--silent` | `-s` | Mute output | `false` |\n\n`[preset]` is a shorthand for `--preset <preset>`. If both are provided, they must match. If no preset is provided, the CLI offers to open the custom preset builder on `ui.shadcn.com/create`.\n\n### `add` — Add components\n\n> **IMPORTANT:** To compare local components against upstream or to preview changes, ALWAYS use `npx shadcn@latest add <component> --dry-run`, `--diff`, or `--view`. NEVER fetch raw files from GitHub or other sources manually. The CLI handles registry resolution, file paths, and CSS diffing automatically.\n\n```bash\nnpx shadcn@latest add [components...] [options]\n```\n\nAccepts component names, registry-prefixed names (`@magicui/shimmer-button`), GitHub item addresses (`owner/repo/item`), URLs, or local paths.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--yes` | `-y` | Skip confirmation prompt | `false` |\n| `--overwrite` | `-o` | Overwrite existing files | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n| `--all` | `-a` | Add all available components | `false` |\n| `--path <path>` | `-p` | Target path for the component | — |\n| `--silent` | `-s` | Mute output | `false` |\n| `--dry-run` |  | Preview all changes without writing files | `false` |\n| `--diff [path]` |  | Show diffs. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |\n| `--view [path]` |  | Show file contents. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |\n\n#### Dry-Run Mode\n\nUse `--dry-run` to preview what `add` would do without writing any files. `--diff` and `--view` both imply `--dry-run`.\n\n```bash\n# Preview all changes.\nnpx shadcn@latest add button --dry-run\n\n# Show diffs for all files (top 5).\nnpx shadcn@latest add button --diff\n\n# Show the diff for a specific file.\nnpx shadcn@latest add button --diff button.tsx\n\n# Show contents for all files (top 5).\nnpx shadcn@latest add button --view\n\n# Show the full content of a specific file.\nnpx shadcn@latest add button --view button.tsx\n\n# Works with URLs too.\nnpx shadcn@latest add https://api.npoint.io/abc123 --dry-run\n\n# Works with public GitHub registries too.\nnpx shadcn@latest add owner/repo/item --dry-run\n\n# CSS diffs.\nnpx shadcn@latest add button --diff globals.css\n```\n\n**When to use dry-run:**\n\n- When the user asks \"what files will this add?\" or \"what will this change?\" — use `--dry-run`.\n- Before overwriting existing components — use `--diff` to preview the changes first.\n- When the user wants to inspect component source code without installing — use `--view`.\n- When checking what CSS changes would be made to `globals.css` — use `--diff globals.css`.\n- When the user asks to review or audit third-party registry code before installing — use `--view` to inspect the source.\n\n> **`npx shadcn@latest add --dry-run` vs `npx shadcn@latest view`:** Prefer `npx shadcn@latest add --dry-run/--diff/--view` over `npx shadcn@latest view` when the user wants to preview changes to their project. `npx shadcn@latest view` only shows raw registry metadata. `npx shadcn@latest add --dry-run` shows exactly what would happen in the user's project: resolved file paths, diffs against existing files, and CSS updates. Use `npx shadcn@latest view` only when the user wants to browse registry info without a project context.\n\n#### Smart Merge from Upstream\n\nSee [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full workflow.\n\n### `search` — Search registries\n\n```bash\nnpx shadcn@latest search [registries...] [options]\n```\n\nFuzzy search across registries. Also aliased as `npx shadcn@latest list`. Supports namespaces (`@acme`), public GitHub registry sources (`owner/repo`), and registry catalog URLs. Without `-q`, lists all items. When no registries are passed, searches every registry configured in `components.json`.\n\n| Flag | Short | Description | Default |\n| --- | --- | --- | --- |\n| `--query <query>` | `-q` | Search query | — |\n| `--type <type>` | `-t` | Filter by item type (e.g. `ui`, `block`, `hook`); comma-separated | — |\n| `--limit <number>` | `-l` | Max items to display | `100` |\n| `--offset <number>` | `-o` | Items to skip | `0` |\n| `--json` |  | Output as JSON | `false` |\n| `--cwd <cwd>` | `-c` | Working directory | current |\n\n### `view` — View item details\n\n```bash\nnpx shadcn@latest view <items...> [options]\n```\n\nDisplays item info including file contents. Examples: `npx shadcn@latest view @shadcn/button`, `npx shadcn@latest view owner/repo/item`.\n\n### `docs` — Get component documentation URLs\n\n```bash\nnpx shadcn@latest docs <components...> [options]\n```\n\nOutputs resolved URLs for component documentation, examples, and API references. Accepts one or more component names. Fetch the URLs to get the actual content.\n\nExample output for `npx shadcn@latest docs input button`:\n\n```\nbase  radix\n\ninput\n  docs      https://ui.shadcn.com/docs/components/radix/input\n  examples  https://raw.githubusercontent.com/.../examples/input-example.tsx\n\nbutton\n  docs      https://ui.shadcn.com/docs/components/radix/button\n  examples  https://raw.githubusercontent.com/.../examples/button-example.tsx\n```\n\nSome components include an `api` link to the underlying library (e.g. `cmdk` for the command component).\n\n### `diff` — Check for updates\n\nDo not use this command. Use `npx shadcn@latest add --diff` instead.\n\n### `info` — Project information\n\n```bash\nnpx shadcn@latest info [options]\n```\n\nDisplays project info and `components.json` configuration. Run this first to discover the project's framework, aliases, Tailwind version, and resolved paths.\n\n| Flag          | Short | Description       | Default |\n| ------------- | ----- | ----------------- | ------- |\n| `--cwd <cwd>` | `-c`  | Working directory | current |\n\n**Project Info fields:**\n\n| Field | Type | Meaning |\n| --- | --- | --- |\n| `framework` | `string` | Detected framework (`next`, `vite`, `react-router`, `start`, etc.) |\n| `frameworkVersion` | `string` | Framework version (e.g. `15.2.4`) |\n| `isSrcDir` | `boolean` | Whether the project uses a `src/` directory |\n| `isRSC` | `boolean` | Whether React Server Components are enabled |\n| `isTsx` | `boolean` | Whether the project uses TypeScript |\n| `tailwindVersion` | `string` | `\"v3\"` or `\"v4\"` |\n| `tailwindConfigFile` | `string` | Path to the Tailwind config file |\n| `tailwindCssFile` | `string` | Path to the global CSS file |\n| `aliasPrefix` | `string` | Import alias prefix (e.g. `@`, `~`, `@/`) |\n| `packageManager` | `string` | Detected package manager (`npm`, `pnpm`, `yarn`, `bun`) |\n\n**Components.json fields:**\n\n| Field | Type | Meaning |\n| --- | --- | --- |\n| `base` | `string` | Primitive library (`radix` or `base`) — determines component APIs and available props |\n| `style` | `string` | Visual style (e.g. `nova`, `vega`) |\n| `rsc` | `boolean` | RSC flag from config |\n| `tsx` | `boolean` | TypeScript flag |\n| `tailwind.config` | `string` | Tailwind config path |\n| `tailwind.css` | `string` | Global CSS path — this is where custom CSS variables go |\n| `iconLibrary` | `string` | Icon library — determines icon import package (e.g. `lucide-react`, `@tabler/icons-react`) |\n| `aliases.components` | `string` | Component import alias (e.g. `@/components`) |\n| `aliases.utils` | `string` | Utils import alias (e.g. `@/lib/utils`) |\n| `aliases.ui` | `string` | UI component alias (e.g. `@/components/ui`) |\n| `aliases.lib` | `string` | Lib alias (e.g. `@/lib`) |\n| `aliases.hooks` | `string` | Hooks alias (e.g. `@/hooks`) |\n| `resolvedPaths` | `object` | Absolute file-system paths for each alias |\n| `registries` | `object` | Configured custom registries |\n\n**Links fields:**\n\nThe `info` output includes a **Links** section with templated URLs for component docs, source, and examples. For resolved URLs, use `npx shadcn@latest docs <component>` instead.\n\n### `build` — Build a custom registry\n\n```bash\nnpx shadcn@latest build [registry] [options]\n```\n\nBuilds `registry.json` into individual JSON files for distribution. Default input: `./registry.json`, default output: `./public/r`.\n\nFor authoring rules, `include`, item definitions, `registryDependencies`, and GitHub registry behavior, see [registry.md](./registry.md).\n\n| Flag              | Short | Description       | Default      |\n| ----------------- | ----- | ----------------- | ------------ |\n| `--output <path>` | `-o`  | Output directory  | `./public/r` |\n| `--cwd <cwd>`     | `-c`  | Working directory | current      |\n\n---\n\n## Templates\n\n| Value          | Framework      | Monorepo support |\n| -------------- | -------------- | ---------------- |\n| `next`         | Next.js        | Yes              |\n| `vite`         | Vite           | Yes              |\n| `start`        | TanStack Start | Yes              |\n| `react-router` | React Router   | Yes              |\n| `astro`        | Astro          | Yes              |\n| `laravel`      | Laravel        | No               |\n\nAll templates support monorepo scaffolding via the `--monorepo` flag. When passed, the CLI uses a monorepo-specific template directory (e.g. `next-monorepo`, `vite-monorepo`). When neither `--monorepo` nor `--no-monorepo` is passed, the CLI prompts interactively. Laravel does not support monorepo scaffolding.\n\n---\n\n## Presets\n\nThree ways to specify a preset via `--preset`:\n\n1. **Named:** `--preset nova` or `--preset lyra`\n2. **Code:** `--preset a2r6bw` (version-prefixed base62 string, e.g. `a2r6bw` or `b0`)\n3. **URL:** `--preset \"https://ui.shadcn.com/init?base=radix&style=nova&...\"`\n\n> **IMPORTANT:** Never try to decode, fetch, or resolve preset codes manually. Preset codes are opaque — pass them directly to `npx shadcn@latest init --preset <code>` and let the CLI handle resolution. Use `npx shadcn@latest apply --preset <code>` when overwriting an existing project's preset.\n\n## Switching Presets\n\nAsk the user first: **overwrite**, **merge**, or **skip** existing components?\n\n- **Overwrite / Re-install** → `npx shadcn@latest apply --preset <code>`. Overwrites all detected component files with the new preset styles. Use when the user hasn't customized components.\n- **Merge** → `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to get the list of installed components and use the [smart merge workflow](./SKILL.md#updating-components) to update them one by one, preserving local changes. Use when the user has customized components.\n- **Skip** → `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS variables, leaves existing components as-is.\n\nAlways run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.\n\n<!-- File: .agents/skills/shadcn/customization.md (Tokens: ~1460 | Category: anthropic-skill) -->\n# Customization & Theming\n\nComponents reference semantic CSS variable tokens. Change the variables to change every component.\n\n## Contents\n\n- How it works (CSS variables → Tailwind utilities → components)\n- Color variables and OKLCH format\n- Dark mode setup\n- Changing the theme (presets, CSS variables)\n- Adding custom colors (Tailwind v3 and v4)\n- Border radius\n- Customizing components (variants, className, wrappers)\n- Checking for updates\n\n---\n\n## How It Works\n\n1. CSS variables defined in `:root` (light) and `.dark` (dark mode).\n2. Tailwind maps them to utilities: `bg-primary`, `text-muted-foreground`, etc.\n3. Components use these utilities — changing a variable changes all components that reference it.\n\n---\n\n## Color Variables\n\nEvery color follows the `name` / `name-foreground` convention. The base variable is for backgrounds, `-foreground` is for text/icons on that background.\n\n| Variable | Purpose |\n| --- | --- |\n| `--background` / `--foreground` | Page background and default text |\n| `--card` / `--card-foreground` | Card surfaces |\n| `--primary` / `--primary-foreground` | Primary buttons and actions |\n| `--secondary` / `--secondary-foreground` | Secondary actions |\n| `--muted` / `--muted-foreground` | Muted/disabled states |\n| `--accent` / `--accent-foreground` | Hover and accent states |\n| `--destructive` / `--destructive-foreground` | Error and destructive actions |\n| `--border` | Default border color |\n| `--input` | Form input borders |\n| `--ring` | Focus ring color |\n| `--chart-1` through `--chart-5` | Chart/data visualization |\n| `--sidebar-*` | Sidebar-specific colors |\n| `--surface` / `--surface-foreground` | Secondary surface |\n\nColors use OKLCH: `--primary: oklch(0.205 0 0)` where values are lightness (0–1), chroma (0 = gray), and hue (0–360).\n\n---\n\n## Dark Mode\n\nClass-based toggle via `.dark` on the root element. In Next.js, use `next-themes`:\n\n```tsx\nimport { ThemeProvider } from \"next-themes\";\n\n<ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n  {children}\n</ThemeProvider>;\n```\n\n---\n\n## Changing the Theme\n\n```bash\n# Apply a preset code from ui.shadcn.com.\nnpx shadcn@latest apply --preset a2r6bw\n\n# Positional shorthand also works.\nnpx shadcn@latest apply a2r6bw\n\n# Switch to a named preset and overwrite existing components.\nnpx shadcn@latest apply --preset nova\n\n# Preserve existing components instead.\nnpx shadcn@latest init --preset nova --force --no-reinstall\n\n# Use a custom theme URL.\nnpx shadcn@latest apply --preset \"https://ui.shadcn.com/init?base=radix&style=nova&theme=blue&...\"\n```\n\nOr edit CSS variables directly in `globals.css`.\n\n---\n\n## Adding Custom Colors\n\nAdd variables to the file at `tailwindCssFile` from `npx shadcn@latest info` (typically `globals.css`). Never create a new CSS file for this.\n\n```css\n/* 1. Define in the global CSS file. */\n:root {\n  --warning: oklch(0.84 0.16 84);\n  --warning-foreground: oklch(0.28 0.07 46);\n}\n.dark {\n  --warning: oklch(0.41 0.11 46);\n  --warning-foreground: oklch(0.99 0.02 95);\n}\n```\n\n```css\n/* 2a. Register with Tailwind v4 (@theme inline). */\n@theme inline {\n  --color-warning: var(--warning);\n  --color-warning-foreground: var(--warning-foreground);\n}\n```\n\nWhen `tailwindVersion` is `\"v3\"` (check via `npx shadcn@latest info`), register in `tailwind.config.js` instead:\n\n```js\n// 2b. Register with Tailwind v3 (tailwind.config.js).\nmodule.exports = {\n  theme: {\n    extend: {\n      colors: {\n        warning: \"oklch(var(--warning) / <alpha-value>)\",\n        \"warning-foreground\":\n          \"oklch(var(--warning-foreground) / <alpha-value>)\",\n      },\n    },\n  },\n};\n```\n\n```tsx\n// 3. Use in components.\n<div className=\"bg-warning text-warning-foreground\">Warning</div>\n```\n\n---\n\n## Border Radius\n\n`--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`).\n\n---\n\n## Customizing Components\n\nSee also: [rules/styling.md](./rules/styling.md) for Incorrect/Correct examples.\n\nPrefer these approaches in order:\n\n### 1. Built-in variants\n\n```tsx\n<Button variant=\"outline\" size=\"sm\">\n  Click\n</Button>\n```\n\n### 2. Tailwind classes via `className`\n\n```tsx\n<Card className=\"mx-auto max-w-md\">...</Card>\n```\n\n### 3. Add a new variant\n\nEdit the component source to add a variant via `cva`:\n\n```tsx\n// components/ui/button.tsx\nwarning: \"bg-warning text-warning-foreground hover:bg-warning/90\",\n```\n\n### 4. Wrapper components\n\nCompose shadcn/ui primitives into higher-level components:\n\n```tsx\nexport function ConfirmDialog({ title, description, onConfirm, children }) {\n  return (\n    <AlertDialog>\n      <AlertDialogTrigger asChild>{children}</AlertDialogTrigger>\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogTitle>{title}</AlertDialogTitle>\n          <AlertDialogDescription>{description}</AlertDialogDescription>\n        </AlertDialogHeader>\n        <AlertDialogFooter>\n          <AlertDialogCancel>Cancel</AlertDialogCancel>\n          <AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n  );\n}\n```\n\n---\n\n## Checking for Updates\n\n```bash\nnpx shadcn@latest add button --diff\n```\n\nTo preview exactly what would change before updating, use `--dry-run` and `--diff`:\n\n```bash\nnpx shadcn@latest add button --dry-run        # see all affected files\nnpx shadcn@latest add button --diff button.tsx # see the diff for a specific file\n```\n\nSee [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full smart merge workflow.\n\n<!-- File: .agents/skills/shadcn/evals/evals.json (Tokens: ~1387 | Category: anthropic-skill) -->\n{\n  \"skill_name\": \"shadcn\",\n  \"evals\": [\n    {\n      \"id\": 1,\n      \"prompt\": \"I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.\",\n      \"expected_output\": \"A React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses FieldGroup and Field components for form layout instead of raw div with space-y\",\n        \"Uses Switch for independent on/off notification toggles (not looping Button with manual active state)\",\n        \"Uses data-invalid on Field and aria-invalid on the input control for validation states\",\n        \"Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing\",\n        \"Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500\",\n        \"No manual dark: color overrides\"\n      ]\n    },\n    {\n      \"id\": 2,\n      \"prompt\": \"Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn/ui with radix-nova preset and tabler icons.\",\n      \"expected_output\": \"A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Includes DialogTitle for accessibility (visible or with sr-only class)\",\n        \"Avatar component includes AvatarFallback\",\n        \"Icons on buttons use the data-icon attribute (data-icon=\\\"inline-start\\\" or data-icon=\\\"inline-end\\\")\",\n        \"No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)\",\n        \"Uses tabler icons (@tabler/icons-react) instead of lucide-react\",\n        \"Uses asChild for custom triggers (radix preset)\"\n      ]\n    },\n    {\n      \"id\": 3,\n      \"prompt\": \"Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn/ui with base-nova preset and lucide icons.\",\n      \"expected_output\": \"A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)\",\n        \"Uses Skeleton component for loading placeholders instead of custom animate-pulse divs\",\n        \"Uses Badge component for percentage change instead of custom styled spans\",\n        \"Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600\",\n        \"Uses gap-* instead of space-y-* or space-x-* for spacing\",\n        \"Uses size-* when width and height are equal instead of separate w-* h-*\"\n      ]\n    },\n    {\n      \"id\": 4,\n      \"prompt\": \"I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Build a chat conversation view: a scrollable thread of messages from two different people, each with an avatar, sender name, timestamp, and message bubble. A couple of messages include an image attachment and a PDF file attachment, and there's a 'Today' divider separating the days.\",\n      \"expected_output\": \"A React component composing MessageScroller, Message, Bubble, Attachment, and Marker from the registry instead of hand-rolled bubble/divider/attachment markup.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses MessageScroller (MessageScrollerProvider, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem) for the scrollable thread instead of a raw overflow-y-auto div or ScrollArea\",\n        \"Wraps each row in MessageScrollerItem inside MessageScrollerContent\",\n        \"Uses Message with MessageAvatar/MessageContent/MessageHeader for row layout instead of custom flex divs\",\n        \"Uses Bubble + BubbleContent for the message surface instead of a styled div with bg-muted/bg-primary\",\n        \"Uses Attachment (AttachmentMedia, AttachmentContent, AttachmentTitle, AttachmentDescription) for the file and image attachments instead of Item or a custom card\",\n        \"Uses Marker (variant=\\\"separator\\\") for the 'Today' divider instead of Separator plus a centered label\",\n        \"Uses semantic color tokens and gap-* spacing; no raw colors like bg-emerald-500 and no space-y-*\",\n        \"Includes \\\"use client\\\" when the component uses state or event handlers (isRSC)\"\n      ]\n    },\n    {\n      \"id\": 5,\n      \"prompt\": \"Using shadcn/ui (base-nova preset, lucide icons), build a streaming AI chat UI. The assistant's reply streams in while it generates, the view auto-scrolls to follow the latest content but stops following if the user scrolls up to read earlier messages, a 'jump to latest' button appears when the user has scrolled away from the bottom, and a subtle 'thinking…' shimmer shows while the model is generating.\",\n      \"expected_output\": \"A React component that delegates scroll/anchor behavior to MessageScroller and uses MessageScrollerButton for jump-to-latest and the shimmer utility for the thinking indicator — no hand-rolled scroll logic or custom shimmer keyframes.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Uses MessageScroller with MessageScrollerProvider (autoScroll) and scrollAnchor on message items for the stick-to-bottom/follow behavior instead of a custom useStickToBottom hook or ResizeObserver/scrollTop wiring\",\n        \"Uses MessageScrollerButton for the jump-to-latest control instead of a hand-built conditional button driven by manual scroll-position state\",\n        \"Uses the shimmer utility class for the 'thinking…' indicator instead of a custom @keyframes or bg-clip-text gradient animation\",\n        \"Wraps each message row in MessageScrollerItem inside MessageScrollerContent\",\n        \"Uses Message + Bubble + BubbleContent for the conversation rows instead of hand-rolled bubble divs\",\n        \"Uses semantic color tokens and gap-* spacing; includes \\\"use client\\\" (isRSC)\"\n      ]\n    }\n  ]\n}\n\n<!-- File: .agents/skills/shadcn/mcp.md (Tokens: ~874 | Category: anthropic-skill) -->\n# shadcn MCP Server\n\nThe CLI includes an MCP server that lets AI assistants search, browse, view, and install items from registries.\n\n---\n\n## Setup\n\n```bash\nshadcn mcp        # start the MCP server (stdio)\nshadcn mcp init   # write config for your editor\n```\n\nEditor config files:\n\n| Editor      | Config file                     |\n| ----------- | ------------------------------- |\n| Claude Code | `.mcp.json`                     |\n| Cursor      | `.cursor/mcp.json`              |\n| VS Code     | `.vscode/mcp.json`              |\n| OpenCode    | `opencode.json`                 |\n| Codex       | `~/.codex/config.toml` (manual) |\n\n---\n\n## Tools\n\n> **Tip:** MCP tools handle registry operations (search, view, install). For project configuration (aliases, framework, Tailwind version), use `npx shadcn@latest info` — there is no MCP equivalent.\n\n### `shadcn:get_project_registries`\n\nReturns registry names from `components.json`. Errors if no `components.json` exists.\n\n**Input:** none\n\n### `shadcn:list_items_in_registries`\n\nLists all items from one or more registries. Registries can be configured namespaces such as `@acme`, public GitHub sources such as `owner/repo`, or registry catalog URLs. Omit `registries` to list from every registry configured in `components.json`.\n\n**Input:** `registries` (string[], optional — omit for all configured), `types` (string[], optional — e.g. `[\"ui\", \"block\"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)\n\n### `shadcn:search_items_in_registries`\n\nFuzzy search across registries. Registries can be configured namespaces, public GitHub sources, or registry catalog URLs. Omit `registries` to search every registry configured in `components.json` — e.g. \"find me a hero\" across all configured registries.\n\n**Input:** `registries` (string[], optional — omit for all configured), `query` (string), `types` (string[], optional — e.g. `[\"ui\", \"block\"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)\n\n### `shadcn:view_items_in_registries`\n\nView item details including full file contents.\n\n**Input:** `items` (string[]) — e.g. `[\"@shadcn/button\", \"@shadcn/card\", \"owner/repo/item\"]`\n\n### `shadcn:get_item_examples_from_registries`\n\nFind usage examples and demos with source code. Omit `registries` to search every registry configured in `components.json`.\n\n**Input:** `registries` (string[], optional — omit for all configured), `query` (string) — e.g. `\"accordion-demo\"`, `\"button example\"`\n\n### `shadcn:get_add_command_for_items`\n\nReturns the CLI install command.\n\n**Input:** `items` (string[]) — e.g. `[\"@shadcn/button\"]`\n\n### `shadcn:get_audit_checklist`\n\nReturns a checklist for verifying components (imports, deps, lint, TypeScript).\n\n**Input:** none\n\n---\n\n## Configuring Registries\n\nNamespaced and authenticated registries are set in `components.json`. The `@shadcn` registry is always built-in. Public GitHub registries can also be used directly as `owner/repo` registry sources when the repository has a root `registry.json`; they do not need `components.json` configuration.\n\n```json\n{\n  \"registries\": {\n    \"@acme\": \"https://acme.com/r/{name}.json\",\n    \"@private\": {\n      \"url\": \"https://private.com/r/{name}.json\",\n      \"headers\": { \"Authorization\": \"Bearer ${MY_TOKEN}\" }\n    }\n  }\n}\n```\n\n- Names must start with `@`.\n- URLs must contain `{name}`.\n- `${VAR}` references are resolved from environment variables.\n\nCommunity registry index: `https://ui.shadcn.com/r/registries.json`\n\n<!-- File: .agents/skills/shadcn/registry.md (Tokens: ~2079 | Category: anthropic-skill) -->\n# Registry Authoring and Addresses\n\nUse this reference when the user wants to create, fix, publish, or reason about a shadcn registry.\n\n## Mental Model\n\nA registry has two forms:\n\n- **Source registry**: an authored `registry.json` in a project or repository. It may use `include` and file paths that point at source files.\n- **Built registry**: generated JSON files served to CLI consumers, usually from `public/r`. Use `npx shadcn@latest build` to create this form.\n\nThe CLI installer consumes registry item payloads. A source registry is a way to author those payloads from real files.\n\nRegistry items are not limited to React components. They can distribute components, hooks, utilities, design tokens, pages, config files, docs, rules, workflows, templates, MCP files, and other project files.\n\n## Root `registry.json`\n\nThe root registry file should define registry metadata and either `items` or `include`.\n\n```json\n{\n  \"$schema\": \"https://ui.shadcn.com/schema/registry.json\",\n  \"name\": \"acme\",\n  \"homepage\": \"https://acme.com\",\n  \"items\": [\n    {\n      \"name\": \"absolute-url\",\n      \"type\": \"registry:lib\",\n      \"title\": \"Absolute URL\",\n      \"description\": \"A utility to turn any path into an absolute URL.\",\n      \"files\": [\n        {\n          \"path\": \"lib/absolute-url.ts\",\n          \"type\": \"registry:lib\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nRoot registry rules:\n\n- Root `registry.json` must include `name` and `homepage`.\n- `items` is an array of registry item definitions.\n- `include` may be used to split the source registry into multiple files.\n- Included registry files may omit `name` and `homepage`.\n\n## Include\n\nUse `include` to keep large registries modular.\n\n```json\n{\n  \"$schema\": \"https://ui.shadcn.com/schema/registry.json\",\n  \"name\": \"acme\",\n  \"homepage\": \"https://acme.com\",\n  \"include\": [\"registry/ui/registry.json\", \"registry/blocks/registry.json\"]\n}\n```\n\nInclude rules:\n\n- Include paths are relative to the `registry.json` that declares them.\n- Include paths must explicitly point to a `registry.json` file.\n- Do not use remote URLs, absolute paths, or parent traversal (`..`).\n- Item file paths are relative to the registry file that declares the item.\n- Duplicate item names fail across the resolved registry.\n\nExample included file:\n\n```json\n{\n  \"items\": [\n    {\n      \"name\": \"button\",\n      \"type\": \"registry:ui\",\n      \"files\": [\n        {\n          \"path\": \"button.tsx\",\n          \"type\": \"registry:ui\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nIf this file is at `registry/ui/registry.json`, then `button.tsx` is read from `registry/ui/button.tsx`, and the built item path is emitted relative to the root registry.\n\n## Item Definitions\n\nCommon item fields:\n\n```json\n{\n  \"name\": \"login-form\",\n  \"type\": \"registry:block\",\n  \"title\": \"Login Form\",\n  \"description\": \"A login form with email and password fields.\",\n  \"dependencies\": [\"zod\"],\n  \"registryDependencies\": [\"button\", \"input\", \"label\"],\n  \"files\": [\n    {\n      \"path\": \"blocks/login-form.tsx\",\n      \"type\": \"registry:block\"\n    }\n  ],\n  \"cssVars\": {\n    \"light\": {\n      \"brand\": \"oklch(0.62 0.18 250)\"\n    },\n    \"dark\": {\n      \"brand\": \"oklch(0.72 0.16 250)\"\n    }\n  }\n}\n```\n\nImportant fields:\n\n- `name`: the installable item name. It is not necessarily a file path.\n- `type`: one of the registry item types, such as `registry:ui`, `registry:block`, `registry:lib`, `registry:hook`, `registry:file`, `registry:page`, `registry:theme`, `registry:style`, `registry:font`, or `registry:item`.\n- `files`: source files copied or generated by the item.\n- `dependencies`: npm runtime dependencies.\n- `devDependencies`: npm development dependencies.\n- `registryDependencies`: other registry items required by this item.\n- `cssVars`, `css`, `tailwind`, `envVars`, and `docs`: optional install-time additions.\n\nFile rules:\n\n- File paths are relative to the declaring `registry.json`.\n- `registry:file` and `registry:page` files require a `target`.\n- Do not use remote file URLs in source registry file paths.\n- Keep source files copy-pasteable: no hidden app-only imports.\n\n## Registry Dependencies\n\n`registryDependencies` entries are item addresses, not file paths.\n\n```json\n{\n  \"name\": \"login-form\",\n  \"type\": \"registry:block\",\n  \"registryDependencies\": [\"button\", \"@acme/input\", \"acme/ui/card#v1.2.0\"],\n  \"files\": [\n    {\n      \"path\": \"blocks/login-form.tsx\",\n      \"type\": \"registry:block\"\n    }\n  ]\n}\n```\n\nDependency rules:\n\n- Bare names such as `\"button\"` mean official shadcn items.\n- Bare names never mean same-registry or same-repository items.\n- Namespaced dependencies use `@namespace/item-name`.\n- GitHub dependencies use `owner/repo/item-name`.\n- Pin GitHub dependencies with `owner/repo/item-name#ref` when needed.\n- Refs are not inherited. If `owner/repo/foo#v2` depends on `bar` from the same repo at `v2`, write `owner/repo/bar#v2`.\n- Do not use relative dependencies such as `\"./bar\"`.\n\n## Address Schemes\n\nWhen reasoning about a registry item string, classify it first.\n\n| Address | Scheme | Meaning |\n| --- | --- | --- |\n| `button` | shadcn | Official shadcn item named `button`. |\n| `@acme/button` | namespace | Item `button` from configured registry `@acme`. |\n| `@acme/ui/button` | namespace | Item `ui/button` from configured registry `@acme`. |\n| `https://example.com/r/button.json` | url | Built registry item JSON at that URL. |\n| `./button.json` | file | Built registry item JSON on disk. |\n| `acme/ui/button` | github | Item `button` from GitHub repo `acme/ui`. |\n| `acme/ui/forms/login#main` | github | Item `forms/login` from GitHub repo `acme/ui` at ref `main`. |\n\nFor namespace and GitHub addresses, slashful item names are allowed and are item names, not file paths. Addresses ending in `.json` keep file-address precedence, so `acme/ui/data/schema.json` is treated as a file path, not a GitHub item address.\n\n## GitHub Registries\n\nA public GitHub repository can act as a source registry when it has a root `registry.json`.\n\n```txt\nowner/repo/item-name[#ref]\n```\n\nRules:\n\n- The first two path segments are GitHub owner and repo.\n- All remaining path segments are the registry item name.\n- The source entrypoint is always root `registry.json`.\n- GitHub registries are source registries consumed directly by the CLI. They do not require `shadcn build` or generated item JSON files.\n- `include` follows the same source-registry rules as local registries.\n- Currently, GitHub addresses support public `github.com` repositories only.\n- Private repos and GitHub Enterprise require explicit product decisions.\n\nWhen implementing GitHub registry fetching, resolve refs to a commit SHA before reading source files. Do not read moving refs directly from `raw.githubusercontent.com`, because branch-like refs can be cached for several minutes.\n\nPreferred flow:\n\n```txt\nowner/repo[#ref]\n  -> resolve ref with git ls-remote\n  -> commit SHA\n  -> read https://raw.githubusercontent.com/{owner}/{repo}/{sha}/registry.json\n  -> read includes and item files from the same SHA\n```\n\nThis keeps a command on one consistent repository snapshot.\n\nFull 40-character commit SHAs are already stable and can be used directly. Branches, tags, and short refs require Git so the CLI can resolve them to a commit SHA first.\n\n## Build and Verify\n\nUse the CLI to build source registries:\n\n```bash\nnpx shadcn@latest build\nnpx shadcn@latest build registry.json --output public/r\n```\n\nUse CLI commands to inspect the result:\n\n```bash\nnpx shadcn@latest list @acme\nnpx shadcn@latest search @acme -q \"login\"\nnpx shadcn@latest view @acme/login-form\nnpx shadcn@latest add @acme/login-form --dry-run\nnpx shadcn@latest registry validate ./registry.json\n```\n\nUse GitHub addresses directly for public GitHub registries:\n\n```bash\nnpx shadcn@latest list owner/repo\nnpx shadcn@latest search owner/repo -q \"login\"\nnpx shadcn@latest view owner/repo/item\nnpx shadcn@latest add owner/repo/item --dry-run\nnpx shadcn@latest registry validate owner/repo\n```\n\nWhen working on registry implementation in the shadcn/ui codebase:\n\n- Keep address parsing pure and testable.\n- Do not add side effects to validators.\n- Preserve existing behavior for official shadcn, namespace, URL, and file schemes.\n- Add tests for address parsing, source loading, dependency resolution, list, search, view, and add paths.\n- Prefer small source-reader abstractions over a plugin system until there are multiple real providers.\n\n<!-- File: .agents/skills/shadcn/rules/base-vs-radix.md (Tokens: ~1831 | Category: anthropic-skill) -->\n# Base vs Radix\n\nAPI differences between `base` and `radix`. Check the `base` field from `npx shadcn@latest info`.\n\n## Contents\n\n- Composition: asChild vs render\n- Button / trigger as non-button element\n- Select (items prop, placeholder, positioning, multiple, object values)\n- ToggleGroup (type vs multiple)\n- Slider (scalar vs array)\n- Accordion (type and defaultValue)\n\n---\n\n## Composition: asChild (radix) vs render (base)\n\nRadix uses `asChild` to replace the default element. Base uses `render`. Don't wrap triggers in extra elements.\n\n**Incorrect:**\n\n```tsx\n<DialogTrigger>\n  <div>\n    <Button>Open</Button>\n  </div>\n</DialogTrigger>\n```\n\n**Correct (radix):**\n\n```tsx\n<DialogTrigger asChild>\n  <Button>Open</Button>\n</DialogTrigger>\n```\n\n**Correct (base):**\n\n```tsx\n<DialogTrigger render={<Button />}>Open</DialogTrigger>\n```\n\nThis applies to all trigger and close components: `DialogTrigger`, `SheetTrigger`, `AlertDialogTrigger`, `DropdownMenuTrigger`, `PopoverTrigger`, `TooltipTrigger`, `CollapsibleTrigger`, `DialogClose`, `SheetClose`, `NavigationMenuLink`, `BreadcrumbLink`, `SidebarMenuButton`, `Badge`, `Item`.\n\n---\n\n## Button / trigger as non-button element (base only)\n\nWhen `render` changes an element to a non-button (`<a>`, `<span>`), add `nativeButton={false}`.\n\n**Incorrect (base):** missing `nativeButton={false}`.\n\n```tsx\n<Button render={<a href=\"/docs\" />}>Read the docs</Button>\n```\n\n**Correct (base):**\n\n```tsx\n<Button render={<a href=\"/docs\" />} nativeButton={false}>\n  Read the docs\n</Button>\n```\n\n**Correct (radix):**\n\n```tsx\n<Button asChild>\n  <a href=\"/docs\">Read the docs</a>\n</Button>\n```\n\nSame for triggers whose `render` is not a `Button`:\n\n```tsx\n// base.\n<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>\n  Pick date\n</PopoverTrigger>\n```\n\n---\n\n## Select\n\n**items prop (base only).** Base requires an `items` prop on the root. Radix uses inline JSX only.\n\n**Incorrect (base):**\n\n```tsx\n<Select>\n  <SelectTrigger>\n    <SelectValue placeholder=\"Select a fruit\" />\n  </SelectTrigger>\n</Select>\n```\n\n**Correct (base):**\n\n```tsx\nconst items = [\n  { label: \"Select a fruit\", value: null },\n  { label: \"Apple\", value: \"apple\" },\n  { label: \"Banana\", value: \"banana\" },\n]\n\n<Select items={items}>\n  <SelectTrigger>\n    <SelectValue />\n  </SelectTrigger>\n  <SelectContent>\n    <SelectGroup>\n      {items.map((item) => (\n        <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>\n      ))}\n    </SelectGroup>\n  </SelectContent>\n</Select>\n```\n\n**Correct (radix):**\n\n```tsx\n<Select>\n  <SelectTrigger>\n    <SelectValue placeholder=\"Select a fruit\" />\n  </SelectTrigger>\n  <SelectContent>\n    <SelectGroup>\n      <SelectItem value=\"apple\">Apple</SelectItem>\n      <SelectItem value=\"banana\">Banana</SelectItem>\n    </SelectGroup>\n  </SelectContent>\n</Select>\n```\n\n**Placeholder.** Base uses a `{ value: null }` item in the items array. Radix uses `<SelectValue placeholder=\"...\">`.\n\n**Content positioning.** Base uses `alignItemWithTrigger`. Radix uses `position`.\n\n```tsx\n// base.\n<SelectContent alignItemWithTrigger={false} side=\"bottom\">\n\n// radix.\n<SelectContent position=\"popper\">\n```\n\n---\n\n## Select — multiple selection and object values (base only)\n\nBase supports `multiple`, render-function children on `SelectValue`, and object values with `itemToStringValue`. Radix is single-select with string values only.\n\n**Correct (base — multiple selection):**\n\n```tsx\n<Select items={items} multiple defaultValue={[]}>\n  <SelectTrigger>\n    <SelectValue>\n      {(value: string[]) =>\n        value.length === 0 ? \"Select fruits\" : `${value.length} selected`\n      }\n    </SelectValue>\n  </SelectTrigger>\n  ...\n</Select>\n```\n\n**Correct (base — object values):**\n\n```tsx\n<Select defaultValue={plans[0]} itemToStringValue={(plan) => plan.name}>\n  <SelectTrigger>\n    <SelectValue>{(value) => value.name}</SelectValue>\n  </SelectTrigger>\n  ...\n</Select>\n```\n\n---\n\n## ToggleGroup\n\nBase uses a `multiple` boolean prop. Radix uses `type=\"single\"` or `type=\"multiple\"`.\n\n**Incorrect (base):**\n\n```tsx\n<ToggleGroup type=\"single\" defaultValue=\"daily\">\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n</ToggleGroup>\n```\n\n**Correct (base):**\n\n```tsx\n// Single (no prop needed), defaultValue is always an array.\n<ToggleGroup defaultValue={[\"daily\"]} spacing={2}>\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n  <ToggleGroupItem value=\"weekly\">Weekly</ToggleGroupItem>\n</ToggleGroup>\n\n// Multi-selection.\n<ToggleGroup multiple>\n  <ToggleGroupItem value=\"bold\">Bold</ToggleGroupItem>\n  <ToggleGroupItem value=\"italic\">Italic</ToggleGroupItem>\n</ToggleGroup>\n```\n\n**Correct (radix):**\n\n```tsx\n// Single, defaultValue is a string.\n<ToggleGroup type=\"single\" defaultValue=\"daily\" spacing={2}>\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n  <ToggleGroupItem value=\"weekly\">Weekly</ToggleGroupItem>\n</ToggleGroup>\n\n// Multi-selection.\n<ToggleGroup type=\"multiple\">\n  <ToggleGroupItem value=\"bold\">Bold</ToggleGroupItem>\n  <ToggleGroupItem value=\"italic\">Italic</ToggleGroupItem>\n</ToggleGroup>\n```\n\n**Controlled single value:**\n\n```tsx\n// base — wrap/unwrap arrays.\nconst [value, setValue] = React.useState(\"normal\")\n<ToggleGroup value={[value]} onValueChange={(v) => setValue(v[0])}>\n\n// radix — plain string.\nconst [value, setValue] = React.useState(\"normal\")\n<ToggleGroup type=\"single\" value={value} onValueChange={setValue}>\n```\n\n---\n\n## Slider\n\nBase accepts a plain number for a single thumb. Radix always requires an array.\n\n**Incorrect (base):**\n\n```tsx\n<Slider defaultValue={[50]} max={100} step={1} />\n```\n\n**Correct (base):**\n\n```tsx\n<Slider defaultValue={50} max={100} step={1} />\n```\n\n**Correct (radix):**\n\n```tsx\n<Slider defaultValue={[50]} max={100} step={1} />\n```\n\nBoth use arrays for range sliders. Controlled `onValueChange` in base may need a cast:\n\n```tsx\n// base.\nconst [value, setValue] = React.useState([0.3, 0.7])\n<Slider value={value} onValueChange={(v) => setValue(v as number[])} />\n\n// radix.\nconst [value, setValue] = React.useState([0.3, 0.7])\n<Slider value={value} onValueChange={setValue} />\n```\n\n---\n\n## Accordion\n\nRadix requires `type=\"single\"` or `type=\"multiple\"` and supports `collapsible`. `defaultValue` is a string. Base uses no `type` prop, uses `multiple` boolean, and `defaultValue` is always an array.\n\n**Incorrect (base):**\n\n```tsx\n<Accordion type=\"single\" collapsible defaultValue=\"item-1\">\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n</Accordion>\n```\n\n**Correct (base):**\n\n```tsx\n<Accordion defaultValue={[\"item-1\"]}>\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n</Accordion>\n\n// Multi-select.\n<Accordion multiple defaultValue={[\"item-1\", \"item-2\"]}>\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n  <AccordionItem value=\"item-2\">...</AccordionItem>\n</Accordion>\n```\n\n**Correct (radix):**\n\n```tsx\n<Accordion type=\"single\" collapsible defaultValue=\"item-1\">\n  <AccordionItem value=\"item-1\">...</AccordionItem>\n</Accordion>\n```\n\n<!-- File: .agents/skills/shadcn/rules/chat.md (Tokens: ~1683 | Category: anthropic-skill) -->\n# Chat & Messaging\n\nComponents for conversation and chat UI. Compose these instead of hand-rolling bubbles, scroll containers, dividers, or attachment cards.\n\nInstall: `npx shadcn@latest add message-scroller message bubble attachment marker`\n\nThe same component names and props ship for both `base` and `radix`; only composition differs (`render` vs `asChild`). See [base-vs-radix.md](./base-vs-radix.md).\n\n## Contents\n\n- Scrollable threads use MessageScroller\n- Message rows use Message\n- Message surfaces use Bubble\n- Attachments use Attachment\n- System notes and dividers use Marker\n- Streaming, anchoring, and jump-to-latest are built in\n- Escape hatch: the scroller hooks\n\n---\n\n## Scrollable threads use MessageScroller\n\nA conversation that scrolls, follows new messages, restores position, or jumps to a message uses `MessageScroller`. Don't build a raw overflow container with manual scroll wiring, and don't reach for `ScrollArea`.\n\nThe parts nest in a fixed order. Every direct child of the content is wrapped in a `MessageScrollerItem` so the scroller can measure, anchor, preserve position, track visibility, and jump to it. `MessageScrollerButton` sits inside `MessageScroller`, after the viewport.\n\n**Incorrect:**\n\n```tsx\n// Hand-rolled scroll container with manual stick-to-bottom logic.\n<div ref={scrollRef} onScroll={handleScroll} className=\"flex-1 overflow-y-auto\">\n  <div className=\"flex flex-col gap-6 p-4\">\n    {messages.map((m) => (\n      <ChatMessage key={m.id} message={m} />\n    ))}\n  </div>\n</div>\n```\n\n**Correct:**\n\n```tsx\n<MessageScrollerProvider autoScroll>\n  <MessageScroller>\n    <MessageScrollerViewport>\n      <MessageScrollerContent>\n        {messages.map((message) => (\n          <MessageScrollerItem\n            key={message.id}\n            messageId={message.id}\n            scrollAnchor={message.role === \"user\"}\n          >\n            <Message align={message.role === \"user\" ? \"end\" : \"start\"}>\n              {/* ...message content... */}\n            </Message>\n          </MessageScrollerItem>\n        ))}\n      </MessageScrollerContent>\n    </MessageScrollerViewport>\n    <MessageScrollerButton />\n  </MessageScroller>\n</MessageScrollerProvider>\n```\n\n---\n\n## Message rows use Message\n\n`Message` lays out a single row: avatar, header, content, footer, with alignment. Group consecutive rows from one sender with `MessageGroup`. Don't rebuild the row from flex divs.\n\n`align=\"end\"` is the current user's side; `align=\"start\"` is everyone else.\n\n```tsx\n<Message align=\"start\">\n  <MessageAvatar>\n    <Avatar>\n      <AvatarImage src={sender.avatar} alt={sender.name} />\n      <AvatarFallback>{initials}</AvatarFallback>\n    </Avatar>\n  </MessageAvatar>\n  <MessageContent>\n    <MessageHeader>{sender.name}</MessageHeader>\n    <Bubble>\n      <BubbleContent>{text}</BubbleContent>\n    </Bubble>\n    <MessageFooter>{time}</MessageFooter>\n  </MessageContent>\n</Message>\n```\n\n---\n\n## Message surfaces use Bubble\n\nThe colored message surface is `Bubble` + `BubbleContent`, never a styled `div` with `bg-muted` / `bg-primary` and hand-managed corners.\n\n- `variant`: `default`, `secondary`, `muted`, `tinted`, `outline`, `ghost`, `destructive`.\n- `align`: `start` or `end` (matches the `Message` side).\n\n`BubbleReactions` renders the reaction cluster. `side` (`top` | `bottom`) and `align` (`start` | `end`) position it against the bubble. Don't lay reactions out with absolutely-positioned `Badge`s.\n\n**Incorrect:**\n\n```tsx\n<div className=\"w-fit rounded-2xl bg-primary px-3 py-2 text-primary-foreground\">\n  {text}\n</div>\n```\n\n**Correct:**\n\n```tsx\n<Bubble variant=\"default\" align=\"end\">\n  <BubbleContent>{text}</BubbleContent>\n  <BubbleReactions side=\"bottom\" align=\"end\">\n    <Badge variant=\"secondary\">👍 2</Badge>\n  </BubbleReactions>\n</Bubble>\n```\n\n---\n\n## Attachments use Attachment\n\nFile and image attachments use `Attachment`, not `Item` or a custom card. It carries upload state, so wire `state` to the real status rather than rendering a separate spinner.\n\n- `state`: `idle`, `uploading`, `processing`, `error`, `done`. `uploading` and `processing` apply the `shimmer` animation to the title automatically.\n- `size`: `default`, `sm`, `xs`. `orientation`: `horizontal`, `vertical`.\n- Use `AttachmentGroup` to lay out several attachments in a scrolling row.\n\n```tsx\n<Attachment state=\"done\">\n  <AttachmentMedia variant=\"icon\">\n    <FileTextIcon />\n  </AttachmentMedia>\n  <AttachmentContent>\n    <AttachmentTitle>homepage-feedback.pdf</AttachmentTitle>\n    <AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>\n  </AttachmentContent>\n  <AttachmentActions>\n    <AttachmentAction>\n      <DownloadIcon />\n    </AttachmentAction>\n  </AttachmentActions>\n</Attachment>\n```\n\nFor an image, use `<AttachmentMedia variant=\"image\">` with an `img` child.\n\n---\n\n## System notes and dividers use Marker\n\nStatus lines (\"Sarah joined the conversation\"), date dividers (\"Today\"), and labeled separators are `Marker`, not a `Separator` plus a centered span.\n\n- `variant`: `default` (plain row), `separator` (centered label with rules on each side), `border` (bottom-bordered row).\n- `MarkerIcon` holds a leading icon; `MarkerContent` holds the label.\n\n**Incorrect:**\n\n```tsx\n<div className=\"flex items-center gap-3 py-2\">\n  <Separator className=\"flex-1\" />\n  <span className=\"text-xs text-muted-foreground\">Today</span>\n  <Separator className=\"flex-1\" />\n</div>\n```\n\n**Correct:**\n\n```tsx\n<Marker variant=\"separator\">\n  <MarkerContent>Today</MarkerContent>\n</Marker>\n```\n\n---\n\n## Streaming, anchoring, and jump-to-latest are built in\n\n`MessageScroller` handles the behavior that chat UIs usually reinvent. Don't write a `useStickToBottom` hook, a `ResizeObserver`, or manual `scrollTop` math.\n\n- **Follow the live edge while streaming.** `MessageScrollerProvider` with `autoScroll` keeps the view pinned to new content and yields the moment the user scrolls up. Streaming token updates that grow the last message are followed automatically.\n- **Anchor a turn.** `scrollAnchor` on a `MessageScrollerItem` marks the row to hold in view (typically the user's message that started the turn).\n- **Jump to latest.** `MessageScrollerButton` appears when the user scrolls away and scrolls back on click. `direction=\"end\"` (default) or `direction=\"start\"`. It is a self-managing control, so don't gate it behind your own scroll-position state.\n\nFor a \"thinking…\" indicator while the model generates, apply the `shimmer` utility to text. Don't author a custom keyframe animation. See [styling.md](./styling.md).\n\n---\n\n## Escape hatch: the scroller hooks\n\nFor behavior the parts don't expose, read state from the hooks rather than re-implementing the scroller: `useMessageScroller`, `useMessageScrollerVisibility`, and `useMessageScrollerScrollable`. They come from the auto-installed `@shadcn/react` dependency, so there's nothing extra to install. Reach for them only when composition can't express what you need.\n\n<!-- File: .agents/skills/shadcn/rules/composition.md (Tokens: ~1230 | Category: anthropic-skill) -->\n# Component Composition\n\n## Contents\n\n- Items always inside their Group component\n- Callouts use Alert\n- Empty states use Empty component\n- Toast notifications follow the project base\n- Choosing between overlay components\n- Dialog, Sheet, and Drawer always need a Title\n- Card structure\n- Button has no isPending or isLoading prop\n- TabsTrigger must be inside TabsList\n- Avatar always needs AvatarFallback\n- Use Separator instead of raw hr or border divs\n- Use Skeleton for loading placeholders\n- Use Badge instead of custom styled spans\n\n---\n\n## Items always inside their Group component\n\nNever render items directly inside the content container.\n\n**Incorrect:**\n\n```tsx\n<SelectContent>\n  <SelectItem value=\"apple\">Apple</SelectItem>\n  <SelectItem value=\"banana\">Banana</SelectItem>\n</SelectContent>\n```\n\n**Correct:**\n\n```tsx\n<SelectContent>\n  <SelectGroup>\n    <SelectItem value=\"apple\">Apple</SelectItem>\n    <SelectItem value=\"banana\">Banana</SelectItem>\n  </SelectGroup>\n</SelectContent>\n```\n\nThis applies to all group-based components:\n\n| Item | Group |\n| --- | --- |\n| `SelectItem`, `SelectLabel` | `SelectGroup` |\n| `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` |\n| `MenubarItem` | `MenubarGroup` |\n| `ContextMenuItem` | `ContextMenuGroup` |\n| `CommandItem` | `CommandGroup` |\n| `MessageScrollerItem` | `MessageScrollerContent` |\n| `Message` (consecutive, same sender) | `MessageGroup` |\n| `Bubble` (stacked) | `BubbleGroup` |\n| `Attachment` (in a row) | `AttachmentGroup` |\n\nChat components nest in a fixed order (`MessageScrollerProvider` → `MessageScroller` → `MessageScrollerViewport` → `MessageScrollerContent` → `MessageScrollerItem`). See [chat.md](./chat.md).\n\n---\n\n## Callouts use Alert\n\n```tsx\n<Alert>\n  <AlertTitle>Warning</AlertTitle>\n  <AlertDescription>Something needs attention.</AlertDescription>\n</Alert>\n```\n\n---\n\n## Empty states use Empty component\n\n```tsx\n<Empty>\n  <EmptyHeader>\n    <EmptyMedia variant=\"icon\">\n      <FolderIcon />\n    </EmptyMedia>\n    <EmptyTitle>No projects yet</EmptyTitle>\n    <EmptyDescription>Get started by creating a new project.</EmptyDescription>\n  </EmptyHeader>\n  <EmptyContent>\n    <Button>Create Project</Button>\n  </EmptyContent>\n</Empty>\n```\n\n---\n\n## Toast notifications follow the project base\n\nFor Base UI projects, use the `toast` component:\n\n```tsx\nimport { toast } from \"@/components/ui/toast\";\n\ntoast.add({\n  title: \"Changes saved.\",\n});\n```\n\nFor Radix and React Aria projects, use Sonner:\n\n```tsx\nimport { toast } from \"sonner\";\n\ntoast.success(\"Changes saved.\");\ntoast.error(\"Something went wrong.\");\ntoast(\"File deleted.\", {\n  action: { label: \"Undo\", onClick: () => undoDelete() },\n});\n```\n\n---\n\n## Choosing between overlay components\n\n| Use case                           | Component     |\n| ---------------------------------- | ------------- |\n| Focused task that requires input   | `Dialog`      |\n| Destructive action confirmation    | `AlertDialog` |\n| Side panel with details or filters | `Sheet`       |\n| Mobile-first bottom panel          | `Drawer`      |\n| Quick info on hover                | `HoverCard`   |\n| Small contextual content on click  | `Popover`     |\n\n---\n\n## Dialog, Sheet, and Drawer always need a Title\n\n`DialogTitle`, `SheetTitle`, `DrawerTitle` are required for accessibility. Use `className=\"sr-only\"` if visually hidden.\n\n```tsx\n<DialogContent>\n  <DialogHeader>\n    <DialogTitle>Edit Profile</DialogTitle>\n    <DialogDescription>Update your profile.</DialogDescription>\n  </DialogHeader>\n  ...\n</DialogContent>\n```\n\n---\n\n## Card structure\n\nUse full composition — don't dump everything into `CardContent`:\n\n```tsx\n<Card>\n  <CardHeader>\n    <CardTitle>Team Members</CardTitle>\n    <CardDescription>Manage your team.</CardDescription>\n  </CardHeader>\n  <CardContent>...</CardContent>\n  <CardFooter>\n    <Button>Invite</Button>\n  </CardFooter>\n</Card>\n```\n\n---\n\n## Button has no isPending or isLoading prop\n\nCompose with `Spinner` + `data-icon` + `disabled`:\n\n```tsx\n<Button disabled>\n  <Spinner data-icon=\"inline-start\" />\n  Saving...\n</Button>\n```\n\n---\n\n## TabsTrigger must be inside TabsList\n\nNever render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`:\n\n```tsx\n<Tabs defaultValue=\"account\">\n  <TabsList>\n    <TabsTrigger value=\"account\">Account</TabsTrigger>\n    <TabsTrigger value=\"password\">Password</TabsTrigger>\n  </TabsList>\n  <TabsContent value=\"account\">...</TabsContent>\n</Tabs>\n```\n\n---\n\n## Avatar always needs AvatarFallback\n\nAlways include `AvatarFallback` for when the image fails to load:\n\n```tsx\n<Avatar>\n  <AvatarImage src=\"/avatar.png\" alt=\"User\" />\n  <AvatarFallback>JD</AvatarFallback>\n</Avatar>\n```\n\n---\n\n## Use existing components instead of custom markup\n\n| Instead of | Use |\n| --- | --- |\n| `<hr>` or `<div className=\"border-t\">` | `<Separator />` |\n| `<div className=\"animate-pulse\">` with styled divs | `<Skeleton className=\"h-4 w-3/4\" />` |\n| `<span className=\"rounded-full bg-green-100 ...\">` | `<Badge variant=\"secondary\">` |\n\n<!-- File: .agents/skills/shadcn/rules/forms.md (Tokens: ~1210 | Category: anthropic-skill) -->\n# Forms & Inputs\n\n## Contents\n\n- Forms use FieldGroup + Field\n- InputGroup requires InputGroupInput/InputGroupTextarea\n- Buttons inside inputs use InputGroup + InputGroupAddon\n- Option sets (2–7 choices) use ToggleGroup\n- FieldSet + FieldLegend for grouping related fields\n- Field validation and disabled states\n\n---\n\n## Forms use FieldGroup + Field\n\nAlways use `FieldGroup` + `Field` — never raw `div` with `space-y-*`:\n\n```tsx\n<FieldGroup>\n  <Field>\n    <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n    <Input id=\"email\" type=\"email\" />\n  </Field>\n  <Field>\n    <FieldLabel htmlFor=\"password\">Password</FieldLabel>\n    <Input id=\"password\" type=\"password\" />\n  </Field>\n</FieldGroup>\n```\n\nUse `Field orientation=\"horizontal\"` for settings pages. Use `FieldLabel className=\"sr-only\"` for visually hidden labels.\n\n**Choosing form controls:**\n\n- Simple text input → `Input`\n- Dropdown with predefined options → `Select`\n- Searchable dropdown → `Combobox`\n- Native HTML select (no JS) → `native-select`\n- Boolean toggle → `Switch` (for settings) or `Checkbox` (for forms)\n- Single choice from few options → `RadioGroup`\n- Toggle between 2–5 options → `ToggleGroup` + `ToggleGroupItem`\n- OTP/verification code → `InputOTP`\n- Multi-line text → `Textarea`\n\n---\n\n## InputGroup requires InputGroupInput/InputGroupTextarea\n\nNever use raw `Input` or `Textarea` inside an `InputGroup`.\n\n**Incorrect:**\n\n```tsx\n<InputGroup>\n  <Input placeholder=\"Search...\" />\n</InputGroup>\n```\n\n**Correct:**\n\n```tsx\nimport { InputGroup, InputGroupInput } from \"@/components/ui/input-group\";\n\n<InputGroup>\n  <InputGroupInput placeholder=\"Search...\" />\n</InputGroup>;\n```\n\n---\n\n## Buttons inside inputs use InputGroup + InputGroupAddon\n\nNever place a `Button` directly inside or adjacent to an `Input` with custom positioning.\n\n**Incorrect:**\n\n```tsx\n<div className=\"relative\">\n  <Input placeholder=\"Search...\" className=\"pr-10\" />\n  <Button className=\"absolute right-0 top-0\" size=\"icon\">\n    <SearchIcon />\n  </Button>\n</div>\n```\n\n**Correct:**\n\n```tsx\nimport {\n  InputGroup,\n  InputGroupInput,\n  InputGroupAddon,\n} from \"@/components/ui/input-group\";\n\n<InputGroup>\n  <InputGroupInput placeholder=\"Search...\" />\n  <InputGroupAddon>\n    <Button size=\"icon\">\n      <SearchIcon data-icon=\"inline-start\" />\n    </Button>\n  </InputGroupAddon>\n</InputGroup>;\n```\n\n---\n\n## Option sets (2–7 choices) use ToggleGroup\n\nDon't manually loop `Button` components with active state.\n\n**Incorrect:**\n\n```tsx\nconst [selected, setSelected] = useState(\"daily\")\n\n<div className=\"flex gap-2\">\n  {[\"daily\", \"weekly\", \"monthly\"].map((option) => (\n    <Button\n      key={option}\n      variant={selected === option ? \"default\" : \"outline\"}\n      onClick={() => setSelected(option)}\n    >\n      {option}\n    </Button>\n  ))}\n</div>\n```\n\n**Correct:**\n\n```tsx\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\n\n<ToggleGroup spacing={2}>\n  <ToggleGroupItem value=\"daily\">Daily</ToggleGroupItem>\n  <ToggleGroupItem value=\"weekly\">Weekly</ToggleGroupItem>\n  <ToggleGroupItem value=\"monthly\">Monthly</ToggleGroupItem>\n</ToggleGroup>;\n```\n\nCombine with `Field` for labelled toggle groups:\n\n```tsx\n<Field orientation=\"horizontal\">\n  <FieldTitle id=\"theme-label\">Theme</FieldTitle>\n  <ToggleGroup aria-labelledby=\"theme-label\" spacing={2}>\n    <ToggleGroupItem value=\"light\">Light</ToggleGroupItem>\n    <ToggleGroupItem value=\"dark\">Dark</ToggleGroupItem>\n    <ToggleGroupItem value=\"system\">System</ToggleGroupItem>\n  </ToggleGroup>\n</Field>\n```\n\n> **Note:** `defaultValue` and `type`/`multiple` props differ between base and radix. See [base-vs-radix.md](./base-vs-radix.md#togglegroup).\n\n---\n\n## FieldSet + FieldLegend for grouping related fields\n\nUse `FieldSet` + `FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading:\n\n```tsx\n<FieldSet>\n  <FieldLegend variant=\"label\">Preferences</FieldLegend>\n  <FieldDescription>Select all that apply.</FieldDescription>\n  <FieldGroup className=\"gap-3\">\n    <Field orientation=\"horizontal\">\n      <Checkbox id=\"dark\" />\n      <FieldLabel htmlFor=\"dark\" className=\"font-normal\">\n        Dark mode\n      </FieldLabel>\n    </Field>\n  </FieldGroup>\n</FieldSet>\n```\n\n---\n\n## Field validation and disabled states\n\nBoth attributes are needed — `data-invalid`/`data-disabled` styles the field (label, description), while `aria-invalid`/`disabled` styles the control.\n\n```tsx\n// Invalid.\n<Field data-invalid>\n  <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n  <Input id=\"email\" aria-invalid />\n  <FieldDescription>Invalid email address.</FieldDescription>\n</Field>\n\n// Disabled.\n<Field data-disabled>\n  <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n  <Input id=\"email\" disabled />\n</Field>\n```\n\nWorks for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`, `NativeSelect`, `InputOTP`.\n\n<!-- File: .agents/skills/shadcn/rules/icons.md (Tokens: ~522 | Category: anthropic-skill) -->\n# Icons\n\n**Always use the project's configured `iconLibrary` for imports.** Check the `iconLibrary` field from project context: `lucide` → `lucide-react`, `tabler` → `@tabler/icons-react`, etc. Never assume `lucide-react`.\n\n---\n\n## Icons in Button use data-icon attribute\n\nAdd `data-icon=\"inline-start\"` (prefix) or `data-icon=\"inline-end\"` (suffix) to the icon. No sizing classes on the icon.\n\n**Incorrect:**\n\n```tsx\n<Button>\n  <SearchIcon className=\"mr-2 size-4\" />\n  Search\n</Button>\n```\n\n**Correct:**\n\n```tsx\n<Button>\n  <SearchIcon data-icon=\"inline-start\"/>\n  Search\n</Button>\n\n<Button>\n  Next\n  <ArrowRightIcon data-icon=\"inline-end\"/>\n</Button>\n```\n\n---\n\n## No sizing classes on icons inside components\n\nComponents handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons inside `Button`, `DropdownMenuItem`, `Alert`, `Sidebar*`, or other shadcn components. Unless the user explicitly asks for custom icon sizes.\n\n**Incorrect:**\n\n```tsx\n<Button>\n  <SearchIcon className=\"size-4\" data-icon=\"inline-start\" />\n  Search\n</Button>\n\n<DropdownMenuItem>\n  <SettingsIcon className=\"mr-2 size-4\" />\n  Settings\n</DropdownMenuItem>\n```\n\n**Correct:**\n\n```tsx\n<Button>\n  <SearchIcon data-icon=\"inline-start\" />\n  Search\n</Button>\n\n<DropdownMenuItem>\n  <SettingsIcon />\n  Settings\n</DropdownMenuItem>\n```\n\n---\n\n## Pass icons as component objects, not string keys\n\nUse `icon={CheckIcon}`, not a string key to a lookup map.\n\n**Incorrect:**\n\n```tsx\nconst iconMap = {\n  check: CheckIcon,\n  alert: AlertIcon,\n};\n\nfunction StatusBadge({ icon }: { icon: string }) {\n  const Icon = iconMap[icon];\n  return <Icon />;\n}\n\n<StatusBadge icon=\"check\" />;\n```\n\n**Correct:**\n\n```tsx\n// Import from the project's configured iconLibrary (e.g. lucide-react, @tabler/icons-react).\nimport { CheckIcon } from \"lucide-react\";\n\nfunction StatusBadge({ icon: Icon }: { icon: React.ComponentType }) {\n  return <Icon />;\n}\n\n<StatusBadge icon={CheckIcon} />;\n```\n\n<!-- File: .agents/skills/shadcn/rules/styling.md (Tokens: ~1188 | Category: anthropic-skill) -->\n# Styling & Customization\n\nSee [customization.md](../customization.md) for theming, CSS variables, and adding custom colors.\n\n## Contents\n\n- Semantic colors\n- Built-in variants first\n- className for layout only\n- No space-x-* / space-y-*\n- Prefer size-* over w-* h-* when equal\n- Prefer truncate shorthand\n- No manual dark: color overrides\n- Use cn() for conditional classes\n- No manual z-index on overlay components\n- Use shimmer / scroll-fade utilities, not custom animations\n\n---\n\n## Semantic colors\n\n**Incorrect:**\n\n```tsx\n<div className=\"bg-blue-500 text-white\">\n  <p className=\"text-gray-600\">Secondary text</p>\n</div>\n```\n\n**Correct:**\n\n```tsx\n<div className=\"bg-primary text-primary-foreground\">\n  <p className=\"text-muted-foreground\">Secondary text</p>\n</div>\n```\n\n---\n\n## No raw color values for status/state indicators\n\nFor positive, negative, or status indicators, use Badge variants, semantic tokens like `text-destructive`, or define custom CSS variables — don't reach for raw Tailwind colors.\n\n**Incorrect:**\n\n```tsx\n<span className=\"text-emerald-600\">+20.1%</span>\n<span className=\"text-green-500\">Active</span>\n<span className=\"text-red-600\">-3.2%</span>\n```\n\n**Correct:**\n\n```tsx\n<Badge variant=\"secondary\">+20.1%</Badge>\n<Badge>Active</Badge>\n<span className=\"text-destructive\">-3.2%</span>\n```\n\nIf you need a success/positive color that doesn't exist as a semantic token, use a Badge variant or ask the user about adding a custom CSS variable to the theme (see [customization.md](../customization.md)).\n\n---\n\n## Built-in variants first\n\n**Incorrect:**\n\n```tsx\n<Button className=\"border border-input bg-transparent hover:bg-accent\">\n  Click me\n</Button>\n```\n\n**Correct:**\n\n```tsx\n<Button variant=\"outline\">Click me</Button>\n```\n\n---\n\n## className for layout only\n\nUse `className` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables.\n\n**Incorrect:**\n\n```tsx\n<Card className=\"bg-blue-100 text-blue-900 font-bold\">\n  <CardContent>Dashboard</CardContent>\n</Card>\n```\n\n**Correct:**\n\n```tsx\n<Card className=\"max-w-md mx-auto\">\n  <CardContent>Dashboard</CardContent>\n</Card>\n```\n\nTo customize a component's appearance, prefer these approaches in order:\n\n1. **Built-in variants** — `variant=\"outline\"`, `variant=\"destructive\"`, etc.\n2. **Semantic color tokens** — `bg-primary`, `text-muted-foreground`.\n3. **CSS variables** — define custom colors in the global CSS file (see [customization.md](../customization.md)).\n\n---\n\n## No space-x-* / space-y-*\n\nUse `gap-*` instead. `space-y-4` → `flex flex-col gap-4`. `space-x-2` → `flex gap-2`.\n\n```tsx\n<div className=\"flex flex-col gap-4\">\n  <Input />\n  <Input />\n  <Button>Submit</Button>\n</div>\n```\n\n---\n\n## Prefer size-* over w-* h-* when equal\n\n`size-10` not `w-10 h-10`. Applies to icons, avatars, skeletons, etc.\n\n---\n\n## Prefer truncate shorthand\n\n`truncate` not `overflow-hidden text-ellipsis whitespace-nowrap`.\n\n---\n\n## No manual dark: color overrides\n\nUse semantic tokens — they handle light/dark via CSS variables. `bg-background text-foreground` not `bg-white dark:bg-gray-950`.\n\n---\n\n## Use cn() for conditional classes\n\nUse the `cn()` utility from the project for conditional or merged class names. Don't write manual ternaries in className strings.\n\n**Incorrect:**\n\n```tsx\n<div className={`flex items-center ${isActive ? \"bg-primary text-primary-foreground\" : \"bg-muted\"}`}>\n```\n\n**Correct:**\n\n```tsx\nimport { cn } from \"@/lib/utils\"\n\n<div className={cn(\"flex items-center\", isActive ? \"bg-primary text-primary-foreground\" : \"bg-muted\")}>\n```\n\n---\n\n## No manual z-index on overlay components\n\n`Dialog`, `Sheet`, `Drawer`, `AlertDialog`, `DropdownMenu`, `Popover`, `Tooltip`, `HoverCard` handle their own stacking. Never add `z-50` or `z-[999]`.\n\n---\n\n## Use shimmer / scroll-fade utilities, not custom animations\n\nFor a live \"thinking…\" or loading-text shimmer, apply the `shimmer` utility. Don't author a custom `@keyframes` or a `bg-clip-text` gradient sweep.\n\nFor scroll-aware edge fading on a scroll container, use `scroll-fade` (and the axis variants `scroll-fade-x` / `scroll-fade-b`). Don't hand-roll mask gradients. The chat components already apply these internally: `Attachment` shimmers its title during upload, and `MessageScrollerViewport` fades its edges.\n\n**Incorrect:**\n\n```tsx\n<span className=\"animate-pulse bg-gradient-to-r from-muted-foreground/40 via-foreground/70 to-muted-foreground/40 bg-clip-text text-transparent [animation:shimmer_1.6s_infinite]\">\n  Thinking…\n</span>\n```\n\n**Correct:**\n\n```tsx\n<span className=\"shimmer\">Thinking…</span>\n```\n\n<!-- File: .agents/skills/shadcn/SKILL.md (Tokens: ~4831 | Category: anthropic-skill) -->\n---\nname: shadcn\ndescription: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for \"shadcn init\", \"create an app with --preset\", or \"switch to --preset\".\nuser-invocable: false\nallowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)\n---\n\n# shadcn/ui\n\nA framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI.\n\n> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.\n\n## Current Project Context\n\n```json\n!`npx shadcn@latest info --json`\n```\n\nThe JSON above contains the project config and installed components. Use `npx shadcn@latest docs <component>` to get documentation and example URLs for any component.\n\n## Principles\n\n1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too.\n2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table.\n3. **Use built-in variants before custom styles.** `variant=\"outline\"`, `size=\"sm\"`, etc.\n4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`.\n\n## Critical Rules\n\nThese rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs.\n\n### Styling & Tailwind → [styling.md](./rules/styling.md)\n\n- **`className` for layout, not styling.** Never override component colors or typography.\n- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`.\n- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`.\n- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`.\n- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`).\n- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries.\n- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking.\n\n### Forms & Inputs → [forms.md](./rules/forms.md)\n\n- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.\n- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`.\n- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.**\n- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state.\n- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading.\n- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control.\n\n### Component Structure → [composition.md](./rules/composition.md)\n\n- **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`.\n- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md)\n- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className=\"sr-only\"` if visually hidden.\n- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`.\n- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`.\n- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`.\n- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load.\n\n### Use Components, Not Custom Markup → [composition.md](./rules/composition.md)\n\n- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.\n- **Callouts use `Alert`.** Don't build custom styled divs.\n- **Empty states use `Empty`.** Don't build custom empty state markup.\n- **Toast follows the project base.** Use `toast` from the `toast` component for Base UI projects. Use `toast()` from `sonner` for Radix and React Aria projects.\n- **Use `Separator`** instead of `<hr>` or `<div className=\"border-t\">`.\n- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.\n- **Use `Badge`** instead of custom styled spans.\n\n### Icons → [icons.md](./rules/icons.md)\n\n- **Icons in `Button` use `data-icon`.** `data-icon=\"inline-start\"` or `data-icon=\"inline-end\"` on the icon.\n- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`.\n- **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup.\n\n### Chat & Messaging → [chat.md](./rules/chat.md)\n\n- **Chat UI composes the chat primitives.** Conversations use `MessageScroller`, rows use `Message`, surfaces use `Bubble`. Never hand-rolled bubble `div`s or a raw scroll container.\n- **`MessageScroller` owns scroll behavior.** Streaming follow, anchoring, and jump-to-latest (`MessageScrollerButton`) are built in. Don't write a `useStickToBottom`/`ResizeObserver` hook.\n- **Attachments use `Attachment`; system notes and dividers use `Marker`.** Not `Item` cards or `Separator` + a label.\n\n### CLI\n\n- **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@latest preset decode <code>`, `preset url <code>`, or `preset open <code>`. For project-aware preset detection, use `npx shadcn@latest preset resolve`.\n- **Apply preset codes directly with the CLI.** Use `npx shadcn@latest apply <code>` for existing projects, or `npx shadcn@latest init --preset <code>` when initializing.\n\n## Key Patterns\n\nThese are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above.\n\n```tsx\n// Form layout: FieldGroup + Field, not div + Label.\n<FieldGroup>\n  <Field>\n    <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n    <Input id=\"email\" />\n  </Field>\n</FieldGroup>\n\n// Validation: data-invalid on Field, aria-invalid on the control.\n<Field data-invalid>\n  <FieldLabel>Email</FieldLabel>\n  <Input aria-invalid />\n  <FieldDescription>Invalid email.</FieldDescription>\n</Field>\n\n// Icons in buttons: data-icon, no sizing classes.\n<Button>\n  <SearchIcon data-icon=\"inline-start\" />\n  Search\n</Button>\n\n// Spacing: gap-*, not space-y-*.\n<div className=\"flex flex-col gap-4\">  // correct\n<div className=\"space-y-4\">           // wrong\n\n// Equal dimensions: size-*, not w-* h-*.\n<Avatar className=\"size-10\">   // correct\n<Avatar className=\"w-10 h-10\"> // wrong\n\n// Status colors: Badge variants or semantic tokens, not raw colors.\n<Badge variant=\"secondary\">+20.1%</Badge>    // correct\n<span className=\"text-emerald-600\">+20.1%</span> // wrong\n```\n\n## Component Selection\n\n| Need | Use |\n| --- | --- |\n| Button/action | `Button` with appropriate variant |\n| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` |\n| Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` |\n| Data display | `Table`, `Card`, `Badge`, `Avatar` |\n| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |\n| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |\n| Feedback | `toast` (Base UI), `sonner` (Radix/Aria), `Alert`, `Progress`, `Skeleton`, `Spinner` |\n| Command palette | `Command` inside `Dialog` |\n| Charts | `Chart` (wraps Recharts) |\n| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |\n| Empty states | `Empty` |\n| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` |\n| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` |\n| Chat / conversation UI | `MessageScroller`, `Message`, `Bubble`, `Attachment`, `Marker` |\n\n## Key Fields\n\nThe injected project context contains these key fields:\n\n- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode.\n- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `\"use client\"` at the top of the file. Always reference this field when advising on the directive.\n- **`tailwindVersion`** → `\"v4\"` uses `@theme inline` blocks; `\"v3\"` uses `tailwind.config.js`.\n- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one.\n- **`style`** → component visual treatment (e.g. `nova`, `vega`).\n- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props.\n- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`.\n- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc.\n- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA).\n- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`).\n- **`preset`** → resolved preset code and values for the current project. Use `npx shadcn@latest preset resolve --json` when you only need preset information.\n\nSee [cli.md — `info` command](./cli.md) for the full field reference.\n\n## Component Docs, Examples, and Usage\n\nRun `npx shadcn@latest docs <component>` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content.\n\n```bash\nnpx shadcn@latest docs button dialog select\n```\n\n**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing.\n\n## Workflow\n\n1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh.\n2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed.\n3. **Find components** — `npx shadcn@latest search`.\n4. **Get docs and examples** — run `npx shadcn@latest docs <component>` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`.\n5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below).\n6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.\n7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on.\n8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says \"add a login block\" without specifying `@shadcn`, `@tailark`, `owner/repo`, etc.), ask which registry to use. Never default to a registry on behalf of the user.\n9. **Switching presets** — Ask the user first: **overwrite**, **partial**, **merge**, or **skip**?\n   - **Inspect current preset**: `npx shadcn@latest preset resolve`. Use `--json` when you need structured values.\n   - **Inspect incoming preset**: `npx shadcn@latest preset decode <code>`. Use `preset url <code>` or `preset open <code>` to share or open the preset builder.\n   - **Overwrite**: `npx shadcn@latest apply <code>`. Overwrites detected components, fonts, and CSS variables.\n   - **Partial**: `npx shadcn@latest apply <code> --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms.\n   - **Merge**: `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually.\n   - **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.\n   - **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.\n\n## Updating Components\n\nWhen the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**\n\n1. Run `npx shadcn@latest add <component> --dry-run` to see all files that would be affected.\n2. For each file, run `npx shadcn@latest add <component> --diff <file>` to see what changed upstream vs local.\n3. Decide per file based on the diff:\n   - No local changes → safe to overwrite.\n   - Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications.\n   - User says \"just update everything\" → use `--overwrite`, but confirm first.\n4. **Never use `--overwrite` without the user's explicit approval.**\n\n## Quick Reference\n\n```bash\n# Create a new project.\nnpx shadcn@latest init --name my-app --preset base-nova\nnpx shadcn@latest init --name my-app --preset a2r6bw --template vite\n\n# Create a monorepo project.\nnpx shadcn@latest init --name my-app --preset base-nova --monorepo\nnpx shadcn@latest init --name my-app --preset base-nova --template next --monorepo\n\n# Initialize existing project.\nnpx shadcn@latest init --preset base-nova\nnpx shadcn@latest init --defaults  # shortcut: --template=next --preset=nova (base style implied)\n\n# Apply a preset to an existing project.\nnpx shadcn@latest apply a2r6bw\nnpx shadcn@latest apply a2r6bw --only theme\nnpx shadcn@latest apply a2r6bw --only font\nnpx shadcn@latest apply a2r6bw --only theme,font\n\n# Inspect preset codes and project preset state.\nnpx shadcn@latest preset decode a2r6bw\nnpx shadcn@latest preset url a2r6bw\nnpx shadcn@latest preset open a2r6bw\nnpx shadcn@latest preset resolve\nnpx shadcn@latest preset resolve --json\n\n# Add components.\nnpx shadcn@latest add button card dialog\nnpx shadcn@latest add @magicui/shimmer-button\nnpx shadcn@latest add owner/repo/item\nnpx shadcn@latest add --all\n\n# Preview changes before adding/updating.\nnpx shadcn@latest add button --dry-run\nnpx shadcn@latest add button --diff button.tsx\nnpx shadcn@latest add @acme/form --view button.tsx\nnpx shadcn@latest add owner/repo/item --dry-run\n\n# Search registries.\nnpx shadcn@latest search @shadcn -q \"sidebar\"\nnpx shadcn@latest search @tailark -q \"stats\"\nnpx shadcn@latest search owner/repo -q \"login\"\nnpx shadcn@latest search                          # all configured registries\nnpx shadcn@latest search @shadcn -q \"menu\" -t ui  # filter by item type\n\n# Get component docs and example URLs.\nnpx shadcn@latest docs button dialog select\n\n# View registry item details (for items not yet installed).\nnpx shadcn@latest view @shadcn/button\nnpx shadcn@latest view owner/repo/item\n```\n\n**Named presets:** `nova`, `vega`, `maia`, `lyra`, `mira`, `luma` **Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo) **Preset codes:** Version-prefixed base62 strings (e.g. `a2r6bw` or `b0`), from [ui.shadcn.com](https://ui.shadcn.com).\n\n## Detailed References\n\n- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states\n- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading\n- [rules/chat.md](./rules/chat.md) — MessageScroller, Message, Bubble, Attachment, Marker; streaming, anchoring, jump-to-latest\n- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects\n- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index\n- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion\n- [cli.md](./cli.md) — Commands, flags, presets, templates\n- [registry.md](./registry.md) — Authoring source registries, `include`, item definitions, dependencies, GitHub registry rules\n- [customization.md](./customization.md) — Theming, CSS variables, extending components\n\n<!-- File: packages/ui/AGENTS.md (Tokens: ~1140 | Category: anthropic-skill) -->\nshadcn/ui component library (new-york style, Radix primitives, Tailwind v4). Consumed as `@repo/ui`.\n\n## Scope\n\n- Only generic primitives belong here. If a component imports a route, a query, or the session, it belongs in `apps/app/components/` instead.\n- Nothing here imports application or domain code (`apps/`, `db/`). Consumers still have to supply Tailwind `@source` entries and the theme tokens.\n- Imports inside this package go through Node subpath imports — `#lib/utils`, `#components/toggle` — declared in `package.json#imports` and mirrored by the `components.json` aliases, so the CLI generates them natively. They resolve against _this_ package from every workspace, so a consumer needs no matching file of its own. Requires `moduleResolution: \"bundler\"`, which `packages/typescript-config/base.jsonc` already sets.\n- `index.ts` exports the top-level primitives in `components/`. Nested files are implementation details — promote one deliberately if it should be public.\n\n- `scripts/` is intentionally outside the tsconfig `include`. These are Bun CLI tools, while the library's declaration build uses the browser-only React preset (`types: [\"vite/client\"]`). Including them would require Bun/Node types and emit declarations for tooling. Both commands hit the network and overwrite files, so smoke-test changes on a single component (`bun ui:update button`).\n\n## Adding and Updating Components\n\n- Use `bun ui:add <component>`; don't hand-write files the registry already has. The wrappers take item names only and reject flags — they exist for reusable `registry:ui` primitives and always write to `packages/ui`. Blocks, pages, custom registries, and the read-only flags go to the CLI directly — point it at the workspace, or it resolves the wrong `components.json`: `bunx shadcn@latest add button --dry-run --cwd packages/ui`.\n- Both run `scripts/postprocess.ts` afterwards: it strips `\"use client\"`, regenerates `index.ts`, and formats. `index.ts` is generated; add components with the CLI, not by editing it.\n- Components import primitives from the unified `radix-ui` package, not the per-primitive `@radix-ui/react-*` ones. The CLI adds whatever a component needs; review and commit the resulting `package.json` and `bun.lock` changes.\n- `bun ui:update` re-fetches every installed component in one `shadcn add` call, overwriting in place. Review `git diff` before committing; local edits are lost. Pass names (`bun ui:update button card`) to narrow it; unknown names are rejected rather than silently added, though a named component still pulls its registry dependencies.\n- Registry output is not uniform — read what it generated. Postprocessing is mechanical; API changes still need review. Convert React 18 patterns before committing — no lint rule covers any of these, so they are a review step:\n  - `<Context.Provider value={x}>` → `<Context value={x}>`\n  - `React.useContext(C)` → `React.use(C)`\n  - `React.ElementRef<T>` → `React.ComponentRef<T>` — `ElementRef` is a deprecated alias in `@types/react` 19. `bun --cwd apps/web check` reports it as a hint.\n- `components.json` needs `tailwind.config: \"\"`. That empty string is how the CLI detects Tailwind v4; without it the registry silently serves the v3-era components (`forwardRef`, no `data-slot`) into a v4 project.\n\n## Styling\n\n- Every component accepts `className` and passes it through `cn()` last — directly, or via the `className` slot on a `cva` variants call — so callers can override defaults without a specificity fight.\n- Use theme tokens (`bg-primary`, `text-muted-foreground`), never raw colors. Each consumer defines the values in its own `styles/globals.css` — `apps/app` and `apps/web` keep separate copies, so a palette change means editing both. `styles.css` here exists only to satisfy the shadcn CLI.\n- Class names must appear as complete literals — Tailwind scans text, so `` `bg-${color}-500` `` produces nothing.\n- Consuming apps must `@source` every directory here that holds class names, or those classes are stripped from their build.\n- Enter/exit utilities (`animate-in`, `fade-in-0`, `zoom-in-95`, `slide-in-from-*`) come from `tw-animate-css`, which each consumer imports separately — `apps/app` does, `apps/web` does not, because nothing it renders is animated. Adding an animated component to a consumer that lacks the import fails silently: Tailwind does not recognise the class names, drops them, and reports nothing, so the component simply renders without transitions.\n\n## Conventions\n\n- Named exports only — no default exports.\n- Variants via `class-variance-authority`; export the variants object when another component composes it (see `toggle.tsx` → `toggle-group.tsx`).\n- Prefer a Radix primitive over hand-rolled behavior — it brings the ARIA roles and keyboard handling with it. `ToggleGroup type=\"single\"` already renders `role=\"radiogroup\"` with `role=\"radio\"` items and arrow-key navigation, so callers add no keyboard code.\n\n<!-- File: packages/ui/CLAUDE.md (Tokens: ~5 | Category: anthropic-skill) -->\n@AGENTS.md\n\n<!-- File: .claude/commands/validate-auth-schema.md (Tokens: ~467 | Category: command-prompt) -->\n# Validate Auth Schema\n\nCheck that the Drizzle schema in `db/schema/` still satisfies what Better Auth expects. Better Auth derives its required tables from the enabled plugins, so the generated JSON — not the upstream docs — is the source of truth:\n\n```bash\nbun run db/scripts/generate-auth-schema.ts\n```\n\nThe script builds a real auth instance from `apps/api/lib/auth.ts` with fixed placeholder credentials, every optional integration switched on, so the output is the full set of tables rather than the subset your environment happens to enable. Per field it carries the `type`, the `required`, `unique` and `index` flags, any literal `defaultValue`, and the reference target with its `onDelete`. Per table it carries `indexes`: the composite indexes a plugin declares.\n\nCompare that output against `db/schema/` and report:\n\n- Tables Better Auth expects that are missing, and tables no plugin in `auth.ts` needs any more.\n- Field-level drift: missing or extra columns, mismatched types, and `required` or `unique` flags that disagree.\n- Foreign keys pointing at the wrong table or column.\n- A `true` `index` flag, a table-level entry in `indexes`, a literal `defaultValue`, or an `onDelete` in the output is Better Auth asking for it: report a schema that lacks one. Indexes, defaults and cascades the project adds beyond that list are its own decisions — check those against the conventions in `db/AGENTS.md` and do not report them as drift.\n- Local naming that must stay mapped, not renamed away: Better Auth's `account` is our `identity` table.\n- Project-specific columns, indexes, and constraints are expected and fine on their own. Flag any that constrain a write Better Auth performs: a `NOT NULL` column with no database default breaks every insert into that table, and an extra unique or check constraint can reject a row Better Auth considers valid. Fields that must participate in its models belong in `additionalFields` rather than added to the table behind its back.\n\nReport findings with the specific fix for each. Do not run `db:push`, `db:generate`, or `db:migrate` as part of a validation — schema changes are a separate, deliberate step.\n\n<!-- File: packages/core/README.md (Tokens: ~15 | Category: plugin-manifest) -->\n# Core Package\n\nShared utilities and helpers used across the monorepo.\n\n<!-- File: packages/typescript-config/README.md (Tokens: ~159 | Category: plugin-manifest) -->\n# TypeScript Configuration\n\nShared TypeScript configuration for the monorepo.\n\n## Usage\n\nExtend from the appropriate configuration in your `tsconfig.json`:\n\n```jsonc\n// React applications\n{ \"extends\": \"@repo/typescript-config/react.jsonc\" }\n\n// Node.js/Bun applications\n{ \"extends\": \"@repo/typescript-config/node.jsonc\" }\n\n// Cloudflare Workers\n{ \"extends\": \"@repo/typescript-config/cloudflare.jsonc\" }\n```\n\n## Available Configurations\n\n- `base.jsonc` -- Core strict-mode configuration shared by all targets\n- `react.jsonc` -- React applications with DOM types and JSX support\n- `node.jsonc` -- Node.js/Bun backend services\n- `cloudflare.jsonc` -- Cloudflare Workers edge functions\n\n<!-- File: packages/ui/README.md (Tokens: ~224 | Category: plugin-manifest) -->\n# UI Components\n\nShared UI component library built on shadcn/ui (new-york style), Radix UI, and Tailwind CSS v4.\n\n[Documentation](https://reactstarter.com/frontend/ui)\n\n## Usage\n\n```typescript\nimport { Button, Card, Input, cn } from \"@repo/ui\";\n```\n\n## Commands\n\n```bash\nbun ui:add <component>    # Add a shadcn/ui component\nbun ui:update             # Re-fetch installed components\n```\n\nSee [AGENTS.md](./AGENTS.md) for the conventions these commands assume.\n\n## Structure\n\n```bash\ncomponents/       # shadcn/ui components\nhooks/            # Custom React hooks\nlib/              # Utilities (cn function)\nscripts/          # ui:add / ui:update and their postprocessing\nindex.ts          # Generated barrel export\n```\n\nConsuming apps must `@source` every directory here that holds class names, or those classes are stripped from their build. See [apps/app/tailwind.config.css](../../apps/app/tailwind.config.css) for an example.\n\n<!-- File: packages/ws-protocol/README.md (Tokens: ~211 | Category: plugin-manifest) -->\n# WebSocket Protocol\n\nType-safe WebSocket protocol definitions using [WS-Kit](https://github.com/kriasoft/ws-kit).\n\n[Documentation](https://reactstarter.com/recipes/websockets)\n\n## Quick Start\n\n```bash\nbun run example       # Run example server\n```\n\n## Usage\n\n```typescript\nimport { z, message, rpc } from \"@ws-kit/zod\";\nimport { createRouter, withZod } from \"@ws-kit/zod\";\n\nconst Ping = message(\"PING\", { timestamp: z.number().optional() });\nconst Pong = message(\"PONG\", { timestamp: z.number() });\n\nconst router = createRouter<{ userId?: string }>()\n  .plugin(withZod())\n  .on(Ping, (ctx) => {\n    ctx.send(Pong, { timestamp: Date.now() });\n  });\n```\n\n## Structure\n\n```bash\nmessages.ts   # Message schema definitions\nrouter.ts     # Router factory with handlers\nexample.ts    # Example server\nindex.ts      # Public exports\n```\n</yakaai_skills>"}