Skip to content

Commit ce1ae98

Browse files
committed
mod
1 parent baaed65 commit ce1ae98

10 files changed

Lines changed: 167 additions & 15 deletions

File tree

03_guidelines/common/ai-coding.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ The static context (8-10 guideline files referenced in CLAUDE.md) typically adds
5757
## 2.2 Task Scoping
5858
* MUST break large tasks into small, focused requests (one file or one function per request)
5959
* MUST specify the exact file path and function name when requesting changes to existing code
60-
* SHOULD provide a Before/After example when explaining a pattern you want the AI to follow
60+
* SHOULD keep code examples minimal (1-3 lines inline ❌/✅). Avoid adding large Before/After sections — benchmark data shows they cause attention dilution without improving compliance
6161

6262
## 2.3 Effective Prompting Patterns
6363
* **Do**: "Add auth check using the ActionResult pattern defined in server-actions.md"
@@ -159,9 +159,10 @@ After every code review where AI-generated code was corrected:
159159

160160
## 5.3 When AI Cannot Follow a Rule
161161
If AI consistently fails to follow a specific guideline:
162-
1. Add a concrete code example (Before/After) to the guideline
163-
2. Add the rule to the project's checklist template
164-
3. If still failing, consider adding a pre-commit hook or linter rule for automated enforcement
162+
1. Clarify the rule wording — make it more specific and actionable
163+
2. Add a minimal inline code example (❌/✅, 1-3 lines) if the rule is ambiguous
164+
3. Add the rule to the project's checklist template
165+
4. If still failing, consider adding a pre-commit hook or linter rule for automated enforcement
165166

166167
## 5.4 Rule Suppression (Escape Hatch)
167168
When a specific guideline rule is not applicable to a particular module or file, suppress it explicitly rather than ignoring it silently:

03_guidelines/common/code.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,51 @@ saveOrder() // Has side effects - "save" signals persistence
4242
buildQuery() // Pure function - "build" signals construction
4343
```
4444

45-
## 2.3 Prohibit Enum → Use Union Literals
45+
## 2.3 Minimize Type Assertions (`as`)
46+
47+
MUST NOT use `as string`, `as any`, or `as unknown as T` unless absolutely necessary. Common violations:
48+
49+
```ts
50+
// ❌ formData.get() returns FormDataEntryValue | null — don't cast
51+
const name = formData.get("name") as string;
52+
53+
// ✅ Use Zod to parse and type-narrow
54+
const parsed = schema.safeParse(Object.fromEntries(formData));
55+
if (!parsed.success) return { success: false, error: parsed.error.flatten() };
56+
const { name } = parsed.data; // already typed
57+
```
58+
59+
Acceptable uses of `as`: `as const` for literal types, Prisma's `globalThis as unknown as` singleton pattern.
60+
61+
## 2.4 Prohibit Enum → Use Union Literals
4662

4763
```ts
4864
type Theme = 'dark' | 'light'
4965
```
5066
5167
Use Enum **only when external API compatibility is required**.
5268
69+
## 2.4 Exhaustive Checks on Union Types
70+
71+
MUST add exhaustive checks when switching on union types or Prisma enums. Use the `never` type to ensure all cases are handled at compile time:
72+
73+
```ts
74+
// ✅ Exhaustive check — compiler error if a new status is added
75+
function getStatusLabel(status: TaskStatus): string {
76+
switch (status) {
77+
case "TODO": return "To Do";
78+
case "IN_PROGRESS": return "In Progress";
79+
case "DONE": return "Done";
80+
default: {
81+
const _exhaustive: never = status;
82+
throw new Error(`Unhandled status: ${_exhaustive}`);
83+
}
84+
}
85+
}
86+
```
87+
88+
This applies to all discriminated unions, Prisma enums (`TaskStatus`, `TeamRole`, `Priority`), and action type dispatchers.
89+
5390
# 3. Lint Standards
5491

5592
## 3.1 Rule Sets

03_guidelines/common/error-handling.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,23 @@ class DomainError extends Error {
273273

274274
* Simulate API failures / latency
275275

276+
## 13. MUST: Error and Not-Found Boundaries (Next.js)
277+
278+
MUST place `error.tsx` in each route group and `not-found.tsx` at the app root:
279+
280+
```
281+
app/
282+
├── error.tsx ← MUST: root fallback
283+
├── not-found.tsx ← MUST: custom 404
284+
├── global-error.tsx ← MUST: catches root layout errors
285+
├── (auth)/
286+
│ └── error.tsx ← MUST: auth errors
287+
└── (protected)/
288+
├── error.tsx ← MUST: protected area fallback
289+
├── tasks/error.tsx ← SHOULD: task-specific
290+
└── teams/error.tsx ← SHOULD: team-specific
291+
```
292+
276293
## Before/After Example
277294

278295
```typescript

