Skip to content

Commit 79962f2

Browse files
iammarkpsclaude
andauthored
refactor: apply progressive disclosure to CLAUDE.md and agent docs (#990)
- Minimize root CLAUDE.md to essentials only (17 lines) - Create .claude/docs/ with consolidated pattern files: - react-patterns.md: Server/Client components, styling - api-patterns.md: Validation (Zod→TypeBox migration noted) - database-patterns.md: Prisma queries, migrations - auth-patterns.md: getServerUser(), admin checks - Update agents to reference docs instead of duplicating - Simplify skills to quick reference + link to full docs Reduces duplication across 6 files (-77 lines net). Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3332b35 commit 79962f2

10 files changed

Lines changed: 284 additions & 127 deletions

File tree

.claude/agents/code-reviewer.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ Senior code reviewer for programming.in.th (Next.js 15, React 19, TypeScript, Pr
1010
**Process**: `git diff --name-only``git diff` → read files → review
1111

1212
**Review for**:
13-
- **Performance**: Server Components (avoid unnecessary `'use client'`), selective Prisma fields, no N+1, pagination, caching
14-
- **Types**: No `any`, Zod validation for APIs, proper error handling
15-
- **Patterns**: Follows codebase conventions, focused functions, clear naming
13+
- **Performance**: Server Components preferred, selective Prisma fields, no N+1, pagination
14+
- **Types**: No `any`, validation on APIs, proper error handling
15+
- **Patterns**: Follows codebase conventions
1616

17-
**Key patterns**:
18-
- Prisma: Always `select`, import from `@/lib/prisma`
19-
- Auth: `getServerUser()` from `@/lib/session`, check `user.admin`
20-
- APIs: Zod schemas, consistent errors (400/401/403/404/500)
21-
- Components: Tailwind, `dark:` variants, accessibility
17+
**Pattern references**:
18+
- [React patterns](../docs/react-patterns.md)
19+
- [API patterns](../docs/api-patterns.md)
20+
- [Database patterns](../docs/database-patterns.md)
21+
- [Auth patterns](../docs/auth-patterns.md)
2222

23-
**Output**: Issues by severity (Critical/Warning/Suggestion) with `file:line` and fixes. Verdict: **APPROVED** / **CHANGES REQUESTED**
23+
**Output**: Issues by severity (Critical/Warning/Suggestion) with `file:line` and fixes.
24+
25+
**Verdict**: **APPROVED** / **CHANGES REQUESTED**

.claude/agents/security-reviewer.md

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,17 @@ Security specialist for programming.in.th (auth, code submissions, file storage)
1010
**Process**: `git diff` for changes OR grep for security patterns → analyze → remediate
1111

1212
**Check for**:
13-
- **Auth**: `getServerUser()` on protected routes, `user.admin` for admin routes
14-
- **Validation**: Zod `safeParse()` for all input, no internal details in errors
13+
- **Auth**: See [auth-patterns.md](../docs/auth-patterns.md)
14+
- **Validation**: Input validation on all endpoints (see [api-patterns.md](../docs/api-patterns.md))
1515
- **Injection**: Prisma parameterized queries, no user input in commands/paths
16-
- **Data exposure**: Selective fields only, no secrets in responses/logs
17-
- **Files**: Presigned S3 URLs, validate types/sizes, sanitize paths
16+
- **Data exposure**: Selective Prisma fields (see [database-patterns.md](../docs/database-patterns.md))
17+
- **Files**: Presigned S3 URLs only, validate types/sizes, sanitize paths
1818

1919
**Search for secrets**:
2020
```bash
2121
grep -rE "(password|secret|key|token)\s*[:=]" --include="*.ts"
2222
```
2323

24-
**Required patterns**:
25-
```typescript
26-
const user = await getServerUser()
27-
if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 })
24+
**Output**: Findings by severity (Critical/High/Medium/Low) with risk, evidence, fix.
2825

29-
const result = Schema.safeParse(input)
30-
if (!result.success) return Response.json({ error: 'Invalid' }, { status: 400 })
31-
```
32-
33-
**Output**: Findings by severity (Critical/High/Medium/Low) with risk, evidence, fix. Verdict: **SECURE** / **ISSUES FOUND** / **CRITICAL**
26+
**Verdict**: **SECURE** / **ISSUES FOUND** / **CRITICAL**

.claude/docs/api-patterns.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# API & Validation Patterns
2+
3+
## Validation Strategy
4+
5+
**Current**: Zod for validation
6+
**Migration in progress**: Moving to Elysia with TypeBox (`t.*` schemas)
7+
8+
## Elysia Routes (Preferred)
9+
10+
```typescript
11+
import { Elysia, t } from 'elysia'
12+
import { prisma } from '@/lib/prisma'
13+
14+
const app = new Elysia()
15+
.get('/tasks/:id', async ({ params, query, status }) => {
16+
const task = await prisma.task.findUnique({
17+
where: { id: params.id },
18+
select: { id: true, title: true }
19+
})
20+
if (!task) return status(404, { error: 'Not found' })
21+
return task
22+
}, {
23+
params: t.Object({ id: t.String() }),
24+
query: t.Object({ limit: t.Optional(t.Numeric()) })
25+
})
26+
.post('/tasks', async ({ body, status }) => {
27+
const task = await prisma.task.create({ data: body })
28+
return status(201, task)
29+
}, {
30+
body: t.Object({
31+
title: t.String({ minLength: 1 }),
32+
fullScore: t.Number({ minimum: 0 })
33+
})
34+
})
35+
```
36+
37+
## Legacy Next.js Routes (Zod)
38+
39+
```typescript
40+
import { z } from 'zod'
41+
42+
const Schema = z.object({ title: z.string().min(1) })
43+
44+
const result = Schema.safeParse(input)
45+
if (!result.success) {
46+
return Response.json({ error: 'Invalid input' }, { status: 400 })
47+
}
48+
```
49+
50+
## Error Responses
51+
52+
Use consistent HTTP status codes:
53+
- `400` - Invalid input
54+
- `401` - Unauthorized (not logged in)
55+
- `403` - Forbidden (logged in but not allowed)
56+
- `404` - Not found
57+
- `500` - Server error
58+
59+
**Never expose internal details** in error messages.
60+
61+
## Checklist
62+
63+
- [ ] Validation on all inputs (`t.Object` or Zod schema)
64+
- [ ] Auth checks (see [auth-patterns.md](./auth-patterns.md))
65+
- [ ] Selective Prisma fields (see [database-patterns.md](./database-patterns.md))
66+
- [ ] Pagination for list endpoints
67+
- [ ] Proper `status()` codes for errors

.claude/docs/auth-patterns.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Authentication Patterns
2+
3+
## Server-Side Auth
4+
5+
```typescript
6+
import { getServerUser } from '@/lib/session'
7+
8+
// In Server Components or API routes
9+
const user = await getServerUser()
10+
11+
if (!user) {
12+
return Response.json({ error: 'Unauthorized' }, { status: 401 })
13+
}
14+
```
15+
16+
## Admin Routes
17+
18+
```typescript
19+
const user = await getServerUser()
20+
21+
if (!user) {
22+
return Response.json({ error: 'Unauthorized' }, { status: 401 })
23+
}
24+
25+
if (!user.admin) {
26+
return Response.json({ error: 'Forbidden' }, { status: 403 })
27+
}
28+
```
29+
30+
## Elysia Auth Guard
31+
32+
```typescript
33+
import { Elysia } from 'elysia'
34+
35+
const app = new Elysia()
36+
.derive(async ({ headers, status }) => {
37+
const user = await getUser(headers.authorization)
38+
if (!user) return status(401, { error: 'Unauthorized' })
39+
return { user }
40+
})
41+
.get('/admin', ({ user, status }) => {
42+
if (!user.admin) return status(403, { error: 'Forbidden' })
43+
return 'admin only'
44+
})
45+
```
46+
47+
## Checklist
48+
49+
- [ ] `getServerUser()` on all protected routes
50+
- [ ] Check `user.admin` for admin-only routes
51+
- [ ] Return 401 for unauthenticated, 403 for unauthorized
52+
- [ ] Never expose user data without ownership check

.claude/docs/database-patterns.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Database & Prisma Patterns
2+
3+
## Setup
4+
5+
- **Schema**: `prisma/schema.prisma`
6+
- **Import**: Always `import { prisma } from '@/lib/prisma'`
7+
8+
## Schema Changes
9+
10+
```bash
11+
# 1. Edit prisma/schema.prisma
12+
# 2. Create migration
13+
pnpm prisma migrate dev --name descriptive_name
14+
# 3. Verify types
15+
pnpm check-types
16+
```
17+
18+
## Query Patterns
19+
20+
### Always Select Specific Fields
21+
22+
```typescript
23+
// Good
24+
const tasks = await prisma.task.findMany({
25+
where: { private: false },
26+
select: { id: true, title: true, fullScore: true }
27+
})
28+
29+
// Bad - fetches all columns
30+
const tasks = await prisma.task.findMany({
31+
where: { private: false }
32+
})
33+
```
34+
35+
### Always Paginate
36+
37+
```typescript
38+
const tasks = await prisma.task.findMany({
39+
where: { private: false },
40+
select: { id: true, title: true },
41+
take: 10,
42+
skip: page * 10
43+
})
44+
```
45+
46+
### Avoid N+1 Queries
47+
48+
```typescript
49+
// Option 1: Include related data
50+
const tasks = await prisma.task.findMany({
51+
select: { id: true, title: true, tags: true }
52+
})
53+
54+
// Option 2: Batch query
55+
const taskIds = tasks.map(t => t.id)
56+
const submissions = await prisma.submission.findMany({
57+
where: { taskId: { in: taskIds } }
58+
})
59+
```
60+
61+
## Indexes
62+
63+
Add `@@index([field])` for columns used in:
64+
- `WHERE` clauses
65+
- `ORDER BY` clauses
66+
- Foreign key lookups
67+
68+
## Models Reference
69+
70+
User, Task, Submission, Assessment, Category, Tag, Bookmark

.claude/docs/react-patterns.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# React & Component Patterns
2+
3+
## Server vs Client Components
4+
5+
**Default to Server Components** - no directive needed.
6+
7+
```tsx
8+
// Server Component (default)
9+
export function TaskCard({ task }: { task: Task }) {
10+
return <div className="p-4 dark:bg-gray-800">{task.title}</div>
11+
}
12+
```
13+
14+
**Use `'use client'` only when required**:
15+
- Event handlers (`onClick`, `onSubmit`)
16+
- React hooks (`useState`, `useEffect`)
17+
- Browser APIs (`localStorage`, `window`)
18+
19+
```tsx
20+
'use client'
21+
import { useState } from 'react'
22+
23+
export function Toggle() {
24+
const [on, setOn] = useState(false)
25+
return <button onClick={() => setOn(!on)}>{on ? 'On' : 'Off'}</button>
26+
}
27+
```
28+
29+
## Performance
30+
31+
- Push `'use client'` to the smallest possible component
32+
- Use `memo()` for expensive renders
33+
- Use Next.js `<Image>` for images
34+
35+
## Styling
36+
37+
- **Tailwind only** - no CSS files
38+
- **Dark mode**: Use `dark:` variants (e.g., `dark:bg-gray-800`)
39+
- **Custom colors**: `prog-primary-500`
40+
41+
## Accessibility
42+
43+
- Labels for all form inputs
44+
- ARIA attributes where semantic HTML isn't sufficient
45+
- Keyboard navigation for custom interactive elements

.claude/skills/api-development/SKILL.md

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,24 @@ description: Use when creating or modifying Elysia API routes. Ensures proper va
44
allowed-tools: Read, Edit, Write, Glob, Grep, Bash
55
---
66

7-
API routes use [Elysia](https://elysiajs.com) with TypeBox validation:
7+
See [API patterns](../../docs/api-patterns.md) for full reference.
88

9+
**Quick example**:
910
```typescript
1011
import { Elysia, t } from 'elysia'
1112
import { prisma } from '@/lib/prisma'
1213

13-
const app = new Elysia()
14-
.get('/tasks/:id', async ({ params, query, status }) => {
15-
const limit = query.limit ?? 10
14+
new Elysia()
15+
.get('/tasks/:id', async ({ params, status }) => {
1616
const task = await prisma.task.findUnique({
1717
where: { id: params.id },
1818
select: { id: true, title: true }
1919
})
2020
if (!task) return status(404, { error: 'Not found' })
2121
return task
2222
}, {
23-
params: t.Object({ id: t.String() }),
24-
query: t.Object({ limit: t.Optional(t.Numeric()) })
23+
params: t.Object({ id: t.String() })
2524
})
26-
.post('/tasks', async ({ body, status }) => {
27-
const task = await prisma.task.create({ data: body })
28-
return status(201, task)
29-
}, {
30-
body: t.Object({
31-
title: t.String({ minLength: 1 }),
32-
fullScore: t.Number({ minimum: 0 })
33-
})
34-
})
35-
```
36-
37-
**Auth guard pattern**:
38-
```typescript
39-
.derive(async ({ headers, status }) => {
40-
const user = await getUser(headers.authorization)
41-
if (!user) return status(401, { error: 'Unauthorized' })
42-
return { user }
43-
})
44-
.get('/admin', ({ user, status }) => {
45-
if (!user.admin) return status(403, { error: 'Forbidden' })
46-
return 'admin only'
47-
})
4825
```
4926

50-
**Checklist**: `t.Object` validation, auth derive/guard, selective Prisma fields, pagination, `status()` for errors.
27+
**Checklist**: `t.Object` validation, auth guards, selective Prisma fields, pagination, proper status codes.

.claude/skills/component-development/SKILL.md

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,20 @@ description: Use when creating or modifying React components. Ensures proper Ser
44
allowed-tools: Read, Edit, Write, Glob, Grep
55
---
66

7-
Components in `src/components/`. Default to Server Components.
7+
See [React patterns](../../docs/react-patterns.md) for full reference.
88

9+
**Quick rules**:
10+
- Default to Server Components (no directive)
11+
- Use `'use client'` only for: `onClick`, `useState`, `useEffect`, browser APIs
12+
- Push client boundary to smallest component
13+
- Tailwind only, use `dark:` variants
14+
15+
**Example**:
916
```tsx
10-
// Server Component (default) - no directive needed
17+
// Server Component (default)
1118
export function TaskCard({ task }: { task: Task }) {
1219
return <div className="p-4 dark:bg-gray-800">{task.title}</div>
1320
}
14-
15-
// Client Component - only for interactivity
16-
'use client'
17-
import { useState } from 'react'
18-
export function Toggle() {
19-
const [on, setOn] = useState(false)
20-
return <button onClick={() => setOn(!on)}>{on ? 'On' : 'Off'}</button>
21-
}
2221
```
2322

24-
**When to use `'use client'`**: onClick/onSubmit, useState/useEffect, browser APIs.
25-
26-
**Performance**: Push `'use client'` to smallest component, use `memo()` for expensive renders, Next.js `<Image>`.
27-
28-
**Accessibility**: Labels for inputs, ARIA attributes, keyboard nav for custom elements.
29-
30-
**Styling**: Tailwind only, `dark:` variants, custom colors: `prog-primary-500`.
23+
**Checklist**: Minimal client components, `dark:` variants, accessible inputs.

0 commit comments

Comments
 (0)