{"manifest":{"name":"SkillSlap Component Builder","version":"1.0.0","description":"Generate Next.js 16 App Router components following SkillSlap patterns: Tailwind + Shadcn, server/client split, typed Supabase queries, no SELECT *. Describe the UI you need and get a production-ready component.","tags":["nextjs","react","typescript","tailwind","supabase","skillslap"],"standard":"agentskills.io","standard_version":"1.0","content_checksum":"809a5fa89765eb2d75b277fc38a54f937c0654ca4ab6d4f5100d58b08d8a3b7a","bundle_checksum":null,"metadata":{},"files":[]},"files":{"SKILL.md":"# SkillSlap Component Builder\n\n> **Purpose:** Generate a production-ready Next.js 16 App Router component following SkillSlap's exact codebase conventions. The output should be paste-ready into `skillslap/components/` with no modification.\n\n---\n\n## Invocation\n\n```\n/component <description>\n```\n\n**Examples:**\n- `/component A skill report modal — user selects a reason from a dropdown and submits`\n- `/component A collections sidebar showing the user's saved skill collections with add/remove`\n- `/component A skill version history timeline showing diffs between versions`\n\n---\n\n## SkillSlap Component Conventions\n\n### Server vs Client\n\n**Server component** (default — no `'use client'`):\n- Fetches data directly from Supabase via `createClient()` from `@/lib/supabase/server`\n- Cannot use hooks, event listeners, or browser APIs\n- Use for: pages, data-fetching wrappers, layout sections\n\n**Client component** (add `'use client'` at top):\n- Required for: `useState`, `useEffect`, event handlers, `useRouter`, `useSearchParams`\n- Keep client components small — push data fetching up to a server parent\n\n```tsx\n// Server parent passes data down\nasync function SkillCollectionsList({ userId }: { userId: string }) {\n  const supabase = await createClient()\n  const { data } = await (supabase.from('skill_collections') as any)\n    .select('id, title, created_at')\n    .eq('user_id', userId)\n  return <SkillCollectionsClient collections={data ?? []} />\n}\n```\n\n### Supabase queries — NEVER SELECT *\nAlways use explicit column lists. For `skills` table use `SKILLS_COLUMNS` from `@/lib/supabase/skills-columns`:\n```tsx\nimport { SKILLS_COLUMNS } from '@/lib/supabase/skills-columns'\n\nconst { data } = await supabase\n  .from('skills')\n  .select(`${SKILLS_COLUMNS}, author:users!skills_author_id_fkey(id, username, avatar_url)`)\n  .eq('status', 'active')\n```\n\nFor joins or partial selects, add `as any` to satisfy the typed client:\n```tsx\nconst { data } = await (supabase.from('skill_collections') as any)\n  .select('id, title, skill_count')\n```\n\n### Styling — Tailwind + `cn()` utility\n```tsx\nimport { cn } from '@/lib/utils/cn'\n\n// Always use cn() for conditional classes\n<div className={cn(\n  'base-classes here',\n  isActive && 'active-classes',\n  className  // always accept className prop\n)} />\n```\n\n**Color palette:**\n- Background: `bg-gray-950`, `bg-gray-900`, `bg-gray-800`\n- Borders: `border-gray-800`, `border-gray-700`\n- Text: `text-white`, `text-gray-300`, `text-gray-400`, `text-gray-500`\n- Accent: `text-purple-400`, `border-purple-600/50`, `bg-purple-600`\n- Success: `text-green-400`\n- Warning: `text-yellow-400`\n- Danger: `text-red-400`\n\n### Shadcn UI components (already installed)\n```tsx\nimport { Button } from '@/components/ui/button'\nimport { Input } from '@/components/ui/input'\nimport { Badge } from '@/components/ui/badge'\nimport { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'\nimport * as Dialog from '@radix-ui/react-dialog'\nimport * as DropdownMenu from '@radix-ui/react-dropdown-menu'\n```\n\n### Icons — lucide-react only\n```tsx\nimport { Plus, X, Search, ChevronDown, Loader2 } from 'lucide-react'\n```\n\n### Error + loading states\n```tsx\n// Loading skeleton\n<div className=\"h-8 bg-gray-800 rounded animate-pulse\" />\n\n// Error display\n<p className=\"text-sm text-red-400\">{error}</p>\n\n// Empty state\n<div className=\"text-center py-12 text-gray-500\">\n  <p className=\"text-sm\">No items yet</p>\n</div>\n```\n\n### Forms + mutations (client component)\n```tsx\n'use client'\nimport { useState } from 'react'\nimport { createClient } from '@/lib/supabase/client'  // note: client, not server\n\nasync function handleSubmit(e: React.FormEvent) {\n  e.preventDefault()\n  setLoading(true)\n  setError(null)\n  try {\n    const supabase = createClient()\n    const { error } = await supabase.from('table').insert({ ... })\n    if (error) throw error\n    onSuccess?.()\n  } catch (err: any) {\n    setError(err.message ?? 'Something went wrong')\n  } finally {\n    setLoading(false)\n  }\n}\n```\n\n### Props interface pattern\n```tsx\ninterface MyComponentProps {\n  // required props first\n  skillId: string\n  // optional props with defaults\n  className?: string\n  onSuccess?: () => void\n}\n\nexport function MyComponent({ skillId, className, onSuccess }: MyComponentProps) {\n```\n\n### TypeScript types\n```tsx\nimport type { SkillWithAuthor } from '@/lib/supabase/types'\n```\n\n---\n\n## Output Format\n\n1. **Main component file** — full TypeScript, no placeholders, no TODOs\n2. **File path** — where it belongs in `skillslap/components/`\n3. **Imports** — only what's used\n4. **If server + client split needed** — output both files\n\n---\n\n## Checklist Before Outputting\n\n- [ ] No `SELECT *` — explicit columns everywhere\n- [ ] `as any` on Supabase joins\n- [ ] `cn()` for all conditional classNames\n- [ ] `className?: string` prop on every component\n- [ ] Loading and error states present\n- [ ] `'use client'` only where actually needed\n- [ ] No hardcoded colors — use SkillSlap palette\n"}}