rex-claude 6.0.0 → 6.2.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/dist/index.js CHANGED
@@ -638,7 +638,7 @@ ${COLORS.bold}${projects.length} projects found${COLORS.reset}
638
638
  break;
639
639
  }
640
640
  case "preload": {
641
- const { preload } = await import("./preload-I3MYBVNU.js");
641
+ const { preload } = await import("./preload-OJJO66IG.js");
642
642
  const cwd = process.argv[3] || process.cwd();
643
643
  const context = await preload(cwd);
644
644
  if (context) console.log(context);
@@ -14,9 +14,50 @@ import "./chunk-PDX44BCA.js";
14
14
  // src/preload.ts
15
15
  import Database from "better-sqlite3";
16
16
  import * as sqliteVec from "sqlite-vec";
17
- import { existsSync } from "fs";
17
+ import { existsSync, readFileSync } from "fs";
18
+ import { join } from "path";
18
19
  var log = createLogger("preload");
19
- var MAX_TOKENS = 200;
20
+ var MAX_TOKENS = 300;
21
+ var SKILL_RULES = [
22
+ // Frontend frameworks → UI/UX skills
23
+ { deps: ["next", "react", "vue", "nuxt", "@angular/core"], skills: ["ux-flow", "ui-craft", "ui-review"] },
24
+ // API layers → API design
25
+ { deps: ["express", "fastify", "hono", "koa", "@hapi/hapi"], skills: ["api-design", "error-handling"] },
26
+ // Database ORMs → DB design
27
+ { deps: ["drizzle-orm", "prisma", "typeorm", "mongoose", "sequelize", "knex"], skills: ["db-design"] },
28
+ // Auth libraries → auth patterns
29
+ { deps: ["next-auth", "lucia", "passport", "jose", "jsonwebtoken", "@auth/core"], skills: ["auth-patterns"] },
30
+ // i18n
31
+ { deps: ["next-intl", "i18next", "react-i18next", "vue-i18n"], skills: ["i18n"] },
32
+ // Testing
33
+ { deps: ["vitest", "jest", "@testing-library/react", "playwright", "cypress"], skills: ["test-strategy"] },
34
+ // Next.js specifically → SEO worth mentioning
35
+ { deps: ["next"], skills: ["seo", "perf"] },
36
+ // Any project → performance
37
+ { deps: ["react", "vue", "angular"], skills: ["perf"] }
38
+ ];
39
+ function detectRelevantSkills(projectRoot) {
40
+ const pkgPath = join(projectRoot, "package.json");
41
+ if (!existsSync(pkgPath)) return [];
42
+ let pkg = {};
43
+ try {
44
+ pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
45
+ } catch {
46
+ return [];
47
+ }
48
+ const allDeps = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
49
+ const suggested = /* @__PURE__ */ new Set();
50
+ for (const rule of SKILL_RULES) {
51
+ if (rule.deps && rule.deps.some((d) => allDeps.includes(d))) {
52
+ rule.skills.forEach((s) => suggested.add(s));
53
+ }
54
+ if (rule.files) {
55
+ const matched = rule.files.some((f) => existsSync(join(projectRoot, f)));
56
+ if (matched) rule.skills.forEach((s) => suggested.add(s));
57
+ }
58
+ }
59
+ return [...suggested];
60
+ }
20
61
  async function preload(cwd) {
21
62
  if (!existsSync(MEMORY_DB_PATH)) {
22
63
  log.debug("No memory DB found, skipping preload");
@@ -66,6 +107,12 @@ async function preload(cwd) {
66
107
  } finally {
67
108
  db.close();
68
109
  }
110
+ if (project?.path) {
111
+ const skills = detectRelevantSkills(project.path);
112
+ if (skills.length > 0) {
113
+ sections.push(`Skills: ${skills.join(", ")}`);
114
+ }
115
+ }
69
116
  const output = sections.join("\n");
70
117
  log.info(`Preloaded ${sections.length} sections for ${project?.name || cwd} (${output.length} chars)`);
71
118
  if (output.length > MAX_TOKENS * 4) {
@@ -0,0 +1,134 @@
1
+ ---
2
+ name: api-design
3
+ description: REST API design. Enforces consistent endpoints, response envelopes, pagination, error codes, and versioning. Use before building any new endpoint or reviewing an existing API surface.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # API Design
8
+
9
+ Consistency is the only thing that matters in an API. One inconsistent endpoint breaks trust in all of them.
10
+
11
+ ## URL conventions
12
+
13
+ ```
14
+ GET /api/v1/users # list (always paginated)
15
+ GET /api/v1/users/:id # single resource
16
+ POST /api/v1/users # create
17
+ PATCH /api/v1/users/:id # partial update
18
+ DELETE /api/v1/users/:id # delete
19
+
20
+ # Nested resources (max 2 levels deep)
21
+ GET /api/v1/users/:id/orders
22
+ POST /api/v1/users/:id/orders
23
+
24
+ # Actions (when REST doesn't fit)
25
+ POST /api/v1/users/:id/activate
26
+ POST /api/v1/invoices/:id/send
27
+ ```
28
+
29
+ - Always plural nouns, never verbs in URL
30
+ - kebab-case for multi-word: `/order-items` not `/orderItems`
31
+ - Version prefix: `/api/v1/` — bump to v2 only for breaking changes
32
+
33
+ ## Response envelope
34
+
35
+ **Every response** uses this shape:
36
+
37
+ ```typescript
38
+ // Success
39
+ {
40
+ "data": { ... } | [...],
41
+ "meta": {
42
+ "total": 150, // always on lists
43
+ "limit": 20,
44
+ "offset": 0
45
+ }
46
+ }
47
+
48
+ // Error
49
+ {
50
+ "data": null,
51
+ "error": {
52
+ "code": "VALIDATION_ERROR", // machine-readable, SCREAMING_SNAKE
53
+ "message": "Email is required", // human-readable, end-user safe
54
+ "field": "email" // optional, for field-level errors
55
+ }
56
+ }
57
+ ```
58
+
59
+ Never return raw arrays at the top level. Never return different shapes for the same endpoint.
60
+
61
+ ## Pagination (mandatory on all lists)
62
+
63
+ ```
64
+ GET /api/v1/orders?limit=20&offset=0
65
+ GET /api/v1/orders?limit=20&offset=20
66
+
67
+ // Response
68
+ {
69
+ "data": [...],
70
+ "meta": { "total": 847, "limit": 20, "offset": 0 }
71
+ }
72
+ ```
73
+
74
+ - Default limit: 20. Max limit: 100 (enforce server-side).
75
+ - Never return unbounded lists. Always paginate.
76
+ - Frontend shows `total` from meta, not `data.length`.
77
+
78
+ ## HTTP status codes
79
+
80
+ | Code | When |
81
+ |------|------|
82
+ | 200 | Success (GET, PATCH) |
83
+ | 201 | Created (POST) — include `Location` header |
84
+ | 204 | Deleted (DELETE) — no body |
85
+ | 400 | Bad request (malformed JSON, invalid params) |
86
+ | 401 | Not authenticated (missing/expired token) |
87
+ | 403 | Authenticated but not authorized |
88
+ | 404 | Resource not found |
89
+ | 409 | Conflict (duplicate email, concurrent update) |
90
+ | 422 | Validation error (valid JSON but business rule violated) |
91
+ | 429 | Rate limited — include `Retry-After` header |
92
+ | 500 | Server error — log internally, never expose stack trace |
93
+
94
+ ## Error codes
95
+
96
+ Use machine-readable `code` values the frontend can switch on:
97
+
98
+ ```
99
+ VALIDATION_ERROR — field validation failed
100
+ NOT_FOUND — resource doesn't exist
101
+ UNAUTHORIZED — not logged in
102
+ FORBIDDEN — logged in but no permission
103
+ DUPLICATE — unique constraint violation
104
+ RATE_LIMITED — too many requests
105
+ INTERNAL_ERROR — catch-all for 500s
106
+ ```
107
+
108
+ ## Validation
109
+
110
+ - Validate at the boundary — never trust client data inside business logic
111
+ - Return ALL validation errors at once, not one by one
112
+ - Use `422` + `field` in error for form validation, `400` for structural issues
113
+
114
+ ## Rate limiting headers
115
+
116
+ Always include on authenticated endpoints:
117
+
118
+ ```
119
+ X-RateLimit-Limit: 100
120
+ X-RateLimit-Remaining: 42
121
+ X-RateLimit-Reset: 1735689600 # Unix timestamp
122
+ Retry-After: 30 # seconds, on 429
123
+ ```
124
+
125
+ ## Checklist before shipping an endpoint
126
+
127
+ - [ ] URL follows conventions (plural, kebab, versioned)
128
+ - [ ] Response uses the standard envelope
129
+ - [ ] List endpoint is paginated (limit+offset+total)
130
+ - [ ] All error cases return correct status + error code
131
+ - [ ] Validation errors return field-level details
132
+ - [ ] No secrets or internal paths in responses
133
+ - [ ] Auth required where needed (don't forget!)
134
+ - [ ] Rate limiting on sensitive endpoints (auth, email send)
@@ -0,0 +1,145 @@
1
+ ---
2
+ name: auth-patterns
3
+ description: Authentication and authorization patterns. JWT, sessions, OAuth, RBAC, route protection. Use when implementing login, protected routes, or permission systems.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Auth Patterns
8
+
9
+ Auth bugs are security bugs. Every decision here has a security consequence.
10
+
11
+ ## Token storage
12
+
13
+ | Method | XSS safe | CSRF safe | Use for |
14
+ |--------|----------|-----------|---------|
15
+ | `httpOnly` cookie | ✅ | ❌ (need CSRF token) | Sessions, refresh tokens |
16
+ | Memory (React state) | ✅ | ✅ | Access tokens (short-lived) |
17
+ | `localStorage` | ❌ | ✅ | Never for auth tokens |
18
+ | `sessionStorage` | ❌ | ✅ | Never for auth tokens |
19
+
20
+ **Rule:** Access token in memory. Refresh token in `httpOnly` `Secure` `SameSite=Strict` cookie.
21
+
22
+ ## JWT structure
23
+
24
+ ```ts
25
+ // Access token: short-lived, stateless
26
+ const accessToken = jwt.sign(
27
+ { sub: user.id, role: user.role, email: user.email },
28
+ ACCESS_TOKEN_SECRET,
29
+ { expiresIn: '15m' } // never more than 1h
30
+ )
31
+
32
+ // Refresh token: long-lived, stored in DB for revocation
33
+ const refreshToken = jwt.sign(
34
+ { sub: user.id, jti: crypto.randomUUID() }, // jti = unique ID for revocation
35
+ REFRESH_TOKEN_SECRET,
36
+ { expiresIn: '30d' }
37
+ )
38
+ // Store hashed refresh token in DB
39
+ await db.refreshTokens.insert({ userId: user.id, tokenHash: hash(refreshToken) })
40
+ ```
41
+
42
+ **Never:** embed sensitive data in JWT (passwords, full profile, card numbers).
43
+ **Always:** verify on every request, don't trust payload without signature check.
44
+
45
+ ## Route protection (Next.js App Router)
46
+
47
+ ```ts
48
+ // middleware.ts — runs on every request before page render
49
+ import { NextResponse } from 'next/server'
50
+ import { verifyToken } from '@/lib/auth'
51
+
52
+ export async function middleware(request: NextRequest) {
53
+ const token = request.cookies.get('access_token')?.value
54
+
55
+ if (!token) {
56
+ return NextResponse.redirect(new URL('/login', request.url))
57
+ }
58
+
59
+ const payload = await verifyToken(token)
60
+ if (!payload) {
61
+ return NextResponse.redirect(new URL('/login', request.url))
62
+ }
63
+
64
+ return NextResponse.next()
65
+ }
66
+
67
+ export const config = {
68
+ matcher: ['/dashboard/:path*', '/settings/:path*', '/api/v1/:path*'],
69
+ }
70
+ ```
71
+
72
+ **Never** protect routes only client-side — always enforce on server/middleware.
73
+
74
+ ## RBAC (Role-Based Access Control)
75
+
76
+ ```ts
77
+ // Define roles and permissions clearly
78
+ const PERMISSIONS = {
79
+ 'admin': ['read', 'write', 'delete', 'manage_users'],
80
+ 'editor': ['read', 'write'],
81
+ 'viewer': ['read'],
82
+ } as const
83
+
84
+ // Check permission, not role (more flexible)
85
+ function can(user: User, action: string): boolean {
86
+ return PERMISSIONS[user.role]?.includes(action) ?? false
87
+ }
88
+
89
+ // Usage
90
+ if (!can(user, 'delete')) {
91
+ return res.status(403).json({ error: { code: 'FORBIDDEN', message: 'Insufficient permissions' } })
92
+ }
93
+ ```
94
+
95
+ **Never:** check `user.role === 'admin'` inline everywhere. Centralize permission logic.
96
+
97
+ ## Password handling
98
+
99
+ ```ts
100
+ import bcrypt from 'bcrypt' // or argon2
101
+
102
+ // Hash on registration (never store plain text)
103
+ const hash = await bcrypt.hash(password, 12) // cost factor 12
104
+
105
+ // Verify on login
106
+ const valid = await bcrypt.compare(password, user.passwordHash)
107
+
108
+ // Timing-safe comparison for tokens
109
+ import { timingSafeEqual } from 'node:crypto'
110
+ const match = timingSafeEqual(Buffer.from(token), Buffer.from(expectedToken))
111
+ ```
112
+
113
+ ## OAuth (social login)
114
+
115
+ ```ts
116
+ // Always use a library — don't implement OAuth yourself
117
+ // next-auth / auth.js for Next.js
118
+ // passport.js for Express
119
+
120
+ // Validate state parameter to prevent CSRF
121
+ // Validate redirect_uri server-side
122
+ // Store minimal profile data (don't keep what you don't need)
123
+ ```
124
+
125
+ ## Rate limiting on auth endpoints
126
+
127
+ ```ts
128
+ // Login: 5 attempts per 15min per IP
129
+ // Password reset: 3 per hour per email
130
+ // Token refresh: 10 per minute per token
131
+ // Registration: 3 per hour per IP
132
+ ```
133
+
134
+ ## Checklist
135
+
136
+ - [ ] Access tokens expire in ≤1h
137
+ - [ ] Refresh tokens stored as hash in DB (revocable)
138
+ - [ ] Tokens in `httpOnly` cookies, never `localStorage`
139
+ - [ ] Passwords hashed with bcrypt/argon2 (cost ≥12)
140
+ - [ ] CSRF protection on all cookie-based state changes
141
+ - [ ] Rate limiting on login, register, reset endpoints
142
+ - [ ] All protected routes verified server-side (not just client guard)
143
+ - [ ] `HTTPS` only in production (`Secure` cookie flag)
144
+ - [ ] Logout invalidates refresh token in DB
145
+ - [ ] No sensitive data in JWT payload
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: db-design
3
+ description: Database schema design, indexing strategy, and migration patterns. Prevents N+1, missing indexes, unsafe migrations, and schema drift. Use when designing tables, adding columns, or writing complex queries.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # DB Design
8
+
9
+ Bad schema is the most expensive technical debt. You can refactor code in hours; migrating 10M rows takes days.
10
+
11
+ ## Schema design principles
12
+
13
+ ### Naming
14
+ - Tables: `snake_case`, plural (`users`, `order_items`, `refresh_tokens`)
15
+ - Columns: `snake_case` (`created_at`, `user_id`, `is_active`)
16
+ - Foreign keys: `{table_singular}_id` (`user_id`, `order_id`)
17
+ - Booleans: prefix `is_` or `has_` (`is_active`, `has_verified_email`)
18
+ - Timestamps: always `created_at` + `updated_at` on every table
19
+
20
+ ### Standard columns every table needs
21
+ ```sql
22
+ id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY -- or UUID
23
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
24
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
25
+ ```
26
+
27
+ ### Soft delete pattern (prefer over hard delete)
28
+ ```sql
29
+ deleted_at TIMESTAMPTZ -- NULL = active, non-NULL = deleted
30
+ -- Query: WHERE deleted_at IS NULL
31
+ ```
32
+
33
+ ## Indexing strategy
34
+
35
+ **Index everything you filter, sort, or join on.**
36
+
37
+ ```sql
38
+ -- Always index foreign keys
39
+ CREATE INDEX idx_orders_user_id ON orders(user_id);
40
+
41
+ -- Composite index for common query patterns (leftmost prefix rule)
42
+ CREATE INDEX idx_orders_user_status ON orders(user_id, status);
43
+ -- This covers: WHERE user_id = ?
44
+ -- And: WHERE user_id = ? AND status = ?
45
+ -- But NOT: WHERE status = ? alone
46
+
47
+ -- Partial index for filtered queries
48
+ CREATE INDEX idx_orders_pending ON orders(created_at)
49
+ WHERE status = 'pending';
50
+
51
+ -- Text search
52
+ CREATE INDEX idx_products_name_search ON products USING gin(to_tsvector('english', name));
53
+ ```
54
+
55
+ **Check for missing indexes:**
56
+ ```sql
57
+ EXPLAIN ANALYZE SELECT ... -- look for "Seq Scan" on large tables
58
+ -- Seq Scan on 1000 rows = fine. On 1M rows = add an index.
59
+ ```
60
+
61
+ **Over-indexing costs:** Every index slows down INSERT/UPDATE/DELETE. Don't index columns you never filter on.
62
+
63
+ ## N+1 pattern (the most common DB bug)
64
+
65
+ ```ts
66
+ // BAD — N+1: 1 query for orders + N queries for each user
67
+ const orders = await db.orders.findMany()
68
+ for (const order of orders) {
69
+ order.user = await db.users.findById(order.userId) // N queries!
70
+ }
71
+
72
+ // GOOD — 1 query with join or eager load
73
+ const orders = await db.orders.findMany({
74
+ include: { user: true } // Prisma
75
+ })
76
+
77
+ // Or raw SQL with JOIN
78
+ SELECT o.*, u.name, u.email
79
+ FROM orders o
80
+ JOIN users u ON u.id = o.user_id
81
+ WHERE o.status = 'pending'
82
+ ```
83
+
84
+ ## Safe migration patterns
85
+
86
+ ```sql
87
+ -- SAFE: adding nullable column (instant, no lock)
88
+ ALTER TABLE users ADD COLUMN avatar_url TEXT;
89
+
90
+ -- SAFE: adding column with default (Postgres 11+, instant)
91
+ ALTER TABLE users ADD COLUMN is_verified BOOLEAN NOT NULL DEFAULT false;
92
+
93
+ -- DANGEROUS: adding NOT NULL without default (locks table, blocks writes)
94
+ -- Do it in 3 steps:
95
+ -- 1. Add nullable
96
+ ALTER TABLE users ADD COLUMN phone TEXT;
97
+ -- 2. Backfill
98
+ UPDATE users SET phone = '' WHERE phone IS NULL;
99
+ -- 3. Add constraint
100
+ ALTER TABLE users ALTER COLUMN phone SET NOT NULL;
101
+
102
+ -- DANGEROUS: dropping column (data loss, code must be deployed first)
103
+ -- 1. Deploy code that no longer uses the column
104
+ -- 2. Then drop in next migration
105
+ ALTER TABLE users DROP COLUMN legacy_field;
106
+
107
+ -- DANGEROUS: renaming column/table (breaks existing queries)
108
+ -- Use a 2-phase rename: add new column, migrate data, remove old
109
+ ```
110
+
111
+ ## Transaction patterns
112
+
113
+ ```ts
114
+ // Always use transactions for multi-step operations
115
+ await db.transaction(async (tx) => {
116
+ const order = await tx.orders.create({ data: { userId, total } })
117
+ await tx.inventory.decrement({ productId, quantity })
118
+ await tx.payments.create({ data: { orderId: order.id, amount: total } })
119
+ // If any step fails, all are rolled back
120
+ })
121
+ ```
122
+
123
+ ## Query hygiene
124
+
125
+ - Never `SELECT *` in production — select only what you need
126
+ - Parameterized queries always (no string interpolation)
127
+ - `LIMIT` on all queries that could return unbounded results
128
+ - Avoid `OFFSET` for large pagination → use cursor-based (`WHERE id > lastId`)
129
+
130
+ ## Checklist
131
+
132
+ - [ ] Every table has `id`, `created_at`, `updated_at`
133
+ - [ ] All foreign keys have indexes
134
+ - [ ] Columns filtered/sorted in queries have indexes
135
+ - [ ] `EXPLAIN ANALYZE` run on queries touching >10k rows
136
+ - [ ] Migrations are reversible (or have a rollback plan)
137
+ - [ ] No `ALTER TABLE` without reviewing lock implications
138
+ - [ ] Multi-step operations wrapped in transactions
139
+ - [ ] No `SELECT *`, no string-interpolated queries
@@ -0,0 +1,196 @@
1
+ ---
2
+ name: error-handling
3
+ description: Error handling strategy. Error boundaries, logging patterns, user-facing messages, monitoring setup, and async error flows. Use when building any feature that can fail, or auditing error handling in existing code.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Error Handling
8
+
9
+ Every error falls into two buckets: expected (handle gracefully) and unexpected (log, alert, recover).
10
+ Never show users a stack trace. Never silently swallow an error.
11
+
12
+ ## Error hierarchy
13
+
14
+ ```
15
+ Expected errors (handle in code)
16
+ ├── Validation errors → show to user, guide to fix
17
+ ├── Not found → 404, redirect or empty state
18
+ ├── Auth errors → redirect to login
19
+ ├── Business rule violations → explain and offer alternative
20
+ └── External API failures → retry or fallback
21
+
22
+ Unexpected errors (log + alert + recover)
23
+ ├── Unhandled promise rejections
24
+ ├── Database connection failures
25
+ ├── Third-party SDK crashes
26
+ └── Memory/timeout errors
27
+ ```
28
+
29
+ ## Frontend: React Error Boundaries
30
+
31
+ ```tsx
32
+ // components/ErrorBoundary.tsx
33
+ 'use client'
34
+ import { Component, ReactNode } from 'react'
35
+
36
+ interface Props { children: ReactNode; fallback?: ReactNode }
37
+ interface State { hasError: boolean; error?: Error }
38
+
39
+ export class ErrorBoundary extends Component<Props, State> {
40
+ state: State = { hasError: false }
41
+
42
+ static getDerivedStateFromError(error: Error): State {
43
+ return { hasError: true, error }
44
+ }
45
+
46
+ componentDidCatch(error: Error, info: { componentStack: string }) {
47
+ // Log to your monitoring service
48
+ console.error('[ErrorBoundary]', error, info)
49
+ reportError(error, { context: info.componentStack })
50
+ }
51
+
52
+ render() {
53
+ if (this.state.hasError) {
54
+ return this.props.fallback ?? <DefaultErrorFallback error={this.state.error} />
55
+ }
56
+ return this.props.children
57
+ }
58
+ }
59
+
60
+ // Wrap at route level and around risky components
61
+ <ErrorBoundary fallback={<ErrorPage />}>
62
+ <Dashboard />
63
+ </ErrorBoundary>
64
+ ```
65
+
66
+ ## Frontend: async error patterns
67
+
68
+ ```tsx
69
+ // NEVER: unhandled promise
70
+ useEffect(() => {
71
+ fetchData() // if this throws, silent failure
72
+ }, [])
73
+
74
+ // GOOD: handle errors explicitly
75
+ const [error, setError] = useState<string | null>(null)
76
+
77
+ useEffect(() => {
78
+ fetchData()
79
+ .catch(err => {
80
+ setError(getErrorMessage(err))
81
+ reportError(err)
82
+ })
83
+ }, [])
84
+
85
+ // GOOD: React Query handles this automatically
86
+ const { data, error, isError } = useQuery({ queryKey: ['users'], queryFn: fetchUsers })
87
+ if (isError) return <ErrorState message={getErrorMessage(error)} />
88
+ ```
89
+
90
+ ## Next.js App Router error files
91
+
92
+ ```tsx
93
+ // app/error.tsx — catches runtime errors in route segment
94
+ 'use client'
95
+ export default function Error({ error, reset }: { error: Error; reset: () => void }) {
96
+ useEffect(() => { reportError(error) }, [error])
97
+
98
+ return (
99
+ <div>
100
+ <h2>Something went wrong</h2>
101
+ <button onClick={reset}>Try again</button>
102
+ </div>
103
+ )
104
+ }
105
+
106
+ // app/not-found.tsx — 404 handler
107
+ export default function NotFound() {
108
+ return <div>Page not found</div>
109
+ }
110
+
111
+ // app/global-error.tsx — catches errors in root layout
112
+ 'use client'
113
+ export default function GlobalError({ error, reset }) { ... }
114
+ ```
115
+
116
+ ## Backend: error response pattern
117
+
118
+ ```ts
119
+ // Never expose internal errors to clients
120
+ class AppError extends Error {
121
+ constructor(
122
+ public code: string, // machine-readable
123
+ public message: string, // end-user safe
124
+ public status: number, // HTTP status
125
+ public details?: unknown // optional context (logged, not returned)
126
+ ) { super(message) }
127
+ }
128
+
129
+ // Express global error handler
130
+ app.use((err: Error, req, res, next) => {
131
+ if (err instanceof AppError) {
132
+ return res.status(err.status).json({
133
+ data: null,
134
+ error: { code: err.code, message: err.message }
135
+ })
136
+ }
137
+
138
+ // Unexpected error — log full details, return generic message
139
+ logger.error({ err, req: { method: req.method, url: req.url } })
140
+ reportError(err)
141
+
142
+ res.status(500).json({
143
+ data: null,
144
+ error: { code: 'INTERNAL_ERROR', message: 'An unexpected error occurred' }
145
+ })
146
+ })
147
+ ```
148
+
149
+ ## Logging levels
150
+
151
+ | Level | When | Example |
152
+ |-------|------|---------|
153
+ | `error` | Unexpected failures, alerts needed | DB crash, unhandled exception |
154
+ | `warn` | Expected failures worth monitoring | Retry attempts, rate limit hit |
155
+ | `info` | Key business events | User registered, payment processed |
156
+ | `debug` | Dev only, never in production | Query params, response body |
157
+
158
+ ```ts
159
+ // Always log with context, not just the message
160
+ logger.error({ err, userId: req.user?.id, endpoint: req.url }, 'Payment failed')
161
+ // Not: logger.error('Payment failed')
162
+ ```
163
+
164
+ ## Async error rules
165
+
166
+ - Every `async` function must have a `try/catch` or be called with `.catch()`
167
+ - Never `await` inside `forEach` — use `Promise.all` or `for...of`
168
+ - Unhandled promise rejections crash Node.js 15+ — always handle
169
+
170
+ ```ts
171
+ // GOOD
172
+ const results = await Promise.all(items.map(item => processItem(item)))
173
+
174
+ // BAD (forEach ignores returned promise)
175
+ items.forEach(async (item) => await processItem(item))
176
+
177
+ // For sequential with error isolation:
178
+ for (const item of items) {
179
+ try {
180
+ await processItem(item)
181
+ } catch (err) {
182
+ logger.warn({ err, itemId: item.id }, 'Failed to process item, continuing')
183
+ }
184
+ }
185
+ ```
186
+
187
+ ## Checklist
188
+
189
+ - [ ] Error boundaries around route segments and risky components
190
+ - [ ] Every `fetch`/`async` call has error handling
191
+ - [ ] User sees human-readable message, never stack trace or raw error
192
+ - [ ] Unexpected errors logged with full context (user, endpoint, stack)
193
+ - [ ] `app/error.tsx` and `app/not-found.tsx` exist and are styled
194
+ - [ ] Global error handler in API (Express/Hono/Next route handler)
195
+ - [ ] `AppError` class for expected errors with machine-readable codes
196
+ - [ ] Error monitoring service wired up (see monitoring patterns)