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.
- package/README.md +1 -2
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +5 -0
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/components/init/CompleteStep.d.ts.map +1 -1
- package/dist/components/init/CompleteStep.js +2 -2
- package/dist/components/init/CompleteStep.js.map +1 -1
- package/dist/components/init/TestCheckStep.d.ts +16 -0
- package/dist/components/init/TestCheckStep.d.ts.map +1 -0
- package/dist/components/init/TestCheckStep.js +120 -0
- package/dist/components/init/TestCheckStep.js.map +1 -0
- package/dist/components/init/index.d.ts +1 -0
- package/dist/components/init/index.d.ts.map +1 -1
- package/dist/components/init/index.js +1 -0
- package/dist/components/init/index.js.map +1 -1
- package/dist/components/init/types.d.ts +9 -0
- package/dist/components/init/types.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.js +69 -6
- package/dist/components/screens/InitScreen.js.map +1 -1
- package/dist/components/screens/ListScreen.d.ts.map +1 -1
- package/dist/components/screens/ListScreen.js +28 -3
- package/dist/components/screens/ListScreen.js.map +1 -1
- package/dist/components/screens/StartScreen.d.ts.map +1 -1
- package/dist/components/screens/StartScreen.js +212 -13
- package/dist/components/screens/StartScreen.js.map +1 -1
- package/dist/components/ui/ArtifactsSidebar.d.ts +19 -0
- package/dist/components/ui/ArtifactsSidebar.d.ts.map +1 -0
- package/dist/components/ui/ArtifactsSidebar.js +51 -0
- package/dist/components/ui/ArtifactsSidebar.js.map +1 -0
- package/dist/components/ui/FeatureSidebar.d.ts.map +1 -1
- package/dist/components/ui/FeatureSidebar.js +1 -1
- package/dist/components/ui/FeatureSidebar.js.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +1 -0
- package/dist/components/ui/index.js.map +1 -1
- package/dist/services/ClaudeErrorDetector.js +3 -3
- package/dist/services/ClaudeErrorDetector.js.map +1 -1
- package/dist/services/ConfigService.d.ts +1 -0
- package/dist/services/ConfigService.d.ts.map +1 -1
- package/dist/services/ConfigService.js.map +1 -1
- package/dist/services/ProjectDetector.d.ts +28 -0
- package/dist/services/ProjectDetector.d.ts.map +1 -0
- package/dist/services/ProjectDetector.js +236 -0
- package/dist/services/ProjectDetector.js.map +1 -0
- package/dist/services/TestRunner.d.ts +46 -0
- package/dist/services/TestRunner.d.ts.map +1 -0
- package/dist/services/TestRunner.js +85 -0
- package/dist/services/TestRunner.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -1
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/framework/.red64/settings/templates/specs/gap-analysis.md +163 -0
- package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
- package/framework/agents/claude/.claude/agents/red64/validate-gap.md +13 -7
- package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
- package/framework/agents/claude/.claude/commands/red64/validate-gap.md +4 -0
- package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
- package/framework/agents/codex/.codex/agents/red64/validate-gap.md +13 -7
- package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
- package/framework/agents/codex/.codex/commands/red64/validate-gap.md +4 -0
- package/framework/stacks/generic/feedback.md +80 -0
- package/framework/stacks/nextjs/accessibility.md +437 -0
- package/framework/stacks/nextjs/api.md +431 -0
- package/framework/stacks/nextjs/coding-style.md +282 -0
- package/framework/stacks/nextjs/commenting.md +226 -0
- package/framework/stacks/nextjs/components.md +411 -0
- package/framework/stacks/nextjs/conventions.md +333 -0
- package/framework/stacks/nextjs/css.md +310 -0
- package/framework/stacks/nextjs/error-handling.md +442 -0
- package/framework/stacks/nextjs/feedback.md +124 -0
- package/framework/stacks/nextjs/migrations.md +332 -0
- package/framework/stacks/nextjs/models.md +362 -0
- package/framework/stacks/nextjs/queries.md +410 -0
- package/framework/stacks/nextjs/responsive.md +338 -0
- package/framework/stacks/nextjs/tech-stack.md +177 -0
- package/framework/stacks/nextjs/test-writing.md +475 -0
- package/framework/stacks/nextjs/validation.md +467 -0
- package/framework/stacks/python/api.md +468 -0
- package/framework/stacks/python/authentication.md +342 -0
- package/framework/stacks/python/code-quality.md +283 -0
- package/framework/stacks/python/code-refactoring.md +315 -0
- package/framework/stacks/python/coding-style.md +462 -0
- package/framework/stacks/python/conventions.md +399 -0
- package/framework/stacks/python/error-handling.md +512 -0
- package/framework/stacks/python/feedback.md +92 -0
- package/framework/stacks/python/implement-ai-llm.md +468 -0
- package/framework/stacks/python/migrations.md +388 -0
- package/framework/stacks/python/models.md +399 -0
- package/framework/stacks/python/python.md +232 -0
- package/framework/stacks/python/queries.md +451 -0
- package/framework/stacks/python/structure.md +245 -58
- package/framework/stacks/python/tech.md +92 -35
- package/framework/stacks/python/testing.md +380 -0
- package/framework/stacks/python/validation.md +471 -0
- package/framework/stacks/rails/authentication.md +176 -0
- package/framework/stacks/rails/code-quality.md +287 -0
- package/framework/stacks/rails/code-refactoring.md +299 -0
- package/framework/stacks/rails/feedback.md +130 -0
- package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
- package/framework/stacks/rails/rails.md +301 -0
- package/framework/stacks/rails/rails8-best-practices.md +498 -0
- package/framework/stacks/rails/rails8-css.md +573 -0
- package/framework/stacks/rails/structure.md +140 -0
- package/framework/stacks/rails/tech.md +108 -0
- package/framework/stacks/react/code-quality.md +521 -0
- package/framework/stacks/react/components.md +625 -0
- package/framework/stacks/react/data-fetching.md +586 -0
- package/framework/stacks/react/feedback.md +110 -0
- package/framework/stacks/react/forms.md +694 -0
- package/framework/stacks/react/performance.md +640 -0
- package/framework/stacks/react/product.md +22 -9
- package/framework/stacks/react/state-management.md +472 -0
- package/framework/stacks/react/structure.md +351 -44
- package/framework/stacks/react/tech.md +219 -30
- package/framework/stacks/react/testing.md +690 -0
- package/package.json +1 -1
- package/framework/stacks/node/product.md +0 -27
- package/framework/stacks/node/structure.md +0 -82
- package/framework/stacks/node/tech.md +0 -63
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
Comprehensive error handling patterns for Next.js 15 App Router with error boundaries, server actions, API routes, and monitoring.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Philosophy
|
|
8
|
+
|
|
9
|
+
- **Fail visibly**: Never swallow errors silently; always surface them to the user or monitoring
|
|
10
|
+
- **Boundaries everywhere**: Every route segment gets an `error.tsx` for graceful degradation
|
|
11
|
+
- **Typed errors**: Use custom error classes to distinguish expected from unexpected failures
|
|
12
|
+
- **User-friendly messages**: Show actionable text to users, log technical details for developers
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Error Boundary Hierarchy
|
|
17
|
+
|
|
18
|
+
### Route-Level Error Boundary
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// app/dashboard/error.tsx
|
|
22
|
+
"use client";
|
|
23
|
+
|
|
24
|
+
import { useEffect } from "react";
|
|
25
|
+
import { Button } from "@/components/ui/button";
|
|
26
|
+
|
|
27
|
+
interface ErrorProps {
|
|
28
|
+
error: Error & { digest?: string };
|
|
29
|
+
reset: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default function DashboardError({ error, reset }: ErrorProps) {
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
// Log to error monitoring service
|
|
35
|
+
console.error("Dashboard error:", error);
|
|
36
|
+
}, [error]);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="flex flex-col items-center justify-center gap-4 py-16" role="alert">
|
|
40
|
+
<h2 className="text-xl font-semibold">Something went wrong</h2>
|
|
41
|
+
<p className="text-muted-foreground max-w-md text-center">
|
|
42
|
+
We encountered an error loading the dashboard. This has been reported automatically.
|
|
43
|
+
</p>
|
|
44
|
+
<div className="flex gap-2">
|
|
45
|
+
<Button onClick={reset}>Try again</Button>
|
|
46
|
+
<Button variant="outline" onClick={() => window.location.reload()}>
|
|
47
|
+
Reload page
|
|
48
|
+
</Button>
|
|
49
|
+
</div>
|
|
50
|
+
{process.env.NODE_ENV === "development" && (
|
|
51
|
+
<pre className="mt-4 max-w-lg overflow-auto rounded bg-red-50 p-4 text-sm text-red-900">
|
|
52
|
+
{error.message}
|
|
53
|
+
{"\n"}
|
|
54
|
+
{error.stack}
|
|
55
|
+
</pre>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Global Error Boundary
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// app/global-error.tsx
|
|
66
|
+
"use client";
|
|
67
|
+
|
|
68
|
+
export default function GlobalError({
|
|
69
|
+
error,
|
|
70
|
+
reset,
|
|
71
|
+
}: {
|
|
72
|
+
error: Error & { digest?: string };
|
|
73
|
+
reset: () => void;
|
|
74
|
+
}) {
|
|
75
|
+
return (
|
|
76
|
+
<html>
|
|
77
|
+
<body>
|
|
78
|
+
<div className="flex min-h-screen items-center justify-center p-4">
|
|
79
|
+
<div className="text-center">
|
|
80
|
+
<h1 className="text-2xl font-bold">Application Error</h1>
|
|
81
|
+
<p className="text-muted-foreground mt-2">
|
|
82
|
+
Something went wrong. Please try refreshing the page.
|
|
83
|
+
</p>
|
|
84
|
+
<button
|
|
85
|
+
onClick={reset}
|
|
86
|
+
className="mt-4 rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
|
|
87
|
+
>
|
|
88
|
+
Try again
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</body>
|
|
93
|
+
</html>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Not Found
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// app/not-found.tsx
|
|
102
|
+
import Link from "next/link";
|
|
103
|
+
|
|
104
|
+
export default function NotFound() {
|
|
105
|
+
return (
|
|
106
|
+
<div className="flex min-h-[60vh] flex-col items-center justify-center gap-4">
|
|
107
|
+
<h1 className="text-4xl font-bold">404</h1>
|
|
108
|
+
<p className="text-muted-foreground text-lg">Page not found</p>
|
|
109
|
+
<Link href="/" className="text-primary underline underline-offset-4">
|
|
110
|
+
Go back home
|
|
111
|
+
</Link>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Custom Error Classes
|
|
120
|
+
|
|
121
|
+
### Error Hierarchy
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// lib/errors.ts
|
|
125
|
+
export class AppError extends Error {
|
|
126
|
+
constructor(
|
|
127
|
+
message: string,
|
|
128
|
+
public code: string,
|
|
129
|
+
public statusCode: number = 500,
|
|
130
|
+
public details?: Record<string, unknown>
|
|
131
|
+
) {
|
|
132
|
+
super(message);
|
|
133
|
+
this.name = "AppError";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export class NotFoundError extends AppError {
|
|
138
|
+
constructor(resource: string, id: string | number) {
|
|
139
|
+
super(`${resource} not found: ${id}`, "NOT_FOUND", 404, { resource, id });
|
|
140
|
+
this.name = "NotFoundError";
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export class ValidationError extends AppError {
|
|
145
|
+
constructor(message: string, public fieldErrors?: Record<string, string[]>) {
|
|
146
|
+
super(message, "VALIDATION_ERROR", 422, { fieldErrors });
|
|
147
|
+
this.name = "ValidationError";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export class UnauthorizedError extends AppError {
|
|
152
|
+
constructor(message = "Authentication required") {
|
|
153
|
+
super(message, "UNAUTHORIZED", 401);
|
|
154
|
+
this.name = "UnauthorizedError";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export class ForbiddenError extends AppError {
|
|
159
|
+
constructor(message = "Insufficient permissions") {
|
|
160
|
+
super(message, "FORBIDDEN", 403);
|
|
161
|
+
this.name = "ForbiddenError";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export class ConflictError extends AppError {
|
|
166
|
+
constructor(message: string) {
|
|
167
|
+
super(message, "CONFLICT", 409);
|
|
168
|
+
this.name = "ConflictError";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Server Component Error Handling
|
|
176
|
+
|
|
177
|
+
### Try/Catch in Async Components
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// app/users/[id]/page.tsx
|
|
181
|
+
import { notFound } from "next/navigation";
|
|
182
|
+
import { prisma } from "@/lib/prisma";
|
|
183
|
+
|
|
184
|
+
export default async function UserPage({ params }: { params: Promise<{ id: string }> }) {
|
|
185
|
+
const { id } = await params;
|
|
186
|
+
|
|
187
|
+
const user = await prisma.user.findUnique({
|
|
188
|
+
where: { id },
|
|
189
|
+
include: { posts: { take: 10, orderBy: { createdAt: "desc" } } },
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (!user) {
|
|
193
|
+
notFound();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return <UserProfile user={user} />;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Service-Level Error Handling
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// lib/services/user-service.ts
|
|
204
|
+
import { NotFoundError, ConflictError } from "@/lib/errors";
|
|
205
|
+
import { prisma } from "@/lib/prisma";
|
|
206
|
+
|
|
207
|
+
export async function getUserById(id: string) {
|
|
208
|
+
const user = await prisma.user.findUnique({ where: { id } });
|
|
209
|
+
if (!user) {
|
|
210
|
+
throw new NotFoundError("User", id);
|
|
211
|
+
}
|
|
212
|
+
return user;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function createUser(data: { email: string; name: string }) {
|
|
216
|
+
const existing = await prisma.user.findUnique({ where: { email: data.email } });
|
|
217
|
+
if (existing) {
|
|
218
|
+
throw new ConflictError(`User with email ${data.email} already exists`);
|
|
219
|
+
}
|
|
220
|
+
return prisma.user.create({ data });
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Server Action Error Handling
|
|
227
|
+
|
|
228
|
+
### Action Result Pattern
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// types/actions.ts
|
|
232
|
+
export type ActionResult<T = void> =
|
|
233
|
+
| { success: true; data: T }
|
|
234
|
+
| { success: false; error: string; fieldErrors?: Record<string, string[]> };
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Safe Action Wrapper
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// lib/safe-action.ts
|
|
241
|
+
import { AppError, ValidationError } from "@/lib/errors";
|
|
242
|
+
import type { ActionResult } from "@/types/actions";
|
|
243
|
+
|
|
244
|
+
export async function safeAction<T>(
|
|
245
|
+
fn: () => Promise<T>
|
|
246
|
+
): Promise<ActionResult<T>> {
|
|
247
|
+
try {
|
|
248
|
+
const data = await fn();
|
|
249
|
+
return { success: true, data };
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (error instanceof ValidationError) {
|
|
252
|
+
return {
|
|
253
|
+
success: false,
|
|
254
|
+
error: error.message,
|
|
255
|
+
fieldErrors: error.fieldErrors,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
if (error instanceof AppError) {
|
|
259
|
+
return { success: false, error: error.message };
|
|
260
|
+
}
|
|
261
|
+
console.error("Unexpected error in action:", error);
|
|
262
|
+
return { success: false, error: "An unexpected error occurred" };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Usage in Server Actions
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// actions/users.ts
|
|
271
|
+
"use server";
|
|
272
|
+
|
|
273
|
+
import { z } from "zod";
|
|
274
|
+
import { auth } from "@/lib/auth";
|
|
275
|
+
import { safeAction } from "@/lib/safe-action";
|
|
276
|
+
import { createUser } from "@/lib/services/user-service";
|
|
277
|
+
import { revalidatePath } from "next/cache";
|
|
278
|
+
|
|
279
|
+
const createUserSchema = z.object({
|
|
280
|
+
email: z.string().email("Invalid email address"),
|
|
281
|
+
name: z.string().min(1, "Name is required").max(255),
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
export async function createUserAction(formData: FormData) {
|
|
285
|
+
return safeAction(async () => {
|
|
286
|
+
const session = await auth();
|
|
287
|
+
if (!session?.user) throw new UnauthorizedError();
|
|
288
|
+
|
|
289
|
+
const parsed = createUserSchema.safeParse({
|
|
290
|
+
email: formData.get("email"),
|
|
291
|
+
name: formData.get("name"),
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
if (!parsed.success) {
|
|
295
|
+
throw new ValidationError("Invalid input", parsed.error.flatten().fieldErrors);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const user = await createUser(parsed.data);
|
|
299
|
+
revalidatePath("/users");
|
|
300
|
+
return user;
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## API Route Error Handling
|
|
308
|
+
|
|
309
|
+
### Centralized Error Response
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// lib/api-error.ts
|
|
313
|
+
import { NextResponse } from "next/server";
|
|
314
|
+
import { AppError } from "@/lib/errors";
|
|
315
|
+
|
|
316
|
+
export function handleApiError(error: unknown) {
|
|
317
|
+
if (error instanceof AppError) {
|
|
318
|
+
return NextResponse.json(
|
|
319
|
+
{ error: { code: error.code, message: error.message, details: error.details } },
|
|
320
|
+
{ status: error.statusCode }
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
console.error("Unhandled API error:", error);
|
|
325
|
+
return NextResponse.json(
|
|
326
|
+
{ error: { code: "INTERNAL_ERROR", message: "An internal error occurred" } },
|
|
327
|
+
{ status: 500 }
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Usage
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
// app/api/users/[id]/route.ts
|
|
336
|
+
import { handleApiError } from "@/lib/api-error";
|
|
337
|
+
import { getUserById } from "@/lib/services/user-service";
|
|
338
|
+
|
|
339
|
+
export async function GET(
|
|
340
|
+
_request: Request,
|
|
341
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
342
|
+
) {
|
|
343
|
+
try {
|
|
344
|
+
const { id } = await params;
|
|
345
|
+
const user = await getUserById(id);
|
|
346
|
+
return Response.json(user);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
return handleApiError(error);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Client-Side Error Handling
|
|
356
|
+
|
|
357
|
+
### Toast Notifications
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
// components/forms/create-user-form.tsx
|
|
361
|
+
"use client";
|
|
362
|
+
|
|
363
|
+
import { useActionState } from "react";
|
|
364
|
+
import { createUserAction } from "@/actions/users";
|
|
365
|
+
import { toast } from "sonner";
|
|
366
|
+
|
|
367
|
+
export function CreateUserForm() {
|
|
368
|
+
const [state, formAction, isPending] = useActionState(
|
|
369
|
+
async (_prev: unknown, formData: FormData) => {
|
|
370
|
+
const result = await createUserAction(formData);
|
|
371
|
+
if (result.success) {
|
|
372
|
+
toast.success("User created successfully");
|
|
373
|
+
} else {
|
|
374
|
+
toast.error(result.error);
|
|
375
|
+
}
|
|
376
|
+
return result;
|
|
377
|
+
},
|
|
378
|
+
null
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
<form action={formAction}>
|
|
383
|
+
<input name="email" type="email" required />
|
|
384
|
+
{state && !state.success && state.fieldErrors?.email && (
|
|
385
|
+
<p className="text-sm text-destructive">{state.fieldErrors.email[0]}</p>
|
|
386
|
+
)}
|
|
387
|
+
<input name="name" required />
|
|
388
|
+
<button type="submit" disabled={isPending}>
|
|
389
|
+
{isPending ? "Creating..." : "Create User"}
|
|
390
|
+
</button>
|
|
391
|
+
</form>
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Error Monitoring
|
|
399
|
+
|
|
400
|
+
### Sentry Integration
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
// lib/sentry.ts
|
|
404
|
+
import * as Sentry from "@sentry/nextjs";
|
|
405
|
+
|
|
406
|
+
export function captureError(error: unknown, context?: Record<string, unknown>) {
|
|
407
|
+
if (error instanceof AppError && error.statusCode < 500) {
|
|
408
|
+
// Expected errors (4xx) - don't send to Sentry
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
Sentry.captureException(error, { extra: context });
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// instrumentation.ts (Next.js instrumentation hook)
|
|
417
|
+
export async function register() {
|
|
418
|
+
if (process.env.NEXT_RUNTIME === "nodejs") {
|
|
419
|
+
await import("./sentry.server.config");
|
|
420
|
+
}
|
|
421
|
+
if (process.env.NEXT_RUNTIME === "edge") {
|
|
422
|
+
await import("./sentry.edge.config");
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## Anti-Patterns
|
|
430
|
+
|
|
431
|
+
| Anti-Pattern | Problem | Correct Approach |
|
|
432
|
+
|---|---|---|
|
|
433
|
+
| Empty catch blocks | Errors disappear silently | Log and re-throw or return error state |
|
|
434
|
+
| `try/catch` around every line | Unreadable, catch at boundaries | Catch at route/action level, let errors propagate |
|
|
435
|
+
| Showing stack traces to users | Security risk, bad UX | User-friendly message + log details server-side |
|
|
436
|
+
| Using `Error` for expected cases | Cannot distinguish expected vs unexpected | Custom error classes with codes |
|
|
437
|
+
| No `error.tsx` in route segments | White screen on failure | Add error boundaries to all route segments |
|
|
438
|
+
| Catching `redirect()` or `notFound()` | These are thrown intentionally by Next.js | Never catch Next.js navigation functions |
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
_Errors are inevitable. Handle them at boundaries, classify them with types, and always tell the user what happened and what they can do about it._
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Feedback Configuration
|
|
2
|
+
|
|
3
|
+
Project-specific commands for automated feedback during Next.js implementation.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Test Commands
|
|
8
|
+
|
|
9
|
+
Commands to run tests during implementation. The agent will use these to verify code changes.
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
# Primary test command (REQUIRED)
|
|
13
|
+
test: pnpm test
|
|
14
|
+
|
|
15
|
+
# Test with coverage report
|
|
16
|
+
test_coverage: pnpm test -- --coverage
|
|
17
|
+
|
|
18
|
+
# Run specific test file (use {file} as placeholder)
|
|
19
|
+
test_file: pnpm test {file}
|
|
20
|
+
|
|
21
|
+
# Watch mode (for development)
|
|
22
|
+
test_watch: pnpm test -- --watch
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Linting Commands
|
|
28
|
+
|
|
29
|
+
Commands for code quality checks.
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
# Primary lint command (Next.js built-in)
|
|
33
|
+
lint: pnpm lint
|
|
34
|
+
|
|
35
|
+
# Lint with auto-fix
|
|
36
|
+
lint_fix: pnpm lint --fix
|
|
37
|
+
|
|
38
|
+
# Type checking
|
|
39
|
+
type_check: pnpm tsc --noEmit
|
|
40
|
+
|
|
41
|
+
# Format check
|
|
42
|
+
format_check: pnpm prettier --check .
|
|
43
|
+
|
|
44
|
+
# Format fix
|
|
45
|
+
format_fix: pnpm prettier --write .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Development Server
|
|
51
|
+
|
|
52
|
+
Commands for starting the development server (required for UI verification).
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
# Start dev server (Next.js)
|
|
56
|
+
dev_server: pnpm dev
|
|
57
|
+
|
|
58
|
+
# Dev server port
|
|
59
|
+
dev_port: 3000
|
|
60
|
+
|
|
61
|
+
# Dev server base URL
|
|
62
|
+
dev_url: http://localhost:3000
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Build Commands
|
|
68
|
+
|
|
69
|
+
Commands for production builds.
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
# Production build
|
|
73
|
+
build: pnpm build
|
|
74
|
+
|
|
75
|
+
# Start production server
|
|
76
|
+
start: pnpm start
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## UI Verification
|
|
82
|
+
|
|
83
|
+
Settings for agent-browser UI verification.
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
# Enable UI verification for this project
|
|
87
|
+
ui_verification_enabled: true
|
|
88
|
+
|
|
89
|
+
# Default wait time after navigation (milliseconds)
|
|
90
|
+
navigation_wait: 3000
|
|
91
|
+
|
|
92
|
+
# Screenshot directory
|
|
93
|
+
screenshot_dir: /tmp/ui-captures
|
|
94
|
+
|
|
95
|
+
# Common routes to verify
|
|
96
|
+
routes:
|
|
97
|
+
- /
|
|
98
|
+
- /api/health
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## E2E Testing
|
|
104
|
+
|
|
105
|
+
End-to-end testing with Playwright.
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
# Run E2E tests
|
|
109
|
+
e2e: pnpm test:e2e
|
|
110
|
+
|
|
111
|
+
# Run E2E with UI
|
|
112
|
+
e2e_ui: pnpm test:e2e --ui
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Notes
|
|
118
|
+
|
|
119
|
+
- Uses Jest or Vitest for unit tests (check package.json)
|
|
120
|
+
- Uses Playwright for E2E testing
|
|
121
|
+
- Next.js dev server on port 3000 by default
|
|
122
|
+
- Built-in ESLint configuration via `next lint`
|
|
123
|
+
- UI verification is enabled by default for Next.js projects
|
|
124
|
+
- API routes available at `/api/*`
|