red64-cli 0.1.0 → 0.3.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 (125) hide show
  1. package/README.md +1 -2
  2. package/dist/cli/parseArgs.d.ts.map +1 -1
  3. package/dist/cli/parseArgs.js +5 -0
  4. package/dist/cli/parseArgs.js.map +1 -1
  5. package/dist/components/init/CompleteStep.d.ts.map +1 -1
  6. package/dist/components/init/CompleteStep.js +2 -2
  7. package/dist/components/init/CompleteStep.js.map +1 -1
  8. package/dist/components/init/TestCheckStep.d.ts +16 -0
  9. package/dist/components/init/TestCheckStep.d.ts.map +1 -0
  10. package/dist/components/init/TestCheckStep.js +120 -0
  11. package/dist/components/init/TestCheckStep.js.map +1 -0
  12. package/dist/components/init/index.d.ts +1 -0
  13. package/dist/components/init/index.d.ts.map +1 -1
  14. package/dist/components/init/index.js +1 -0
  15. package/dist/components/init/index.js.map +1 -1
  16. package/dist/components/init/types.d.ts +9 -0
  17. package/dist/components/init/types.d.ts.map +1 -1
  18. package/dist/components/screens/InitScreen.d.ts.map +1 -1
  19. package/dist/components/screens/InitScreen.js +69 -6
  20. package/dist/components/screens/InitScreen.js.map +1 -1
  21. package/dist/components/screens/ListScreen.d.ts.map +1 -1
  22. package/dist/components/screens/ListScreen.js +28 -3
  23. package/dist/components/screens/ListScreen.js.map +1 -1
  24. package/dist/components/screens/StartScreen.d.ts.map +1 -1
  25. package/dist/components/screens/StartScreen.js +212 -13
  26. package/dist/components/screens/StartScreen.js.map +1 -1
  27. package/dist/components/ui/ArtifactsSidebar.d.ts +19 -0
  28. package/dist/components/ui/ArtifactsSidebar.d.ts.map +1 -0
  29. package/dist/components/ui/ArtifactsSidebar.js +51 -0
  30. package/dist/components/ui/ArtifactsSidebar.js.map +1 -0
  31. package/dist/components/ui/FeatureSidebar.d.ts.map +1 -1
  32. package/dist/components/ui/FeatureSidebar.js +1 -1
  33. package/dist/components/ui/FeatureSidebar.js.map +1 -1
  34. package/dist/components/ui/index.d.ts +1 -0
  35. package/dist/components/ui/index.d.ts.map +1 -1
  36. package/dist/components/ui/index.js +1 -0
  37. package/dist/components/ui/index.js.map +1 -1
  38. package/dist/services/ClaudeErrorDetector.js +3 -3
  39. package/dist/services/ClaudeErrorDetector.js.map +1 -1
  40. package/dist/services/ConfigService.d.ts +1 -0
  41. package/dist/services/ConfigService.d.ts.map +1 -1
  42. package/dist/services/ConfigService.js.map +1 -1
  43. package/dist/services/ProjectDetector.d.ts +28 -0
  44. package/dist/services/ProjectDetector.d.ts.map +1 -0
  45. package/dist/services/ProjectDetector.js +236 -0
  46. package/dist/services/ProjectDetector.js.map +1 -0
  47. package/dist/services/TestRunner.d.ts +46 -0
  48. package/dist/services/TestRunner.d.ts.map +1 -0
  49. package/dist/services/TestRunner.js +85 -0
  50. package/dist/services/TestRunner.js.map +1 -0
  51. package/dist/services/index.d.ts +2 -0
  52. package/dist/services/index.d.ts.map +1 -1
  53. package/dist/services/index.js +2 -0
  54. package/dist/services/index.js.map +1 -1
  55. package/dist/types/index.d.ts +13 -0
  56. package/dist/types/index.d.ts.map +1 -1
  57. package/dist/types/index.js.map +1 -1
  58. package/framework/.red64/settings/templates/specs/gap-analysis.md +163 -0
  59. package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
  60. package/framework/agents/claude/.claude/agents/red64/validate-gap.md +13 -7
  61. package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
  62. package/framework/agents/claude/.claude/commands/red64/validate-gap.md +4 -0
  63. package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
  64. package/framework/agents/codex/.codex/agents/red64/validate-gap.md +13 -7
  65. package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
  66. package/framework/agents/codex/.codex/commands/red64/validate-gap.md +4 -0
  67. package/framework/stacks/generic/feedback.md +80 -0
  68. package/framework/stacks/nextjs/accessibility.md +437 -0
  69. package/framework/stacks/nextjs/api.md +431 -0
  70. package/framework/stacks/nextjs/coding-style.md +282 -0
  71. package/framework/stacks/nextjs/commenting.md +226 -0
  72. package/framework/stacks/nextjs/components.md +411 -0
  73. package/framework/stacks/nextjs/conventions.md +333 -0
  74. package/framework/stacks/nextjs/css.md +310 -0
  75. package/framework/stacks/nextjs/error-handling.md +442 -0
  76. package/framework/stacks/nextjs/feedback.md +124 -0
  77. package/framework/stacks/nextjs/migrations.md +332 -0
  78. package/framework/stacks/nextjs/models.md +362 -0
  79. package/framework/stacks/nextjs/queries.md +410 -0
  80. package/framework/stacks/nextjs/responsive.md +338 -0
  81. package/framework/stacks/nextjs/tech-stack.md +177 -0
  82. package/framework/stacks/nextjs/test-writing.md +475 -0
  83. package/framework/stacks/nextjs/validation.md +467 -0
  84. package/framework/stacks/python/api.md +468 -0
  85. package/framework/stacks/python/authentication.md +342 -0
  86. package/framework/stacks/python/code-quality.md +283 -0
  87. package/framework/stacks/python/code-refactoring.md +315 -0
  88. package/framework/stacks/python/coding-style.md +462 -0
  89. package/framework/stacks/python/conventions.md +399 -0
  90. package/framework/stacks/python/error-handling.md +512 -0
  91. package/framework/stacks/python/feedback.md +92 -0
  92. package/framework/stacks/python/implement-ai-llm.md +468 -0
  93. package/framework/stacks/python/migrations.md +388 -0
  94. package/framework/stacks/python/models.md +399 -0
  95. package/framework/stacks/python/python.md +232 -0
  96. package/framework/stacks/python/queries.md +451 -0
  97. package/framework/stacks/python/structure.md +245 -58
  98. package/framework/stacks/python/tech.md +92 -35
  99. package/framework/stacks/python/testing.md +380 -0
  100. package/framework/stacks/python/validation.md +471 -0
  101. package/framework/stacks/rails/authentication.md +176 -0
  102. package/framework/stacks/rails/code-quality.md +287 -0
  103. package/framework/stacks/rails/code-refactoring.md +299 -0
  104. package/framework/stacks/rails/feedback.md +130 -0
  105. package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
  106. package/framework/stacks/rails/rails.md +301 -0
  107. package/framework/stacks/rails/rails8-best-practices.md +498 -0
  108. package/framework/stacks/rails/rails8-css.md +573 -0
  109. package/framework/stacks/rails/structure.md +140 -0
  110. package/framework/stacks/rails/tech.md +108 -0
  111. package/framework/stacks/react/code-quality.md +521 -0
  112. package/framework/stacks/react/components.md +625 -0
  113. package/framework/stacks/react/data-fetching.md +586 -0
  114. package/framework/stacks/react/feedback.md +110 -0
  115. package/framework/stacks/react/forms.md +694 -0
  116. package/framework/stacks/react/performance.md +640 -0
  117. package/framework/stacks/react/product.md +22 -9
  118. package/framework/stacks/react/state-management.md +472 -0
  119. package/framework/stacks/react/structure.md +351 -44
  120. package/framework/stacks/react/tech.md +219 -30
  121. package/framework/stacks/react/testing.md +690 -0
  122. package/package.json +1 -1
  123. package/framework/stacks/node/product.md +0 -27
  124. package/framework/stacks/node/structure.md +0 -82
  125. package/framework/stacks/node/tech.md +0 -63
