nextjs-hackathon-stack 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/index.js +152 -0
  4. package/package.json +54 -0
  5. package/template/.cursor/agents/business-intelligence.md +38 -0
  6. package/template/.cursor/agents/code-reviewer.md +74 -0
  7. package/template/.cursor/agents/frontend.md +45 -0
  8. package/template/.cursor/agents/security-researcher.md +49 -0
  9. package/template/.cursor/agents/technical-lead.md +36 -0
  10. package/template/.cursor/agents/test-qa.md +85 -0
  11. package/template/.cursor/rules/ai.mdc +53 -0
  12. package/template/.cursor/rules/architecture.mdc +42 -0
  13. package/template/.cursor/rules/coding-standards.mdc +53 -0
  14. package/template/.cursor/rules/components.mdc +33 -0
  15. package/template/.cursor/rules/data-fetching.mdc +63 -0
  16. package/template/.cursor/rules/forms.mdc +59 -0
  17. package/template/.cursor/rules/general.mdc +52 -0
  18. package/template/.cursor/rules/nextjs.mdc +46 -0
  19. package/template/.cursor/rules/security.mdc +54 -0
  20. package/template/.cursor/rules/supabase.mdc +36 -0
  21. package/template/.cursor/rules/testing.mdc +116 -0
  22. package/template/.cursor/skills/create-api-route/SKILL.md +62 -0
  23. package/template/.cursor/skills/create-feature/SKILL.md +52 -0
  24. package/template/.cursor/skills/review-branch/SKILL.md +61 -0
  25. package/template/.cursor/skills/security-audit/SKILL.md +69 -0
  26. package/template/.env.example +24 -0
  27. package/template/.requirements/README.md +15 -0
  28. package/template/.requirements/auth.md +50 -0
  29. package/template/.requirements/template.md +25 -0
  30. package/template/README.md +98 -0
  31. package/template/components.json +19 -0
  32. package/template/drizzle.config.ts +10 -0
  33. package/template/eslint.config.ts +66 -0
  34. package/template/middleware.ts +10 -0
  35. package/template/next.config.ts +31 -0
  36. package/template/package.json.tmpl +62 -0
  37. package/template/playwright.config.ts +22 -0
  38. package/template/src/app/(auth)/login/page.tsx +12 -0
  39. package/template/src/app/(protected)/layout.tsx +19 -0
  40. package/template/src/app/(protected)/page.tsx +16 -0
  41. package/template/src/app/api/auth/callback/route.ts +21 -0
  42. package/template/src/app/globals.css +1 -0
  43. package/template/src/app/layout.tsx +21 -0
  44. package/template/src/e2e/chat.spec.ts +8 -0
  45. package/template/src/e2e/home.spec.ts +8 -0
  46. package/template/src/e2e/login.spec.ts +21 -0
  47. package/template/src/features/auth/__tests__/login-form.test.tsx +43 -0
  48. package/template/src/features/auth/__tests__/use-auth.test.ts +46 -0
  49. package/template/src/features/auth/actions/login.action.ts +38 -0
  50. package/template/src/features/auth/actions/logout.action.ts +10 -0
  51. package/template/src/features/auth/components/login-form.tsx +80 -0
  52. package/template/src/features/auth/hooks/use-auth.ts +17 -0
  53. package/template/src/features/chat/__tests__/chat-ui.test.tsx +30 -0
  54. package/template/src/features/chat/__tests__/route.test.ts +19 -0
  55. package/template/src/features/chat/api/route.ts +16 -0
  56. package/template/src/features/chat/components/chat-ui.tsx +47 -0
  57. package/template/src/features/chat/hooks/use-chat.ts +1 -0
  58. package/template/src/features/tts/__tests__/route.test.ts +13 -0
  59. package/template/src/features/tts/__tests__/tts-player.test.tsx +20 -0
  60. package/template/src/features/tts/api/route.ts +14 -0
  61. package/template/src/features/tts/components/tts-player.tsx +59 -0
  62. package/template/src/features/video/__tests__/route.test.ts +13 -0
  63. package/template/src/features/video/__tests__/video-generator.test.tsx +20 -0
  64. package/template/src/features/video/api/route.ts +14 -0
  65. package/template/src/features/video/components/video-generator.tsx +56 -0
  66. package/template/src/shared/__tests__/ai.test.ts +8 -0
  67. package/template/src/shared/__tests__/minimax-media.test.ts +57 -0
  68. package/template/src/shared/__tests__/providers.test.tsx +14 -0
  69. package/template/src/shared/__tests__/schema.test.ts +18 -0
  70. package/template/src/shared/__tests__/supabase-client.test.ts +13 -0
  71. package/template/src/shared/__tests__/supabase-server.test.ts +22 -0
  72. package/template/src/shared/components/providers.tsx +19 -0
  73. package/template/src/shared/db/index.ts +7 -0
  74. package/template/src/shared/db/schema.ts +15 -0
  75. package/template/src/shared/lib/ai.ts +8 -0
  76. package/template/src/shared/lib/minimax-media.ts +63 -0
  77. package/template/src/shared/lib/supabase/client.ts +8 -0
  78. package/template/src/shared/lib/supabase/middleware.ts +40 -0
  79. package/template/src/shared/lib/supabase/server.ts +26 -0
  80. package/template/src/test-setup.ts +1 -0
  81. package/template/tailwind.css +20 -0
  82. package/template/tsconfig.json +31 -0
  83. package/template/vitest.config.ts +33 -0