03_guidelines/common/naming.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,23 @@ form-schema.ts
163163

164164
---
165165

166-
# 8. Forms (React Hook Form)
166+
# 8. Event Handlers and Callbacks
167+
168+
* MUST use `handle` + noun + verb pattern: `handleTaskDelete`, `handleFormSubmit`, `handleStatusToggle`
169+
*`handleDelete`, `handleSubmit`, `onSubmit` — missing noun, unclear what is being acted on
170+
*`handleTaskDelete`, `handleProfileUpdate`, `handleInvitationAccept`
171+
* Props passed to child components use `on` prefix: `onTaskDelete`, `onStatusChange`
172+
173+
---
174+
175+
# 9. Forms (React Hook Form)
167176

168177
* `[Domain]Form.tsx`
169178
* `use[Domain]Form.ts`
170179

171180
---
172181

173-
# 9. WebSocket / Realtime Names
182+
# 10. WebSocket / Realtime Names
174183

175184
* Event names: **snake_case**
176185
* Channel names: **plural**
@@ -188,7 +197,7 @@ form-schema.ts
188197
| DB column | snake_case (singular) |
189198
| URL path | kebab-case (plural) |
190199
| API path | REST / plural |
191-
| React component | PascalCase (file name also PascalCase) |
200+
| React component | PascalCase export, **kebab-case file name** (`task-card.tsx` exports `TaskCard`) |
192201
| Other files/directories | kebab-case |
193202
| Type / Enum | PascalCase |
194203
| Zod schema | PascalCase / file name is kebab-case (-schema.ts) |

03_guidelines/common/performance.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,33 @@ Decision rule for where to place the RSC/CC boundary:
2626
---
2727

2828
## 2. Client Bundle Optimization (Client Components)
29-
### Dynamic Import of Dependencies
29+
### Dynamic Import of Heavy Components
30+
31+
SHOULD use `next/dynamic` for client components that are heavy, below the fold, or conditionally rendered:
32+
3033
```ts
31-
const Editor = dynamic(() => import('./Editor'), { ssr: false });
34+
import dynamic from "next/dynamic";
35+
36+
// ✅ Heavy editor loaded only when needed
37+
const RichTextEditor = dynamic(() => import("./RichTextEditor"), {
38+
ssr: false,
39+
loading: () => <div className="h-64 animate-pulse bg-muted rounded" />,
40+
});
41+
42+
// ✅ Modal/dialog loaded on demand
43+
const CreateTaskDialog = dynamic(() => import("./CreateTaskDialog"));
44+
45+
// ✅ Chart component (large dependency) deferred
46+
const AnalyticsChart = dynamic(() => import("./AnalyticsChart"), { ssr: false });
3247
```
3348

49+
Candidates for dynamic import:
50+
* Rich text editors, code editors, markdown renderers
51+
* Chart/graph components (recharts, chart.js)
52+
* Modal/dialog content that is not visible on initial load
53+
* Admin-only components on pages accessible to all users
54+
* Any component importing a dependency > 50KB
55+
3456
### Use Tree-Shakable Imports Consistently
3557
* `import { X } from 'lodash'` — avoid (imports entire library)
3658
* `import debounce from 'lodash/debounce'` — preferred (imports only the function)