@@ -0,0 +1,431 @@
1
+ # API Design Standards
2
+
3
+ Next.js App Router API conventions for route handlers, server actions, request validation, and consistent response patterns.
4
+
5
+ ---
6
+
7
+ ## Philosophy
8
+
9
+ - **Server Actions first**: Use server actions for mutations; reserve route handlers for external/webhook APIs
10
+ - **Type-safe end-to-end**: Zod validates input, TypeScript types flow from server to client
11
+ - **Consistent responses**: Every route handler follows the same response envelope
12
+ - **Thin handlers**: Validation and response formatting in the handler, business logic in services
13
+
14
+ ---
15
+
16
+ ## Route Handler Conventions
17
+
18
+ ### File Structure
19
+
20
+ ```
21
+ app/
22
+ api/
23
+ health/
24
+ route.ts # GET /api/health
25
+ users/
26
+ route.ts # GET, POST /api/users
27
+ [id]/
28
+ route.ts # GET, PATCH, DELETE /api/users/:id
29
+ webhooks/
30
+ stripe/
31
+ route.ts # POST /api/webhooks/stripe
32
+ ```
33
+
34
+ ### Basic Route Handler
35
+
36
+ ```typescript
37
+ // app/api/users/route.ts
38
+ import { NextRequest, NextResponse } from "next/server";
39
+ import { z } from "zod";
40
+ import { prisma } from "@/lib/prisma";
41
+ import { auth } from "@/lib/auth";
42
+
43
+ const createUserSchema = z.object({
44
+ email: z.string().email(),
45
+ name: z.string().min(1).max(255),
46
+ });
47
+
48
+ export async function GET(request: NextRequest) {
49
+ const session = await auth();
50
+ if (!session) {
51
+ return NextResponse.json(
52
+ { error: { code: "UNAUTHORIZED", message: "Authentication required" } },
53
+ { status: 401 }
54
+ );
55
+ }
56
+
57
+ const { searchParams } = request.nextUrl;
58
+ const page = Number(searchParams.get("page") ?? "1");
59
+ const limit = Math.min(Number(searchParams.get("limit") ?? "20"), 100);
60
+
61
+ const [users, total] = await Promise.all([
62
+ prisma.user.findMany({
63
+ skip: (page - 1) * limit,
64
+ take: limit,
65
+ orderBy: { createdAt: "desc" },
66
+ select: { id: true, email: true, name: true, createdAt: true },
67
+ }),
68
+ prisma.user.count(),
69
+ ]);
70
+
71
+ return NextResponse.json({
72
+ items: users,
73
+ total,
74
+ page,
75
+ limit,
76
+ totalPages: Math.ceil(total / limit),
77
+ });
78
+ }
79
+
80
+ export async function POST(request: NextRequest) {
81
+ const session = await auth();
82
+ if (!session) {
83
+ return NextResponse.json(
84
+ { error: { code: "UNAUTHORIZED", message: "Authentication required" } },
85
+ { status: 401 }
86
+ );
87
+ }
88
+
89
+ const body = await request.json();
90
+ const parsed = createUserSchema.safeParse(body);
91
+
92
+ if (!parsed.success) {
93
+ return NextResponse.json(
94
+ { error: { code: "VALIDATION_ERROR", message: "Invalid input", details: parsed.error.flatten() } },
95
+ { status: 422 }
96
+ );
97
+ }
98
+
99
+ const user = await prisma.user.create({ data: parsed.data });
100
+ return NextResponse.json(user, { status: 201 });
101
+ }
102
+ ```
103
+
104
+ ---
105
+
106
+ ## HTTP Methods and Status Codes
107
+
108
+ ### RESTful Conventions
109
+
110
+ | Method | URL Pattern | Action | Status |
111
+ |---|---|---|---|
112
+ | `GET` | `/api/users` | List users | 200 |
113
+ | `GET` | `/api/users/42` | Get single user | 200 |
114
+ | `POST` | `/api/users` | Create user | 201 |
115
+ | `PATCH` | `/api/users/42` | Partial update | 200 |
116
+ | `DELETE` | `/api/users/42` | Delete user | 204 |
117
+
118
+ ### URL Naming Rules
119
+
120
+ ```
121
+ # GOOD
122
+ GET /api/users
123
+ POST /api/users
124
+ GET /api/users/42/posts
125
+
126
+ # BAD
127
+ GET /api/getUsers
128
+ POST /api/createUser
129
+ GET /api/user/42/getAllPosts
130
+ ```
131
+
132
+ - Plural nouns for resources: `/users`, `/posts`
133
+ - Lowercase with hyphens for multi-word: `/api-keys`, `/user-profiles`
134
+ - No verbs in URLs (HTTP methods convey action)
135
+ - No trailing slashes
136
+ - Maximum two levels of nesting
137
+
138
+ ---
139
+
140
+ ## Response Envelope
141
+
142
+ ### Success: Single Resource
143
+
144
+ ```json
145
+ {
146
+ "id": 42,
147
+ "email": "user@example.com",
148
+ "name": "Jane Doe",
149
+ "createdAt": "2024-01-15T10:30:00.000Z"
150
+ }
151
+ ```
152
+
153
+ ### Success: Collection
154
+
155
+ ```json
156
+ {
157
+ "items": [...],
158
+ "total": 142,
159
+ "page": 1,
160
+ "limit": 20,
161
+ "totalPages": 8
162
+ }
163
+ ```
164
+
165
+ ### Error Response
166
+
167
+ ```json
168
+ {
169
+ "error": {
170
+ "code": "NOT_FOUND",
171
+ "message": "User not found",
172
+ "details": { "resource": "User", "id": "42" }
173
+ }
174
+ }
175
+ ```
176
+
177
+ ### Standard Error Codes
178
+
179
+ | Code | HTTP Status | Meaning |
180
+ |---|---|---|
181
+ | `VALIDATION_ERROR` | 422 | Invalid request body or params |
182
+ | `UNAUTHORIZED` | 401 | Missing or invalid authentication |
183
+ | `FORBIDDEN` | 403 | Authenticated but insufficient permissions |
184
+ | `NOT_FOUND` | 404 | Resource does not exist |
185
+ | `CONFLICT` | 409 | Duplicate resource or state conflict |
186
+ | `RATE_LIMITED` | 429 | Too many requests |
187
+ | `INTERNAL_ERROR` | 500 | Unhandled server error |
188
+
189
+ ---
190
+
191
+ ## Server Actions vs Route Handlers
192
+
193
+ ### When to Use Each
194
+
195
+ | Use Case | Approach |
196
+ |---|---|
197
+ | Form submissions | Server Action |
198
+ | Data mutations from UI | Server Action |
199
+ | External API consumers | Route Handler |
200
+ | Webhooks | Route Handler |
201
+ | File uploads with progress | Route Handler |
202
+ | CORS-enabled endpoints | Route Handler |
203
+ | Streaming responses | Route Handler |
204
+
205
+ ### Server Action Pattern
206
+
207
+ ```typescript
208
+ // app/actions/users.ts
209
+ "use server";
210
+
211
+ import { z } from "zod";
212
+ import { auth } from "@/lib/auth";
213
+ import { prisma } from "@/lib/prisma";
214
+ import { revalidatePath } from "next/cache";
215
+
216
+ const updateProfileSchema = z.object({
217
+ name: z.string().min(1).max(255),
218
+ bio: z.string().max(500).optional(),
219
+ });
220
+
221
+ export async function updateProfile(formData: FormData) {
222
+ const session = await auth();
223
+ if (!session?.user?.id) {
224
+ return { error: "Unauthorized" };
225
+ }
226
+
227
+ const parsed = updateProfileSchema.safeParse({
228
+ name: formData.get("name"),
229
+ bio: formData.get("bio"),
230
+ });
231
+
232
+ if (!parsed.success) {
233
+ return { error: "Invalid input", fieldErrors: parsed.error.flatten().fieldErrors };
234
+ }
235
+
236
+ await prisma.user.update({
237
+ where: { id: session.user.id },
238
+ data: parsed.data,
239
+ });
240
+
241
+ revalidatePath("/profile");
242
+ return { success: true };
243
+ }
244
+ ```
245
+
246
+ ---
247
+
248
+ ## Middleware
249
+
250
+ ### Authentication Middleware
251
+
252
+ ```typescript
253
+ // middleware.ts
254
+ import { auth } from "@/lib/auth";
255
+ import { NextResponse } from "next/server";
256
+
257
+ export default auth((req) => {
258
+ const isApiRoute = req.nextUrl.pathname.startsWith("/api");
259
+ const isPublicApi = req.nextUrl.pathname.startsWith("/api/health") ||
260
+ req.nextUrl.pathname.startsWith("/api/webhooks");
261
+
262
+ if (isApiRoute && !isPublicApi && !req.auth) {
263
+ return NextResponse.json(
264
+ { error: { code: "UNAUTHORIZED", message: "Authentication required" } },
265
+ { status: 401 }
266
+ );
267
+ }
268
+
269
+ return NextResponse.next();
270
+ });
271
+
272
+ export const config = {
273
+ matcher: ["/api/:path*", "/dashboard/:path*"],
274
+ };
275
+ ```
276
+
277
+ ### CORS for Route Handlers
278
+
279
+ ```typescript
280
+ // app/api/public/route.ts
281
+ import { NextRequest, NextResponse } from "next/server";
282
+
283
+ const ALLOWED_ORIGINS = [
284
+ "https://example.com",
285
+ process.env.NODE_ENV === "development" && "http://localhost:3001",
286
+ ].filter(Boolean) as string[];
287
+
288
+ function corsHeaders(origin: string | null) {
289
+ const headers = new Headers();
290
+ if (origin && ALLOWED_ORIGINS.includes(origin)) {
291
+ headers.set("Access-Control-Allow-Origin", origin);
292
+ headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
293
+ headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
294
+ headers.set("Access-Control-Max-Age", "86400");
295
+ }
296
+ return headers;
297
+ }
298
+
299
+ export async function OPTIONS(request: NextRequest) {
300
+ return new NextResponse(null, {
301
+ status: 204,
302
+ headers: corsHeaders(request.headers.get("origin")),
303
+ });
304
+ }
305
+
306
+ export async function GET(request: NextRequest) {
307
+ const data = { message: "Hello" };
308
+ return NextResponse.json(data, {
309
+ headers: corsHeaders(request.headers.get("origin")),
310
+ });
311
+ }
312
+ ```
313
+
314
+ ---
315
+
316
+ ## Rate Limiting
317
+
318
+ ### Token Bucket with Headers
319
+
320
+ ```typescript
321
+ // lib/rate-limit.ts
322
+ const rateLimitMap = new Map<string, { tokens: number; lastRefill: number }>();
323
+
324
+ export function rateLimit(
325
+ key: string,
326
+ options: { limit: number; windowMs: number }
327
+ ): { success: boolean; remaining: number; reset: number } {
328
+ const now = Date.now();
329
+ const record = rateLimitMap.get(key) ?? { tokens: options.limit, lastRefill: now };
330
+
331
+ const elapsed = now - record.lastRefill;
332
+ const refillRate = options.limit / options.windowMs;
333
+ record.tokens = Math.min(options.limit, record.tokens + elapsed * refillRate);
334
+ record.lastRefill = now;
335
+
336
+ if (record.tokens < 1) {
337
+ rateLimitMap.set(key, record);
338
+ return { success: false, remaining: 0, reset: Math.ceil(options.windowMs / 1000) };
339
+ }
340
+
341
+ record.tokens -= 1;
342
+ rateLimitMap.set(key, record);
343
+ return { success: true, remaining: Math.floor(record.tokens), reset: Math.ceil(options.windowMs / 1000) };
344
+ }
345
+
346
+ // Usage in route handler
347
+ export async function POST(request: NextRequest) {
348
+ const ip = request.headers.get("x-forwarded-for") ?? "unknown";
349
+ const { success, remaining, reset } = rateLimit(ip, { limit: 10, windowMs: 60_000 });
350
+
351
+ if (!success) {
352
+ return NextResponse.json(
353
+ { error: { code: "RATE_LIMITED", message: "Too many requests" } },
354
+ {
355
+ status: 429,
356
+ headers: {
357
+ "X-RateLimit-Limit": "10",
358
+ "X-RateLimit-Remaining": String(remaining),
359
+ "X-RateLimit-Reset": String(reset),
360
+ "Retry-After": String(reset),
361
+ },
362
+ }
363
+ );
364
+ }
365
+
366
+ // ... handle request
367
+ }
368
+ ```
369
+
370
+ ---
371
+
372
+ ## Streaming Responses
373
+
374
+ ```typescript
375
+ // app/api/stream/route.ts
376
+ export async function GET() {
377
+ const encoder = new TextEncoder();
378
+
379
+ const stream = new ReadableStream({
380
+ async start(controller) {
381
+ for (const chunk of ["Hello", " ", "World"]) {
382
+ controller.enqueue(encoder.encode(chunk));
383
+ await new Promise((r) => setTimeout(r, 100));
384
+ }
385
+ controller.close();
386
+ },
387
+ });
388
+
389
+ return new Response(stream, {
390
+ headers: { "Content-Type": "text/plain; charset=utf-8" },
391
+ });
392
+ }
393
+ ```
394
+
395
+ ---
396
+
397
+ ## Health Check
398
+
399
+ ```typescript
400
+ // app/api/health/route.ts
401
+ import { prisma } from "@/lib/prisma";
402
+
403
+ export async function GET() {
404
+ try {
405
+ await prisma.$queryRaw`SELECT 1`;
406
+ return Response.json({ status: "ok", database: "connected" });
407
+ } catch {
408
+ return Response.json(
409
+ { status: "error", database: "disconnected" },
410
+ { status: 503 }
411
+ );
412
+ }
413
+ }
414
+ ```
415
+
416
+ ---
417
+
418
+ ## Anti-Patterns
419
+
420
+ | Anti-Pattern | Problem | Correct Approach |
421
+ |---|---|---|
422
+ | Verbs in URLs | Not RESTful | Use HTTP methods to convey action |
423
+ | Using route handlers for form submissions | Unnecessary complexity | Use server actions for UI mutations |
424
+ | No input validation | Security risk, bad data | Validate with zod in every handler |
425
+ | Business logic in route handlers | Hard to test, duplicated | Extract to service functions |
426
+ | Returning raw Prisma errors | Leaks schema details | Map to error envelope with safe messages |
427
+ | No rate limiting on auth endpoints | Brute force vulnerability | Rate limit login and signup endpoints |
428
+
429
+ ---
430
+
431
+ _APIs are contracts. Validate every input, standardize every output, and keep business logic out of handlers._
@@ -0,0 +1,282 @@
1
+ # Coding Style
2
+
3
+ TypeScript and React conventions for Next.js 15 App Router projects with strict type safety and consistent patterns.
4
+
5
+ ---
6
+
7
+ ## Philosophy
8
+
9
+ - **Strict TypeScript**: Enable all strict checks; `any` is a code smell, not a solution
10
+ - **Explicit over implicit**: Named exports, explicit return types on public functions, no magic
11
+ - **Consistency**: One way to do things, enforced by tooling, not willpower
12
+ - **Functional React**: Function components, hooks, composition over inheritance
13
+
14
+ ---
15
+
16
+ ## TypeScript Configuration
17
+
18
+ ### Strict Mode (Non-Negotiable)
19
+
20
+ ```jsonc
21
+ // tsconfig.json
22
+ {
23
+ "compilerOptions": {
24
+ "strict": true,
25
+ "noUncheckedIndexedAccess": true,
26
+ "noImplicitReturns": true,
27
+ "noFallthroughCasesInSwitch": true,
28
+ "exactOptionalPropertyTypes": true,
29
+ "forceConsistentCasingInFileNames": true,
30
+ "moduleResolution": "bundler",
31
+ "paths": {
32
+ "@/*": ["./src/*"]
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ ### Type vs Interface
39
+
40
+ | Use | When |
41
+ |---|---|
42
+ | `type` | Unions, intersections, mapped types, utility types |
43
+ | `interface` | Object shapes that may be extended, component props |
44
+
45
+ ```typescript
46
+ // Interface for props (extendable)
47
+ interface ButtonProps {
48
+ variant: "primary" | "secondary";
49
+ size?: "sm" | "md" | "lg";
50
+ children: React.ReactNode;
51
+ }
52
+
53
+ // Type for unions and utility types
54
+ type ApiResponse<T> = { data: T; error: null } | { data: null; error: string };
55
+ type UserRole = "admin" | "member" | "viewer";
56
+ ```
57
+
58
+ **Rule**: Be consistent within a file. When in doubt, use `interface` for props and `type` for everything else.
59
+
60
+ ---
61
+
62
+ ## Naming Conventions
63
+
64
+ | Entity | Convention | Example |
65
+ |---|---|---|
66
+ | Components | PascalCase | `UserProfile`, `DataTable` |
67
+ | Component files | PascalCase or kebab-case | `UserProfile.tsx` or `user-profile.tsx` |
68
+ | Hooks | camelCase with `use` prefix | `useAuth`, `useDebounce` |
69
+ | Utilities | camelCase | `formatDate`, `cn` |
70
+ | Constants | UPPER_SNAKE_CASE | `MAX_RETRIES`, `API_BASE_URL` |
71
+ | Types/Interfaces | PascalCase | `UserProfile`, `ApiResponse` |
72
+ | Enums | PascalCase (members too) | `UserRole.Admin` |
73
+ | Route files | lowercase (Next.js convention) | `page.tsx`, `layout.tsx`, `route.ts` |
74
+ | Server actions | camelCase verb phrases | `createUser`, `updateProfile` |
75
+ | Zod schemas | camelCase + Schema suffix | `createUserSchema`, `loginSchema` |
76
+ | Environment variables | UPPER_SNAKE_CASE | `DATABASE_URL`, `NEXT_PUBLIC_APP_NAME` |
77
+
78
+ ### Boolean Naming
79
+
80
+ ```typescript
81
+ // Prefix with is, has, can, should
82
+ const isLoading = true;
83
+ const hasPermission = user.role === "admin";
84
+ const canEdit = hasPermission && !isArchived;
85
+ const shouldRefetch = Date.now() - lastFetch > STALE_TIME;
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Function Components
91
+
92
+ ### Component Structure
93
+
94
+ ```typescript
95
+ // 1. Imports
96
+ import { type ComponentProps } from "react";
97
+ import { cn } from "@/lib/utils";
98
+
99
+ // 2. Types
100
+ interface CardProps {
101
+ title: string;
102
+ description?: string;
103
+ children: React.ReactNode;
104
+ className?: string;
105
+ }
106
+
107
+ // 3. Component (named export)
108
+ export function Card({ title, description, children, className }: CardProps) {
109
+ return (
110
+ <div className={cn("rounded-lg border p-6", className)}>
111
+ <h3 className="text-lg font-semibold">{title}</h3>
112
+ {description && <p className="text-muted-foreground mt-1">{description}</p>}
113
+ <div className="mt-4">{children}</div>
114
+ </div>
115
+ );
116
+ }
117
+ ```
118
+
119
+ ### Rules
120
+
121
+ - Named exports only (no `export default` except for `page.tsx`, `layout.tsx`, and other Next.js conventions)
122
+ - Props interface defined above the component
123
+ - Destructure props in the function signature
124
+ - No `React.FC` -- use plain function with typed props
125
+
126
+ ---
127
+
128
+ ## Import Organization
129
+
130
+ ### Order (Enforced by ESLint)
131
+
132
+ ```typescript
133
+ // 1. React / Next.js
134
+ import { useState, useCallback } from "react";
135
+ import { useRouter } from "next/navigation";
136
+ import Image from "next/image";
137
+
138
+ // 2. External libraries
139
+ import { z } from "zod";
140
+ import { useForm } from "react-hook-form";
141
+
142
+ // 3. Internal aliases (@/)
143
+ import { prisma } from "@/lib/prisma";
144
+ import { cn } from "@/lib/utils";
145
+ import { Button } from "@/components/ui/button";
146
+
147
+ // 4. Relative imports
148
+ import { UserAvatar } from "./user-avatar";
149
+
150
+ // 5. Types (type-only imports)
151
+ import type { User } from "@prisma/client";
152
+ ```
153
+
154
+ ### Barrel Exports
155
+
156
+ ```typescript
157
+ // components/ui/index.ts -- use sparingly
158
+ export { Button } from "./button";
159
+ export { Input } from "./input";
160
+ export { Card } from "./card";
161
+ ```
162
+
163
+ **Warning**: Barrel exports can hurt tree-shaking and dev server performance. Use them for UI component libraries only, not for feature modules.
164
+
165
+ ---
166
+
167
+ ## Hooks Patterns
168
+
169
+ ### Custom Hook Structure
170
+
171
+ ```typescript
172
+ // hooks/use-debounce.ts
173
+ import { useState, useEffect } from "react";
174
+
175
+ export function useDebounce<T>(value: T, delay: number): T {
176
+ const [debouncedValue, setDebouncedValue] = useState(value);
177
+
178
+ useEffect(() => {
179
+ const timer = setTimeout(() => setDebouncedValue(value), delay);
180
+ return () => clearTimeout(timer);
181
+ }, [value, delay]);
182
+
183
+ return debouncedValue;
184
+ }
185
+ ```
186
+
187
+ ### Hook Rules
188
+
189
+ - One hook per file, named after the hook
190
+ - Always return a stable API (avoid returning new object references)
191
+ - Prefer returning tuples for simple state: `[value, setValue]`
192
+ - Prefer returning objects for complex state: `{ data, error, isLoading }`
193
+
194
+ ---
195
+
196
+ ## ESLint and Prettier
197
+
198
+ ### ESLint Flat Config
199
+
200
+ ```javascript
201
+ // eslint.config.mjs
202
+ import { FlatCompat } from "@eslint/eslintrc";
203
+
204
+ const compat = new FlatCompat({ baseDirectory: import.meta.dirname });
205
+
206
+ export default [
207
+ ...compat.extends("next/core-web-vitals", "next/typescript"),
208
+ {
209
+ rules: {
210
+ "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
211
+ "@typescript-eslint/no-explicit-any": "error",
212
+ "prefer-const": "error",
213
+ "no-console": ["warn", { allow: ["warn", "error"] }],
214
+ },
215
+ },
216
+ ];
217
+ ```
218
+
219
+ ### Prettier Config
220
+
221
+ ```json
222
+ {
223
+ "semi": true,
224
+ "singleQuote": false,
225
+ "tabWidth": 2,
226
+ "trailingComma": "es5",
227
+ "printWidth": 100,
228
+ "plugins": ["prettier-plugin-tailwindcss"]
229
+ }
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Module Organization
235
+
236
+ ### Feature Modules
237
+
238
+ ```
239
+ src/
240
+ app/ # Next.js routes
241
+ components/
242
+ ui/ # Reusable UI primitives (Button, Input, Card)
243
+ forms/ # Form-specific components
244
+ layouts/ # Layout components (Sidebar, Header)
245
+ lib/
246
+ prisma.ts # Prisma client singleton
247
+ auth.ts # NextAuth configuration
248
+ utils.ts # General utilities (cn, formatDate)
249
+ hooks/ # Custom React hooks
250
+ actions/ # Server actions
251
+ types/ # Shared type definitions
252
+ ```
253
+
254
+ ### Avoid Deep Nesting
255
+
256
+ ```typescript
257
+ // GOOD: Flat imports with aliases
258
+ import { Button } from "@/components/ui/button";
259
+ import { prisma } from "@/lib/prisma";
260
+
261
+ // BAD: Deep relative paths
262
+ import { Button } from "../../../components/ui/button";
263
+ import { prisma } from "../../../lib/prisma";
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Anti-Patterns
269
+
270
+ | Anti-Pattern | Problem | Correct Approach |
271
+ |---|---|---|
272
+ | `any` type | Bypasses type checking entirely | Use `unknown` and narrow, or define proper types |
273
+ | `export default` everywhere | Inconsistent naming on import | Named exports except Next.js conventions |
274
+ | `React.FC` | Legacy, issues with generics | Plain function with typed props |
275
+ | Barrel exports for features | Breaks tree-shaking, slow HMR | Direct imports for feature modules |
276
+ | `as` type assertions | Unsafe, hides errors | Type guards and narrowing |
277
+ | Inline types in function signatures | Unreadable, not reusable | Define named types and interfaces |
278
+ | `// @ts-ignore` | Silences real errors | Fix the type, or use `// @ts-expect-error` with explanation |
279
+
280
+ ---
281
+
282
+ _TypeScript exists to catch bugs before they ship. Configure it strictly, trust the compiler, and never silence it without a comment explaining why._