@@ -0,0 +1,53 @@
1
+ ---
2
+ description: AI SDK + Edge runtime rules. RISK 3 (cold starts).
3
+ globs: ["src/features/chat/**", "src/features/video/**", "src/features/tts/**", "src/shared/lib/ai*"]
4
+ ---
5
+
6
+ # AI Rules (Risk 3: Vercel Cold Starts)
7
+
8
+ ## Edge Runtime is Mandatory
9
+ ```typescript
10
+ // ✅ ALL AI routes must export this
11
+ export const runtime = "edge";
12
+
13
+ // ❌ Never use Node.js runtime for AI routes (1-3s cold start)
14
+ // export const runtime = "nodejs"; // WRONG
15
+ ```
16
+
17
+ ## Never Mix Edge + DB
18
+ ```typescript
19
+ // ❌ WRONG — DB + Edge in same route
20
+ export const runtime = "edge";
21
+ import { db } from "@/shared/db"; // WRONG — postgres driver needs Node.js
22
+ ```
23
+
24
+ If you need DB data in an AI route:
25
+ 1. Fetch DB data in a Server Component (Node.js)
26
+ 2. Pass to client
27
+ 3. Client sends to Edge AI route
28
+
29
+ ## AI Model via AI Gateway
30
+ ```typescript
31
+ import { createOpenAI } from "@ai-sdk/openai";
32
+
33
+ const gateway = createOpenAI({
34
+ baseURL: process.env.AI_GATEWAY_URL,
35
+ apiKey: process.env.MINIMAX_API_KEY,
36
+ });
37
+
38
+ export const aiModel = gateway("MiniMax-Text-01");
39
+ ```
40
+
41
+ ## Streaming Pattern
42
+ ```typescript
43
+ import { streamText } from "ai";
44
+ import { aiModel } from "@/shared/lib/ai";
45
+
46
+ export const runtime = "edge";
47
+
48
+ export async function POST(req: Request) {
49
+ const { messages } = await req.json();
50
+ const result = streamText({ model: aiModel, messages });
51
+ return result.toDataStreamResponse();
52
+ }
53
+ ```
@@ -0,0 +1,42 @@
1
+ ---
2
+ description: Feature-based architecture rules. Always applies.
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Architecture Rules
7
+
8
+ ## Feature-Based Structure
9
+ ```
10
+ features/
11
+ └── <feature-name>/
12
+ ├── components/ # React components
13
+ ├── actions/ # Server Actions (*.action.ts)
14
+ ├── hooks/ # React hooks (use-*.ts)
15
+ ├── api/ # Route handlers (route.ts)
16
+ └── __tests__/ # Colocated tests
17
+ ```
18
+
19
+ ## Dependency Direction
20
+ ```
21
+ features/* → shared/* ✅
22
+ shared/* → features/* ❌ (circular dependency)
23
+ features/a → features/b ❌ (cross-feature import)
24
+ ```
25
+
26
+ If two features share logic, move it to `shared/`.
27
+
28
+ ## Layer Rules
29
+ - `app/` — routing, layouts, RSC data fetching
30
+ - `features/` — feature business logic, UI, actions
31
+ - `shared/lib/` — infrastructure (supabase, ai, db)
32
+ - `shared/components/` — design system components
33
+
34
+ ## Error Boundaries
35
+ - Place at route level (`app/(protected)/layout.tsx`)
36
+ - Never let unhandled errors propagate to root
37
+
38
+ ## Server Actions
39
+ - File naming: `name.action.ts`
40
+ - Always `"use server"` at top of file
41
+ - Validate input with Zod before any business logic
42
+ - Return typed result objects, use `redirect()` for navigation
@@ -0,0 +1,53 @@
1
+ ---
2
+ description: Code quality standards. Always applies to all files.
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Coding Standards
7
+
8
+ ## Absolute Rules
9
+ - **Zero `any`** — use `unknown` + type guards or proper generics. `@typescript-eslint/no-explicit-any: error`
10
+ - **Zero comments** — code must be self-documenting through naming. If you need a comment, rename the function
11
+ - **Zero magic numbers/strings** — use typed constants
12
+ - **No `console.log`** in production code — `no-console: warn`
13
+ - **No `!` non-null assertions** — use proper null checks
14
+
15
+ ## TDD (Test-Driven Development)
16
+ 1. **RED**: Write a failing test first
17
+ 2. **GREEN**: Write minimum code to pass the test
18
+ 3. **REFACTOR**: Improve code while tests stay green
19
+
20
+ Never write implementation before tests exist.
21
+
22
+ ## TDD — Iron Rule
23
+ - Tests define the contract. **NEVER modify a test to make it pass.**
24
+ - If a test fails, the implementation is wrong — fix the code, not the test.
25
+ - Only modify tests to: add new tests (RED), restructure without weakening (REFACTOR).
26
+ - Weakening a test (removing assertions, loosening matchers, adding `.skip`) is a rejection-worthy offense.
27
+
28
+ ## SOLID Principles
29
+ - **S**ingle Responsibility: one reason to change per module
30
+ - **O**pen/Closed: extend behavior without modifying existing code
31
+ - **L**iskov Substitution: subtypes must be substitutable for base types
32
+ - **I**nterface Segregation: prefer small, focused interfaces
33
+ - **D**ependency Inversion: depend on abstractions, not concretions
34
+
35
+ ## KISS & DRY
36
+ - Simplest solution that works — no over-engineering
37
+ - Three similar lines is better than a premature abstraction
38
+ - Extract abstraction only when you have 3+ real use cases
39
+
40
+ ## Size Limits
41
+ - Functions: max 20 lines
42
+ - Files: max 200 lines
43
+ - If exceeded, split by responsibility
44
+
45
+ ## Error Handling
46
+ - Use `Result` types for expected failures
47
+ - Never swallow errors silently
48
+ - Always handle Promise rejections
49
+
50
+ ## Types
51
+ - Prefer `type` over `interface` for data shapes
52
+ - Use `satisfies` for type-checked literals
53
+ - Use `as const` for immutable literals
@@ -0,0 +1,33 @@
1
+ ---
2
+ description: shadcn/ui and Tailwind v4 component standards.
3
+ globs: ["src/**/components/**"]
4
+ ---
5
+
6
+ # Component Standards
7
+
8
+ ## shadcn/ui
9
+ - Use shadcn/ui components from `@/shared/components/ui/`
10
+ - Never build custom UI primitives when shadcn has one
11
+ - Extend with `className` prop via `cn()` utility
12
+ - New York style, CSS variables for theming
13
+
14
+ ## Tailwind v4
15
+ - CSS-native config via `@theme {}` in CSS
16
+ - No `tailwind.config.js` — all config in `tailwind.css`
17
+ - No inline styles, no CSS modules
18
+ - Use Tailwind utility classes only
19
+
20
+ ## Accessibility (Required)
21
+ - Semantic HTML elements (`<button>`, `<nav>`, `<main>`, etc.)
22
+ - ARIA labels for interactive elements without visible text
23
+ - Keyboard navigation support
24
+ - Color contrast ratios (WCAG AA minimum)
25
+ - `role="alert"` for error messages
26
+
27
+ ## Server vs Client Components
28
+ - Default to Server Components (no `"use client"`)
29
+ - Add `"use client"` only when you need:
30
+ - Event handlers (`onClick`, `onChange`, etc.)
31
+ - Hooks (`useState`, `useEffect`, etc.)
32
+ - Browser APIs
33
+ - Keep Client Components small — push them to leaf nodes
@@ -0,0 +1,63 @@
1
+ ---
2
+ description: TanStack Query vs RSC data fetching rules. RISK 1. Always applies.
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Data Fetching Rules (Risk 1: TanStack Query + RSC Boundary)
7
+
8
+ ## Hard Boundary
9
+
10
+ | Use Case | Where |
11
+ |---|---|
12
+ | Initial page data | RSC `async` Server Component |
13
+ | Background refresh / polling | TanStack Query |
14
+ | User-triggered client reads | TanStack Query |
15
+ | All mutations | Server Actions ONLY |
16
+
17
+ **Never use TanStack Query mutations. Never use `useMutation`.**
18
+
19
+ ## WRONG ❌
20
+ ```typescript
21
+ // ❌ Fetching initial data in a Client Component with TanStack Query
22
+ "use client";
23
+ export function UserProfile({ userId }: { userId: string }) {
24
+ const { data } = useQuery({
25
+ queryKey: ["user", userId],
26
+ queryFn: () => fetchUser(userId), // Initial data should come from RSC
27
+ });
28
+ return <div>{data?.name}</div>;
29
+ }
30
+
31
+ // ❌ Mutation with TanStack Query
32
+ const mutation = useMutation({
33
+ mutationFn: (data) => updateUser(data), // Use Server Action instead
34
+ });
35
+ ```
36
+
37
+ ## CORRECT ✅
38
+ ```typescript
39
+ // ✅ Initial data from RSC, passed as prop
40
+ export default async function UserPage({ params }: { params: { id: string } }) {
41
+ const user = await fetchUser(params.id); // RSC fetch
42
+ return <UserProfile initialUser={user} />;
43
+ }
44
+
45
+ // ✅ TanStack Query for background refresh only
46
+ "use client";
47
+ export function UserProfile({ initialUser }: { initialUser: User }) {
48
+ const { data: user } = useQuery({
49
+ queryKey: ["user", initialUser.id],
50
+ queryFn: () => fetchUser(initialUser.id),
51
+ initialData: initialUser, // Seeded from RSC
52
+ staleTime: 5 * 60 * 1000,
53
+ });
54
+ return <div>{user.name}</div>;
55
+ }
56
+
57
+ // ✅ Mutation via Server Action
58
+ async function updateUserAction(formData: FormData) {
59
+ "use server";
60
+ await db.update(users).set({ name: formData.get("name") });
61
+ revalidatePath("/profile");
62
+ }
63
+ ```
@@ -0,0 +1,59 @@
1
+ ---
2
+ description: React Hook Form + Server Actions pattern. RISK 4.
3
+ globs: ["**/*form*", "**/*.action.ts"]
4
+ ---
5
+
6
+ # Forms Pattern (Risk 4: RHF + Server Actions)
7
+
8
+ ## The One Pattern
9
+
10
+ ```
11
+ Client: RHF + zodResolver → client validation (UX)
12
+ ↓ valid
13
+ Server Action → zod validate again (security) → business logic
14
+ ```
15
+
16
+ Zod validates TWICE:
17
+ 1. Client-side: immediate UX feedback via RHF
18
+ 2. Server-side: never trust the client
19
+
20
+ ## Template
21
+ ```typescript
22
+ // form.tsx (client)
23
+ "use client";
24
+ const schema = z.object({ email: z.string().email() });
25
+ type FormValues = z.infer<typeof schema>;
26
+
27
+ export function MyForm() {
28
+ const [state, formAction, isPending] = useActionState(myAction, { status: "idle" });
29
+ const { register, handleSubmit, formState: { errors } } = useForm<FormValues>({
30
+ resolver: zodResolver(schema),
31
+ });
32
+
33
+ const onSubmit = handleSubmit((data) => {
34
+ const fd = new FormData();
35
+ Object.entries(data).forEach(([k, v]) => fd.set(k, String(v)));
36
+ formAction(fd);
37
+ });
38
+
39
+ return (
40
+ <form onSubmit={onSubmit}>
41
+ {state.status === "error" && <p role="alert">{state.message}</p>}
42
+ <input {...register("email")} />
43
+ {errors.email && <p>{errors.email.message}</p>}
44
+ <button disabled={isPending}>Submit</button>
45
+ </form>
46
+ );
47
+ }
48
+
49
+ // action.ts (server)
50
+ "use server";
51
+ const schema = z.object({ email: z.string().email() });
52
+
53
+ export async function myAction(_prev: State, formData: FormData): Promise<State> {
54
+ const parsed = schema.safeParse({ email: formData.get("email") });
55
+ if (!parsed.success) return { status: "error", message: parsed.error.issues[0]?.message ?? "Invalid" };
56
+ // business logic...
57
+ redirect("/success");
58
+ }
59
+ ```
@@ -0,0 +1,52 @@
1
+ ---
2
+ description: Stack overview and project conventions. Always applies.
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Stack Overview
7
+
8
+ ## Technology Stack
9
+ - **Framework**: Next.js 15 (App Router)
10
+ - **Database**: Supabase (PostgreSQL) with Drizzle ORM for schema/migrations
11
+ - **Auth**: Supabase Auth via `@supabase/ssr`
12
+ - **ORM**: Drizzle + `drizzle-zod` (schema + migrations ONLY — no runtime queries)
13
+ - **DB Runtime Queries**: `supabase-js` client (RLS active)
14
+ - **Client State**: TanStack Query v5
15
+ - **Validation**: Zod (auto-generated via `drizzle-zod`)
16
+ - **Forms**: React Hook Form + Zod resolver
17
+ - **UI**: shadcn/ui + Tailwind CSS v4
18
+ - **AI**: Vercel AI SDK + MiniMax M2.7 via AI Gateway
19
+ - **Testing**: Vitest + React Testing Library + Playwright
20
+
21
+ ## Project Structure
22
+ ```
23
+ src/
24
+ ├── app/ # Next.js App Router pages
25
+ ├── features/ # Feature modules (auth, chat, video, tts)
26
+ │ └── <feature>/
27
+ │ ├── components/
28
+ │ ├── actions/
29
+ │ ├── hooks/
30
+ │ ├── api/
31
+ │ └── __tests__/
32
+ ├── shared/ # Shared across features
33
+ │ ├── components/ui/ # shadcn/ui components
34
+ │ ├── lib/ # supabase, ai, utilities
35
+ │ └── db/ # Drizzle schema + migrations
36
+ └── e2e/ # Playwright tests
37
+ ```
38
+
39
+ ## Naming Conventions
40
+ - Files: `kebab-case.ts`
41
+ - Components: `PascalCase`
42
+ - Hooks: `useCamelCase`
43
+ - Server Actions: `camelCase.action.ts`
44
+ - API Routes: `route.ts` (in feature `api/` folder)
45
+ - Tests: `*.test.ts` or `*.test.tsx`
46
+ - E2e: `*.spec.ts`
47
+
48
+ ## Import Ordering
49
+ 1. Node built-ins
50
+ 2. External packages
51
+ 3. Internal (`@/`)
52
+ 4. Relative (`./`, `../`)
@@ -0,0 +1,46 @@
1
+ ---
2
+ description: Next.js 15 App Router patterns.
3
+ globs: ["src/app/**"]
4
+ ---
5
+
6
+ # Next.js 15 Rules
7
+
8
+ ## App Router Conventions
9
+ - `page.tsx` — public route page component
10
+ - `layout.tsx` — shared layout (wraps child pages)
11
+ - `loading.tsx` — Suspense boundary UI
12
+ - `error.tsx` — Error boundary UI
13
+ - `route.ts` — API route handler
14
+ - `middleware.ts` — Edge middleware (project root)
15
+
16
+ ## Route Groups
17
+ - `(auth)` — unauthenticated routes
18
+ - `(protected)` — authenticated routes with RSC auth check
19
+
20
+ ## Data Fetching in RSC
21
+ ```typescript
22
+ // ✅ Async Server Component — fetch directly
23
+ export default async function Page() {
24
+ const data = await fetchData(); // No useState, no useEffect
25
+ return <ClientComponent initialData={data} />;
26
+ }
27
+ ```
28
+
29
+ ## Route Handler Patterns
30
+ ```typescript
31
+ // app/api/*/route.ts
32
+ export async function POST(request: Request) {
33
+ const body = await request.json() as unknown;
34
+ const parsed = schema.safeParse(body);
35
+ if (!parsed.success) return Response.json({ error: "Invalid input" }, { status: 400 });
36
+ // ...
37
+ }
38
+ ```
39
+
40
+ ## Metadata
41
+ ```typescript
42
+ export const metadata: Metadata = {
43
+ title: "Page Title",
44
+ description: "Page description",
45
+ };
46
+ ```
@@ -0,0 +1,54 @@
1
+ ---
2
+ description: Security rules and OWASP guidelines. Always applies.
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Security Rules
7
+
8
+ ## OWASP Top 10 Awareness
9
+ 1. **Injection** — use Drizzle parameterized queries, never string concatenation
10
+ 2. **Broken Auth** — Supabase Auth only, no custom auth
11
+ 3. **Sensitive Data** — HTTPS-only cookies, no secrets in code
12
+ 4. **XSS** — never use `dangerouslySetInnerHTML`, escape user input
13
+ 5. **Broken Access Control** — RLS on all tables, auth check in all protected routes
14
+ 6. **Security Misconfiguration** — CSP, HSTS, X-Frame-Options in `next.config.ts`
15
+ 7. **Insecure Dependencies** — run `npm audit` regularly
16
+ 8. **SSRF** — validate URLs before fetch
17
+
18
+ ## Environment Variables
19
+ ```
20
+ NEXT_PUBLIC_* → client-safe (Supabase URL, anon key only)
21
+ No prefix → server-only (DB URL, secret keys)
22
+ ```
23
+
24
+ Never use `NEXT_PUBLIC_` for secret keys.
25
+
26
+ ## Supabase RLS
27
+ - Enable RLS on EVERY table
28
+ - Write explicit allow/deny policies
29
+ - Test RLS policies — never assume they work
30
+
31
+ ## Auth in API Routes
32
+ ```typescript
33
+ export async function POST(request: Request) {
34
+ const supabase = await createClient();
35
+ const { data: { user } } = await supabase.auth.getUser();
36
+ if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 });
37
+ // ...
38
+ }
39
+ ```
40
+
41
+ ## Input Validation
42
+ - Validate ALL external input with Zod at the boundary
43
+ - Validate in Server Actions AND API routes
44
+ - Never trust client-side validation alone
45
+
46
+ ## Rate Limiting
47
+ - Apply rate limiting to all AI routes
48
+ - Apply rate limiting to auth endpoints
49
+ - Use Vercel's built-in rate limiting or `@upstash/ratelimit`
50
+
51
+ ## Cookies
52
+ - `httpOnly: true` for auth tokens (Supabase handles this)
53
+ - `secure: true` in production
54
+ - `sameSite: "lax"` minimum
@@ -0,0 +1,36 @@
1
+ ---
2
+ description: Supabase + Drizzle rules. RISK 2 (schema drift).
3
+ globs: ["src/shared/db/**", "src/shared/lib/supabase/**"]
4
+ ---
5
+
6
+ # Supabase + Drizzle Rules (Risk 2: Zod Schema Drift)
7
+
8
+ ## Drizzle: Schema + Migrations ONLY
9
+ ```typescript
10
+ // ✅ Use Drizzle for schema definition
11
+ export const users = pgTable("users", {
12
+ id: uuid("id").primaryKey(),
13
+ email: text("email").notNull(),
14
+ });
15
+
16
+ // ✅ Auto-generate Zod from Drizzle — zero manual Zod for DB types
17
+ export const insertUserSchema = createInsertSchema(users);
18
+ export const selectUserSchema = createSelectSchema(users);
19
+
20
+ // ❌ NEVER write Zod schemas manually for DB types
21
+ const userSchema = z.object({ id: z.string(), email: z.string() }); // WRONG
22
+ ```
23
+
24
+ ## Runtime Queries: supabase-js (RLS active)
25
+ ```typescript
26
+ // ✅ Runtime queries via supabase-js
27
+ const { data, error } = await supabase.from("users").select("*").eq("id", userId);
28
+
29
+ // ❌ Never use Drizzle for runtime queries
30
+ const users = await db.select().from(usersTable); // WRONG — bypasses RLS
31
+ ```
32
+
33
+ ## RLS Mandate
34
+ - Enable RLS on ALL tables in Supabase dashboard
35
+ - Every table must have explicit policies
36
+ - Test RLS policies in migrations
@@ -0,0 +1,116 @@
1
+ ---
2
+ description: TDD, Vitest, and Playwright testing standards.
3
+ globs: ["**/*.test.*", "**/e2e/**"]
4
+ ---
5
+
6
+ # Testing Standards
7
+
8
+ ## Iron Rules (Non-Negotiable)
9
+ 1. **NEVER modify a test to make it pass** — always fix the implementation code. Tests define the contract. If a test fails, the code is wrong, not the test.
10
+ 2. **Tests are immutable during GREEN phase** — only modify tests in RED phase (writing new ones) or REFACTOR phase (improving structure, never weakening assertions)
11
+ 3. **100% branch coverage** — every `if`, `else`, `switch`, `??`, `?.`, ternary, and `catch` must be exercised
12
+ 4. **Every edge case gets a test** — null/undefined, empty string, empty array, boundary values, error responses, timeout, concurrent calls
13
+ 5. **AAA is mandatory** — every test uses Arrange-Act-Assert with labeled comments
14
+
15
+ ## TDD Cycle
16
+ 1. **RED**: Write failing test describing desired behavior
17
+ 2. **GREEN**: Write minimum code to make it pass
18
+ 3. **REFACTOR**: Clean up while keeping tests green
19
+
20
+ ## AAA Pattern (Arrange-Act-Assert)
21
+
22
+ Every test MUST follow the Arrange-Act-Assert structure. No exceptions.
23
+
24
+ ```typescript
25
+ it("shows error message on invalid credentials", async () => {
26
+ // Arrange
27
+ const user = userEvent.setup();
28
+ mockSignIn.mockResolvedValue({ error: { message: "Invalid credentials" } });
29
+ render(<LoginForm />);
30
+
31
+ // Act
32
+ await user.type(screen.getByLabelText(/email/i), "test@example.com");
33
+ await user.type(screen.getByLabelText(/password/i), "password123");
34
+ await user.click(screen.getByRole("button", { name: /sign in/i }));
35
+
36
+ // Assert
37
+ expect(await screen.findByRole("alert")).toHaveTextContent("Invalid credentials");
38
+ });
39
+ ```
40
+
41
+ Rules:
42
+ - **Arrange**: set up state, mocks, render
43
+ - **Act**: perform the single action being tested
44
+ - **Assert**: verify outcome — one logical assertion per test
45
+ - Add blank lines between sections when all three are non-trivial
46
+ - Never skip a section — if Act is empty, you are testing the wrong thing
47
+
48
+ ## Vitest (Unit + Integration)
49
+ - Mock external dependencies only (HTTP calls, Supabase, DB)
50
+ - Test behavior, not implementation details
51
+ - Colocate tests with source: `features/auth/__tests__/`
52
+ - 100% coverage is non-negotiable
53
+
54
+ ```typescript
55
+ // ✅ AAA — tests behavior
56
+ it("shows error message on invalid credentials", async () => {
57
+ // Arrange
58
+ mockSignIn.mockResolvedValue({ error: { message: "Invalid credentials" } });
59
+ render(<LoginForm />);
60
+
61
+ // Act
62
+ await userEvent.click(screen.getByRole("button", { name: /sign in/i }));
63
+
64
+ // Assert
65
+ expect(await screen.findByRole("alert")).toBeInTheDocument();
66
+ });
67
+
68
+ // ❌ No AAA — tests implementation details, brittle
69
+ it("calls signInWithPassword with correct args", () => { ... });
70
+ ```
71
+
72
+ ## Playwright (E2E)
73
+ - Use `data-testid` attributes for stable selectors
74
+ - Page Object Pattern for complex flows
75
+ - Every user flow needs an e2e test
76
+
77
+ ```typescript
78
+ test("user can log in", async ({ page }) => {
79
+ // Arrange
80
+ await page.goto("/login");
81
+
82
+ // Act
83
+ await page.getByLabel(/email/i).fill("user@example.com");
84
+ await page.getByLabel(/password/i).fill("password123");
85
+ await page.getByRole("button", { name: /sign in/i }).click();
86
+
87
+ // Assert
88
+ await expect(page).toHaveURL("/");
89
+ });
90
+ ```
91
+
92
+ ## Edge Case Checklist
93
+ For every function, ask: what happens with...
94
+ - null / undefined input
95
+ - empty string / empty array / empty object
96
+ - boundary values (0, -1, MAX_SAFE_INTEGER)
97
+ - invalid types (wrong shape, missing fields)
98
+ - API error responses (4xx, 5xx, network failure)
99
+ - Loading / pending states
100
+ - Concurrent calls / race conditions
101
+ - Auth expired / missing session
102
+
103
+ If any of these apply, write a test for it. No exceptions.
104
+
105
+ ## Coverage Thresholds
106
+ ```typescript
107
+ // vitest.config.ts
108
+ coverage: {
109
+ thresholds: {
110
+ branches: 100,
111
+ functions: 100,
112
+ lines: 100,
113
+ statements: 100,
114
+ },
115
+ }
116
+ ```
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: create-api-route
3
+ description: Create a new Next.js API route with validation, auth, and tests. Use when adding API endpoints to a feature.
4
+ ---
5
+
6
+ # Create API Route Skill
7
+
8
+ ## Process
9
+
10
+ ### 1. Define Schema
11
+ ```typescript
12
+ const requestSchema = z.object({ /* fields */ });
13
+ const responseSchema = z.object({ /* fields */ });
14
+ type RequestBody = z.infer<typeof requestSchema>;
15
+ ```
16
+
17
+ ### 2. Write Test First (TDD)
18
+ ```typescript
19
+ describe("POST /api/my-route", () => {
20
+ it("returns 400 on invalid input", async () => { ... });
21
+ it("returns 401 when unauthenticated", async () => { ... });
22
+ it("returns 200 with valid input", async () => { ... });
23
+ });
24
+ ```
25
+
26
+ Run test — must FAIL (RED).
27
+
28
+ ### 3. Implement Route
29
+ ```typescript
30
+ // Determine runtime:
31
+ // - AI routes: export const runtime = "edge"
32
+ // - DB routes: no export (Node.js default)
33
+
34
+ export async function POST(request: Request) {
35
+ const supabase = await createClient();
36
+ const { data: { user } } = await supabase.auth.getUser();
37
+ if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 });
38
+
39
+ const body = await request.json() as unknown;
40
+ const parsed = requestSchema.safeParse(body);
41
+ if (!parsed.success) return Response.json({ error: "Invalid input" }, { status: 400 });
42
+
43
+ // business logic...
44
+
45
+ return Response.json(result);
46
+ }
47
+ ```
48
+
49
+ ### 4. Verify
50
+ ```bash
51
+ npm run test:unit
52
+ npm run lint
53
+ npm run typecheck
54
+ ```
55
+
56
+ ## Checklist
57
+ - [ ] Zod schema for request/response
58
+ - [ ] Auth check (unless public endpoint)
59
+ - [ ] Input validation
60
+ - [ ] Correct runtime (`edge` for AI, `nodejs` default for DB)
61
+ - [ ] Tests written BEFORE implementation
62
+ - [ ] 100% coverage