03_guidelines/common/security.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,21 @@ export async function getUserProjects(): Promise<ActionResult<Project[]>> {
159159
- **Production / multiple instances**: Redis (Upstash)
160160
- **Standard headers**: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `Retry-After`
161161

162+
## MUST: Apply Rate Limiting to Auth Server Actions
163+
164+
MUST apply rate limiting to ALL Server Actions that handle authentication — not just API routes. Server Actions for `register`, `login`, `requestPasswordReset`, and `resetPassword` MUST check rate limits per IP before processing:
165+
166+
```ts
167+
"use server";
168+
export async function registerUser(formData: FormData): Promise<ActionResult> {
169+
// ✅ MUST: Rate limit before any auth processing
170+
const ip = (await headers()).get("x-forwarded-for") ?? "unknown";
171+
const { success } = await checkRateLimit(`auth:register:${ip}`, { maxRequests: 5, windowMs: 60_000 });
172+
if (!success) return { success: false, error: "Too many attempts. Try again later." };
173+
// ... rest of registration logic
174+
}
175+
```
176+
162177
---
163178

164179
# 3.3 Webhook Security

03_guidelines/common/validation.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ This document outlines how to manage **Types** and **Validation** in a unified m
66
# 1. Core Policy
77
* Build a structure where types naturally synchronize in the order **Zod → Prisma → TypeScript**.
88
* API schemas can be converted from **Zod → JSON Schema (for AI APIs, etc.)** for external sharing.
9+
* MUST validate on BOTH client and server using the SAME Zod schema. Use `zodResolver` from `@hookform/resolvers/zod` for client-side form validation with react-hook-form. Never rely on server-side validation alone.
910

1011
---
1112

1213
# 2. Business Logic Rules in Zod Schemas
1314

1415
Business logic rules that belong in Zod schemas (not just format validation):
1516

16-
* **Cross-field constraints**: e.g., `endDate` must be after `startDate`, `maxPrice` >= `minPrice`
17+
* **Date range constraints**: MUST validate that future-facing dates (e.g., `dueDate`, `expiresAt`) are in the future using `.refine()`. MUST validate that `endDate` is after `startDate`
1718
* **Domain value boundaries**: e.g., quantity must be 1-999, discount percentage 0-100
1819
* **Conditional required fields**: e.g., if `paymentMethod` is "card", `cardNumber` is required
1920
* **Enumerated state transitions**: e.g., order status can only move from "pending" → "confirmed" → "shipped"

03_guidelines/frameworks/nextjs/project-structure.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,30 @@ settings/
194194

195195
---
196196

197-
# 9. Guidelines Summary
197+
# 9. Dynamic Import for Heavy Components
198+
199+
SHOULD use `next/dynamic` for client components that are heavy, conditionally rendered, or below the fold:
200+
201+
```ts
202+
import dynamic from "next/dynamic";
203+
const RichTextEditor = dynamic(() => import("./RichTextEditor"), { ssr: false });
204+
const CreateTaskDialog = dynamic(() => import("./CreateTaskDialog"));
205+
const AnalyticsChart = dynamic(() => import("./AnalyticsChart"), { ssr: false });
206+
```
207+
208+
Candidates: rich text editors, chart components (recharts, chart.js), modal/dialog content, any component with dependencies > 50KB.
209+
210+
# 10. Accessibility: Icon Buttons
211+
212+
MUST add `aria-label` to every `<Button>` without visible text. Every icon-only button, toggle, or close button MUST describe its action:
213+
214+
```tsx
215+
<Button variant="ghost" size="icon" aria-label="Delete task">
216+
<TrashIcon />
217+
</Button>
218+
```
219+
220+
# 11. Guidelines Summary
198221

199222
- **Vertical slice structure is the default**
200223
- **Business logic must always be contained within features**

03_guidelines/frameworks/nextjs/routing.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,36 @@ Benefits:
112112

113113
---
114114

115-
# 5. Summary (Routing / Metadata / Data Fetching)
115+
# 5. Error and Not-Found Boundaries
116+
117+
## MUST: Place error.tsx in Every Route Group
118+
119+
MUST create `error.tsx` in each route group and high-risk route segment — not just the root:
120+
121+
```
122+
app/
123+
├── error.tsx ← root fallback (MUST)
124+
├── not-found.tsx ← custom 404 (MUST)
125+
├── global-error.tsx ← catches root layout errors (MUST)
126+
├── (auth)/
127+
│ └── error.tsx ← auth-specific errors (MUST)
128+
├── (protected)/
129+
│ ├── error.tsx ← protected area fallback (MUST)
130+
│ ├── tasks/
131+
│ │ └── error.tsx ← task-specific errors (SHOULD)
132+
│ └── teams/
133+
│ └── error.tsx ← team-specific errors (SHOULD)
134+
```
135+
136+
## MUST: Place not-found.tsx at Root
137+
138+
MUST create `app/not-found.tsx` with a user-friendly 404 page. Pages that call `notFound()` (e.g., task detail, team detail) will render this component.
139+
140+
---
141+
142+
# 6. Summary (Routing / Metadata / Data Fetching)
116143
* Organize hierarchy with App Router and separate concerns at the layout level.
117-
* Unify UX with per-page `loading.tsx` / `error.tsx`.
144+
* MUST place `error.tsx` in each route group and `not-found.tsx` at root.
118145
* Public pages use SSG/ISR; user-specific data uses SSR + Server Actions + custom hooks.
119146
* Flexibly and dynamically control SEO/OG/Twitter via the Metadata API.
120147
* Achieve DB access that balances security and performance through Server Components.

03_guidelines/frameworks/nextjs/ui.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ const buttonVariants = cva(
157157

158158
| Element | Required Action |
159159
|---------|----------------|
160-
| Icon button | aria-label or sr-only text |
160+
| Icon button | MUST have `aria-label` describing the action (e.g., `aria-label="Delete task"`, `aria-label="Open menu"`). Every `<Button>` without visible text MUST have an aria-label. |
161161
| Badge / Counter | Explain meaning with aria-label |
162162
| Dynamically updated content | aria-live="polite" |
163163
| Progress bar | role="progressbar" + aria-value* |

0 commit comments

Comments
 (0)