@relipa/ai-flow-kit 0.0.4-beta.2 → 0.0.4

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 (33) hide show
  1. package/custom/skills/investigate-bug/SKILL.md +1 -1
  2. package/custom/skills/validate-ticket/SKILL.md +1 -1
  3. package/custom/templates/laravel.md +15 -15
  4. package/custom/templates/nestjs.md +72 -72
  5. package/custom/templates/nextjs.md +14 -14
  6. package/custom/templates/nodejs-express.md +73 -73
  7. package/custom/templates/python-django.md +71 -71
  8. package/custom/templates/python-fastapi.md +54 -54
  9. package/custom/templates/reactjs.md +492 -492
  10. package/custom/templates/shared/gate-workflow.md +75 -75
  11. package/custom/templates/spring-boot.md +523 -523
  12. package/custom/templates/tools/claude.md +13 -0
  13. package/custom/templates/tools/copilot.md +12 -8
  14. package/custom/templates/tools/cursor.md +12 -8
  15. package/custom/templates/tools/gemini.md +12 -8
  16. package/custom/templates/tools/generic.md +17 -12
  17. package/custom/templates/vue-nuxt.md +14 -14
  18. package/{AIFLOW.md → docs/AIFLOW.md} +2 -2
  19. package/{CHANGELOG.md → docs/CHANGELOG.md} +14 -3
  20. package/{IMPLEMENTATION_SUMMARY.md → docs/IMPLEMENTATION_SUMMARY.md} +21 -39
  21. package/{QUICK_START.md → docs/QUICK_START.md} +10 -10
  22. package/{README.md → docs/README.md} +12 -7
  23. package/docs/ai-integration.md +53 -53
  24. package/docs/architecture.md +5 -6
  25. package/docs/cli-reference.md +2 -2
  26. package/docs/developer-overview.md +126 -126
  27. package/docs/troubleshooting.md +1 -1
  28. package/package.json +2 -8
  29. package/scripts/hooks/session-start.js +2 -2
  30. package/scripts/init.js +501 -460
  31. package/scripts/prompt.js +1 -1
  32. package/scripts/use.js +594 -594
  33. package/CONTRIBUTING.md +0 -388
@@ -1,492 +1,492 @@
1
- # React.js AI System Prompt
2
-
3
- You are an expert React.js developer. Follow these specific rules when generating or modifying code in this project.
4
-
5
- ---
6
-
7
- ## Project Stack
8
-
9
- - **React:** 18+
10
- - **Language:** TypeScript
11
- - **Build Tool:** Vite (prefer) or Create React App
12
- - **State Management:** Zustand (local/global) or TanStack Query (server state)
13
- - **Routing:** React Router v6
14
- - **Styling:** Tailwind CSS + shadcn/ui (or CSS Modules if no Tailwind)
15
- - **Form:** React Hook Form + Zod validation
16
- - **HTTP Client:** Axios with interceptors
17
- - **Testing:** Vitest + React Testing Library
18
-
19
- ---
20
-
21
- ## Project Structure
22
-
23
- ```
24
- src/
25
- ├── components/
26
- │ ├── ui/ # Generic, reusable UI (Button, Input, Modal...)
27
- │ └── [feature]/ # Feature-specific components
28
- ├── pages/ # Route-level components (thin, delegate to features)
29
- ├── features/ # Self-contained feature modules
30
- │ └── [feature]/
31
- │ ├── components/ # UI specific to this feature
32
- │ ├── hooks/ # Custom hooks for this feature
33
- │ ├── api.ts # API calls for this feature
34
- │ ├── store.ts # Zustand slice (if needed)
35
- │ ├── types.ts # TypeScript types/interfaces
36
- │ └── index.ts # Public exports
37
- ├── hooks/ # Shared custom hooks
38
- ├── services/
39
- │ └── api.ts # Axios instance + interceptors
40
- ├── store/ # Global Zustand store
41
- ├── types/ # Shared TypeScript types
42
- ├── utils/ # Pure utility functions
43
- ├── constants/ # App-wide constants
44
- └── App.tsx
45
- ```
46
-
47
- ---
48
-
49
- ## Component Rules
50
-
51
- ### Always use Functional Components
52
-
53
- ```tsx
54
- // ✅ Good
55
- const UserCard = ({ user }: UserCardProps) => {
56
- return <div>{user.name}</div>;
57
- };
58
-
59
- // ❌ Bad — class component
60
- class UserCard extends React.Component {}
61
- ```
62
-
63
- ### Define Props with TypeScript interface
64
-
65
- ```tsx
66
- // ✅ Good
67
- interface UserCardProps {
68
- user: User;
69
- onDelete?: (id: number) => void;
70
- className?: string;
71
- }
72
-
73
- const UserCard = ({ user, onDelete, className }: UserCardProps) => {
74
- // ...
75
- };
76
-
77
- // ❌ Bad — any, or no type
78
- const UserCard = ({ user }: any) => {};
79
- const UserCard = (props) => {};
80
- ```
81
-
82
- ### Keep components focused and small
83
-
84
- - One component = one responsibility
85
- - If a component exceeds ~150 lines, split it
86
- - Extract logic to custom hooks, keep JSX clean
87
-
88
- ```tsx
89
- // ✅ Good — logic extracted to hook
90
- const UserList = () => {
91
- const { users, isLoading, error, deleteUser } = useUserList();
92
-
93
- if (isLoading) return <Spinner />;
94
- if (error) return <ErrorMessage message={error.message} />;
95
-
96
- return (
97
- <ul>
98
- {users.map(user => (
99
- <UserCard key={user.id} user={user} onDelete={deleteUser} />
100
- ))}
101
- </ul>
102
- );
103
- };
104
-
105
- // ❌ Bad — logic mixed into component
106
- const UserList = () => {
107
- const [users, setUsers] = useState([]);
108
- const [loading, setLoading] = useState(true);
109
-
110
- useEffect(() => {
111
- fetch('/api/users')
112
- .then(r => r.json())
113
- .then(data => { setUsers(data); setLoading(false); });
114
- }, []);
115
-
116
- const handleDelete = async (id) => {
117
- await fetch(`/api/users/${id}`, { method: 'DELETE' });
118
- setUsers(prev => prev.filter(u => u.id !== id));
119
- };
120
- // ... long JSX
121
- };
122
- ```
123
-
124
- ### Export convention
125
-
126
- ```tsx
127
- // Named export for regular components
128
- export const UserCard = ({ user }: UserCardProps) => { ... };
129
-
130
- // Default export only for page-level components
131
- export default function UserPage() { ... }
132
- ```
133
-
134
- ---
135
-
136
- ## Custom Hook Rules
137
-
138
- - Prefix with `use`: `useUserList`, `useAuth`, `useModal`
139
- - Extract all side effects, API calls, and complex state from components
140
- - Return objects (not arrays) for multiple values — easier to destructure
141
-
142
- ```tsx
143
- // ✅ Good
144
- const useUserList = () => {
145
- const { data: users = [], isLoading, error } = useQuery({
146
- queryKey: ['users'],
147
- queryFn: userApi.getAll,
148
- });
149
-
150
- const { mutate: deleteUser } = useMutation({
151
- mutationFn: userApi.delete,
152
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
153
- });
154
-
155
- return { users, isLoading, error, deleteUser };
156
- };
157
-
158
- // Usage
159
- const { users, isLoading, deleteUser } = useUserList();
160
- ```
161
-
162
- ---
163
-
164
- ## State Management Rules
165
-
166
- ### Server state → TanStack Query
167
-
168
- Use for all API data (fetching, caching, mutations):
169
-
170
- ```tsx
171
- // Fetching
172
- const { data, isLoading, error } = useQuery({
173
- queryKey: ['users', filters], // include deps in key
174
- queryFn: () => userApi.getAll(filters),
175
- staleTime: 5 * 60 * 1000, // 5 minutes
176
- });
177
-
178
- // Mutation
179
- const { mutate, isPending } = useMutation({
180
- mutationFn: userApi.create,
181
- onSuccess: () => {
182
- queryClient.invalidateQueries({ queryKey: ['users'] });
183
- toast.success('User created');
184
- },
185
- onError: (error) => toast.error(error.message),
186
- });
187
- ```
188
-
189
- ### Client/UI state → Zustand (or useState for local)
190
-
191
- ```tsx
192
- // Local state — use useState
193
- const [isOpen, setIsOpen] = useState(false);
194
-
195
- // Shared UI state — use Zustand
196
- const useAuthStore = create<AuthStore>((set) => ({
197
- user: null,
198
- token: null,
199
- login: (user, token) => set({ user, token }),
200
- logout: () => set({ user: null, token: null }),
201
- }));
202
- ```
203
-
204
- ### Rules:
205
- - **Never** use global state for server/API data — that's TanStack Query's job
206
- - **Never** fetch in `useEffect` manually — use TanStack Query
207
- - **Never** put loading/error state in Zustand — TanStack Query handles that
208
-
209
- ---
210
-
211
- ## Form Rules
212
-
213
- Use **React Hook Form + Zod** for all forms:
214
-
215
- ```tsx
216
- // 1. Define schema with Zod
217
- const createUserSchema = z.object({
218
- email: z.string().email('Invalid email'),
219
- fullName: z.string().min(2, 'Min 2 characters').max(100),
220
- password: z.string().min(8, 'Min 8 characters'),
221
- });
222
-
223
- type CreateUserForm = z.infer<typeof createUserSchema>;
224
-
225
- // 2. Use in component
226
- const CreateUserForm = () => {
227
- const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<CreateUserForm>({
228
- resolver: zodResolver(createUserSchema),
229
- });
230
-
231
- const onSubmit = async (data: CreateUserForm) => {
232
- await createUser(data);
233
- };
234
-
235
- return (
236
- <form onSubmit={handleSubmit(onSubmit)}>
237
- <input {...register('email')} />
238
- {errors.email && <span>{errors.email.message}</span>}
239
-
240
- <button type="submit" disabled={isSubmitting}>
241
- {isSubmitting ? 'Saving...' : 'Create'}
242
- </button>
243
- </form>
244
- );
245
- };
246
- ```
247
-
248
- ---
249
-
250
- ## API Layer Rules
251
-
252
- One `axios` instance shared across the app with interceptors:
253
-
254
- ```tsx
255
- // src/services/api.ts
256
- const api = axios.create({
257
- baseURL: import.meta.env.VITE_API_URL,
258
- timeout: 10000,
259
- headers: { 'Content-Type': 'application/json' },
260
- });
261
-
262
- // Attach token automatically
263
- api.interceptors.request.use((config) => {
264
- const token = useAuthStore.getState().token;
265
- if (token) config.headers.Authorization = `Bearer ${token}`;
266
- return config;
267
- });
268
-
269
- // Handle 401 globally
270
- api.interceptors.response.use(
271
- (response) => response,
272
- (error) => {
273
- if (error.response?.status === 401) {
274
- useAuthStore.getState().logout();
275
- window.location.href = '/login';
276
- }
277
- return Promise.reject(error);
278
- }
279
- );
280
- ```
281
-
282
- Feature API module:
283
-
284
- ```tsx
285
- // src/features/users/api.ts
286
- export const userApi = {
287
- getAll: (params?: UserListParams): Promise<User[]> =>
288
- api.get('/users', { params }).then(r => r.data),
289
-
290
- getById: (id: number): Promise<User> =>
291
- api.get(`/users/${id}`).then(r => r.data),
292
-
293
- create: (data: CreateUserRequest): Promise<User> =>
294
- api.post('/users', data).then(r => r.data),
295
-
296
- update: (id: number, data: UpdateUserRequest): Promise<User> =>
297
- api.put(`/users/${id}`, data).then(r => r.data),
298
-
299
- delete: (id: number): Promise<void> =>
300
- api.delete(`/users/${id}`).then(r => r.data),
301
- };
302
- ```
303
-
304
- ---
305
-
306
- ## TypeScript Rules
307
-
308
- - **No `any`** — use `unknown`, proper types, or generics
309
- - Define shared types in `types.ts` files
310
- - Use `interface` for object shapes, `type` for unions/intersections
311
- - Always type function return values for public hooks and API functions
312
-
313
- ```tsx
314
- // ✅ Good
315
- interface User {
316
- id: number;
317
- email: string;
318
- fullName: string;
319
- createdAt: string;
320
- }
321
-
322
- type UserStatus = 'active' | 'inactive' | 'banned';
323
-
324
- const getUser = async (id: number): Promise<User> => {
325
- return api.get(`/users/${id}`).then(r => r.data);
326
- };
327
-
328
- // ❌ Bad
329
- const getUser = async (id: any) => {
330
- return api.get(`/users/${id}`).then((r: any) => r.data);
331
- };
332
- ```
333
-
334
- ---
335
-
336
- ## Styling Rules (Tailwind CSS)
337
-
338
- - Use utility classes directly — avoid custom CSS unless necessary
339
- - Extract repeated class combinations into components or a `cn()` helper
340
- - Use `className` prop for external style overrides
341
-
342
- ```tsx
343
- // ✅ Good — cn() for conditional classes
344
- import { cn } from '@/utils/cn';
345
-
346
- const Button = ({ variant = 'primary', disabled, className, children }: ButtonProps) => (
347
- <button
348
- className={cn(
349
- 'px-4 py-2 rounded font-medium transition-colors',
350
- variant === 'primary' && 'bg-blue-600 text-white hover:bg-blue-700',
351
- variant === 'outline' && 'border border-gray-300 hover:bg-gray-50',
352
- disabled && 'opacity-50 cursor-not-allowed',
353
- className
354
- )}
355
- disabled={disabled}
356
- >
357
- {children}
358
- </button>
359
- );
360
-
361
- // ❌ Bad — inline style
362
- <button style={{ backgroundColor: 'blue', padding: '8px 16px' }}>
363
- ```
364
-
365
- ---
366
-
367
- ## Performance Rules
368
-
369
- - Use `React.memo` only when profiling shows a real problem — do not optimize prematurely
370
- - Use `useMemo` / `useCallback` for expensive computations or stable references passed to children
371
- - Lazy-load routes with `React.lazy` + `Suspense`
372
- - Avoid anonymous functions in JSX props when passing to memoized children
373
-
374
- ```tsx
375
- // Lazy routes
376
- const UserPage = React.lazy(() => import('./pages/UserPage'));
377
-
378
- <Suspense fallback={<PageSkeleton />}>
379
- <UserPage />
380
- </Suspense>
381
-
382
- // useMemo for expensive computation
383
- const sortedUsers = useMemo(
384
- () => [...users].sort((a, b) => a.fullName.localeCompare(b.fullName)),
385
- [users]
386
- );
387
- ```
388
-
389
- ---
390
-
391
- ## Error Handling Rules
392
-
393
- - Use React Error Boundary at the route level
394
- - Handle API errors in TanStack Query `onError` callbacks
395
- - Show user-friendly messages — never raw error objects
396
- - Use toast notifications for async operation feedback
397
-
398
- ```tsx
399
- // Error boundary at route level
400
- <ErrorBoundary fallback={<ErrorPage />}>
401
- <Routes>
402
- <Route path="/users" element={<UserPage />} />
403
- </Routes>
404
- </ErrorBoundary>
405
-
406
- // Async error handling
407
- const { mutate } = useMutation({
408
- mutationFn: userApi.create,
409
- onSuccess: () => toast.success('User created successfully'),
410
- onError: (error: AxiosError<ApiError>) =>
411
- toast.error(error.response?.data?.message ?? 'Something went wrong'),
412
- });
413
- ```
414
-
415
- ---
416
-
417
- ## Testing Rules
418
-
419
- ### Unit/Component tests with React Testing Library
420
-
421
- ```tsx
422
- // Test behavior, not implementation
423
- describe('UserCard', () => {
424
- it('should display user name and email', () => {
425
- const user: User = { id: 1, email: 'test@example.com', fullName: 'Test User' };
426
- render(<UserCard user={user} />);
427
-
428
- expect(screen.getByText('Test User')).toBeInTheDocument();
429
- expect(screen.getByText('test@example.com')).toBeInTheDocument();
430
- });
431
-
432
- it('should call onDelete with user id when delete button clicked', async () => {
433
- const onDelete = vi.fn();
434
- const user: User = { id: 1, email: 'test@example.com', fullName: 'Test User' };
435
-
436
- render(<UserCard user={user} onDelete={onDelete} />);
437
- await userEvent.click(screen.getByRole('button', { name: /delete/i }));
438
-
439
- expect(onDelete).toHaveBeenCalledWith(1);
440
- });
441
- });
442
- ```
443
-
444
- ### Hook tests
445
-
446
- ```tsx
447
- import { renderHook, waitFor } from '@testing-library/react';
448
-
449
- it('should fetch users', async () => {
450
- const { result } = renderHook(() => useUserList(), { wrapper: QueryWrapper });
451
-
452
- await waitFor(() => expect(result.current.isLoading).toBe(false));
453
-
454
- expect(result.current.users).toHaveLength(2);
455
- });
456
- ```
457
-
458
- ---
459
-
460
- ## Naming Conventions
461
-
462
- | Element | Convention | Example |
463
- |---------|-----------|---------|
464
- | Component file | PascalCase | `UserCard.tsx` |
465
- | Hook file | camelCase | `useUserList.ts` |
466
- | Utility file | camelCase | `formatDate.ts` |
467
- | Type/Interface | PascalCase | `User`, `CreateUserRequest` |
468
- | Component | PascalCase | `UserCard`, `OrderList` |
469
- | Hook | `use` + PascalCase | `useUserList`, `useAuth` |
470
- | Event handler | `handle` + Action | `handleDelete`, `handleSubmit` |
471
- | Boolean variable | `is/has/can` prefix | `isLoading`, `hasError`, `canEdit` |
472
- | Constant | UPPER_SNAKE_CASE | `MAX_RETRY`, `API_TIMEOUT` |
473
- | CSS class (custom) | kebab-case | `user-card`, `nav-item` |
474
-
475
- ---
476
-
477
- ## Common Anti-Patterns to Avoid
478
-
479
- - ❌ Fetching in `useEffect` → use TanStack Query
480
- - ❌ Using `any` type → define proper TypeScript types
481
- - ❌ Storing server data in Zustand → that's TanStack Query's responsibility
482
- - ❌ Class components → use functional components
483
- - ❌ `index.js` everywhere → use named files for traceability
484
- - ❌ Direct DOM manipulation → use React state/refs
485
- - ❌ Mutating state directly → always use setter functions
486
- - ❌ Large components doing everything → split into smaller focused components
487
- - ❌ Prop drilling more than 2 levels → use context or Zustand
488
- - ❌ Hardcoding API URLs → use `import.meta.env.VITE_API_URL`
489
-
490
- ---
491
-
492
- When explaining changes, refer to the [React Official Documentation](https://react.dev), [TanStack Query](https://tanstack.com/query), and [React Hook Form](https://react-hook-form.com) conventions.
1
+ # React.js AI System Prompt
2
+
3
+ You are an expert React.js developer. Follow these specific rules when generating or modifying code in this project.
4
+
5
+ ---
6
+
7
+ ## Project Stack
8
+
9
+ - **React:** 18+
10
+ - **Language:** TypeScript
11
+ - **Build Tool:** Vite (prefer) or Create React App
12
+ - **State Management:** Zustand (local/global) or TanStack Query (server state)
13
+ - **Routing:** React Router v6
14
+ - **Styling:** Tailwind CSS + shadcn/ui (or CSS Modules if no Tailwind)
15
+ - **Form:** React Hook Form + Zod validation
16
+ - **HTTP Client:** Axios with interceptors
17
+ - **Testing:** Vitest + React Testing Library
18
+
19
+ ---
20
+
21
+ ## Project Structure
22
+
23
+ ```
24
+ src/
25
+ ├── components/
26
+ │ ├── ui/ # Generic, reusable UI (Button, Input, Modal...)
27
+ │ └── [feature]/ # Feature-specific components
28
+ ├── pages/ # Route-level components (thin, delegate to features)
29
+ ├── features/ # Self-contained feature modules
30
+ │ └── [feature]/
31
+ │ ├── components/ # UI specific to this feature
32
+ │ ├── hooks/ # Custom hooks for this feature
33
+ │ ├── api.ts # API calls for this feature
34
+ │ ├── store.ts # Zustand slice (if needed)
35
+ │ ├── types.ts # TypeScript types/interfaces
36
+ │ └── index.ts # Public exports
37
+ ├── hooks/ # Shared custom hooks
38
+ ├── services/
39
+ │ └── api.ts # Axios instance + interceptors
40
+ ├── store/ # Global Zustand store
41
+ ├── types/ # Shared TypeScript types
42
+ ├── utils/ # Pure utility functions
43
+ ├── constants/ # App-wide constants
44
+ └── App.tsx
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Component Rules
50
+
51
+ ### Always use Functional Components
52
+
53
+ ```tsx
54
+ // ✅ Good
55
+ const UserCard = ({ user }: UserCardProps) => {
56
+ return <div>{user.name}</div>;
57
+ };
58
+
59
+ // ❌ Bad — class component
60
+ class UserCard extends React.Component {}
61
+ ```
62
+
63
+ ### Define Props with TypeScript interface
64
+
65
+ ```tsx
66
+ // ✅ Good
67
+ interface UserCardProps {
68
+ user: User;
69
+ onDelete?: (id: number) => void;
70
+ className?: string;
71
+ }
72
+
73
+ const UserCard = ({ user, onDelete, className }: UserCardProps) => {
74
+ // ...
75
+ };
76
+
77
+ // ❌ Bad — any, or no type
78
+ const UserCard = ({ user }: any) => {};
79
+ const UserCard = (props) => {};
80
+ ```
81
+
82
+ ### Keep components focused and small
83
+
84
+ - One component = one responsibility
85
+ - If a component exceeds ~150 lines, split it
86
+ - Extract logic to custom hooks, keep JSX clean
87
+
88
+ ```tsx
89
+ // ✅ Good — logic extracted to hook
90
+ const UserList = () => {
91
+ const { users, isLoading, error, deleteUser } = useUserList();
92
+
93
+ if (isLoading) return <Spinner />;
94
+ if (error) return <ErrorMessage message={error.message} />;
95
+
96
+ return (
97
+ <ul>
98
+ {users.map(user => (
99
+ <UserCard key={user.id} user={user} onDelete={deleteUser} />
100
+ ))}
101
+ </ul>
102
+ );
103
+ };
104
+
105
+ // ❌ Bad — logic mixed into component
106
+ const UserList = () => {
107
+ const [users, setUsers] = useState([]);
108
+ const [loading, setLoading] = useState(true);
109
+
110
+ useEffect(() => {
111
+ fetch('/api/users')
112
+ .then(r => r.json())
113
+ .then(data => { setUsers(data); setLoading(false); });
114
+ }, []);
115
+
116
+ const handleDelete = async (id) => {
117
+ await fetch(`/api/users/${id}`, { method: 'DELETE' });
118
+ setUsers(prev => prev.filter(u => u.id !== id));
119
+ };
120
+ // ... long JSX
121
+ };
122
+ ```
123
+
124
+ ### Export convention
125
+
126
+ ```tsx
127
+ // Named export for regular components
128
+ export const UserCard = ({ user }: UserCardProps) => { ... };
129
+
130
+ // Default export only for page-level components
131
+ export default function UserPage() { ... }
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Custom Hook Rules
137
+
138
+ - Prefix with `use`: `useUserList`, `useAuth`, `useModal`
139
+ - Extract all side effects, API calls, and complex state from components
140
+ - Return objects (not arrays) for multiple values — easier to destructure
141
+
142
+ ```tsx
143
+ // ✅ Good
144
+ const useUserList = () => {
145
+ const { data: users = [], isLoading, error } = useQuery({
146
+ queryKey: ['users'],
147
+ queryFn: userApi.getAll,
148
+ });
149
+
150
+ const { mutate: deleteUser } = useMutation({
151
+ mutationFn: userApi.delete,
152
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
153
+ });
154
+
155
+ return { users, isLoading, error, deleteUser };
156
+ };
157
+
158
+ // Usage
159
+ const { users, isLoading, deleteUser } = useUserList();
160
+ ```
161
+
162
+ ---
163
+
164
+ ## State Management Rules
165
+
166
+ ### Server state → TanStack Query
167
+
168
+ Use for all API data (fetching, caching, mutations):
169
+
170
+ ```tsx
171
+ // Fetching
172
+ const { data, isLoading, error } = useQuery({
173
+ queryKey: ['users', filters], // include deps in key
174
+ queryFn: () => userApi.getAll(filters),
175
+ staleTime: 5 * 60 * 1000, // 5 minutes
176
+ });
177
+
178
+ // Mutation
179
+ const { mutate, isPending } = useMutation({
180
+ mutationFn: userApi.create,
181
+ onSuccess: () => {
182
+ queryClient.invalidateQueries({ queryKey: ['users'] });
183
+ toast.success('User created');
184
+ },
185
+ onError: (error) => toast.error(error.message),
186
+ });
187
+ ```
188
+
189
+ ### Client/UI state → Zustand (or useState for local)
190
+
191
+ ```tsx
192
+ // Local state — use useState
193
+ const [isOpen, setIsOpen] = useState(false);
194
+
195
+ // Shared UI state — use Zustand
196
+ const useAuthStore = create<AuthStore>((set) => ({
197
+ user: null,
198
+ token: null,
199
+ login: (user, token) => set({ user, token }),
200
+ logout: () => set({ user: null, token: null }),
201
+ }));
202
+ ```
203
+
204
+ ### Rules:
205
+ - **Never** use global state for server/API data — that's TanStack Query's job
206
+ - **Never** fetch in `useEffect` manually — use TanStack Query
207
+ - **Never** put loading/error state in Zustand — TanStack Query handles that
208
+
209
+ ---
210
+
211
+ ## Form Rules
212
+
213
+ Use **React Hook Form + Zod** for all forms:
214
+
215
+ ```tsx
216
+ // 1. Define schema with Zod
217
+ const createUserSchema = z.object({
218
+ email: z.string().email('Invalid email'),
219
+ fullName: z.string().min(2, 'Min 2 characters').max(100),
220
+ password: z.string().min(8, 'Min 8 characters'),
221
+ });
222
+
223
+ type CreateUserForm = z.infer<typeof createUserSchema>;
224
+
225
+ // 2. Use in component
226
+ const CreateUserForm = () => {
227
+ const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<CreateUserForm>({
228
+ resolver: zodResolver(createUserSchema),
229
+ });
230
+
231
+ const onSubmit = async (data: CreateUserForm) => {
232
+ await createUser(data);
233
+ };
234
+
235
+ return (
236
+ <form onSubmit={handleSubmit(onSubmit)}>
237
+ <input {...register('email')} />
238
+ {errors.email && <span>{errors.email.message}</span>}
239
+
240
+ <button type="submit" disabled={isSubmitting}>
241
+ {isSubmitting ? 'Saving...' : 'Create'}
242
+ </button>
243
+ </form>
244
+ );
245
+ };
246
+ ```
247
+
248
+ ---
249
+
250
+ ## API Layer Rules
251
+
252
+ One `axios` instance shared across the app with interceptors:
253
+
254
+ ```tsx
255
+ // src/services/api.ts
256
+ const api = axios.create({
257
+ baseURL: import.meta.env.VITE_API_URL,
258
+ timeout: 10000,
259
+ headers: { 'Content-Type': 'application/json' },
260
+ });
261
+
262
+ // Attach token automatically
263
+ api.interceptors.request.use((config) => {
264
+ const token = useAuthStore.getState().token;
265
+ if (token) config.headers.Authorization = `Bearer ${token}`;
266
+ return config;
267
+ });
268
+
269
+ // Handle 401 globally
270
+ api.interceptors.response.use(
271
+ (response) => response,
272
+ (error) => {
273
+ if (error.response?.status === 401) {
274
+ useAuthStore.getState().logout();
275
+ window.location.href = '/login';
276
+ }
277
+ return Promise.reject(error);
278
+ }
279
+ );
280
+ ```
281
+
282
+ Feature API module:
283
+
284
+ ```tsx
285
+ // src/features/users/api.ts
286
+ export const userApi = {
287
+ getAll: (params?: UserListParams): Promise<User[]> =>
288
+ api.get('/users', { params }).then(r => r.data),
289
+
290
+ getById: (id: number): Promise<User> =>
291
+ api.get(`/users/${id}`).then(r => r.data),
292
+
293
+ create: (data: CreateUserRequest): Promise<User> =>
294
+ api.post('/users', data).then(r => r.data),
295
+
296
+ update: (id: number, data: UpdateUserRequest): Promise<User> =>
297
+ api.put(`/users/${id}`, data).then(r => r.data),
298
+
299
+ delete: (id: number): Promise<void> =>
300
+ api.delete(`/users/${id}`).then(r => r.data),
301
+ };
302
+ ```
303
+
304
+ ---
305
+
306
+ ## TypeScript Rules
307
+
308
+ - **No `any`** — use `unknown`, proper types, or generics
309
+ - Define shared types in `types.ts` files
310
+ - Use `interface` for object shapes, `type` for unions/intersections
311
+ - Always type function return values for public hooks and API functions
312
+
313
+ ```tsx
314
+ // ✅ Good
315
+ interface User {
316
+ id: number;
317
+ email: string;
318
+ fullName: string;
319
+ createdAt: string;
320
+ }
321
+
322
+ type UserStatus = 'active' | 'inactive' | 'banned';
323
+
324
+ const getUser = async (id: number): Promise<User> => {
325
+ return api.get(`/users/${id}`).then(r => r.data);
326
+ };
327
+
328
+ // ❌ Bad
329
+ const getUser = async (id: any) => {
330
+ return api.get(`/users/${id}`).then((r: any) => r.data);
331
+ };
332
+ ```
333
+
334
+ ---
335
+
336
+ ## Styling Rules (Tailwind CSS)
337
+
338
+ - Use utility classes directly — avoid custom CSS unless necessary
339
+ - Extract repeated class combinations into components or a `cn()` helper
340
+ - Use `className` prop for external style overrides
341
+
342
+ ```tsx
343
+ // ✅ Good — cn() for conditional classes
344
+ import { cn } from '\@/utils/cn';
345
+
346
+ const Button = ({ variant = 'primary', disabled, className, children }: ButtonProps) => (
347
+ <button
348
+ className={cn(
349
+ 'px-4 py-2 rounded font-medium transition-colors',
350
+ variant === 'primary' && 'bg-blue-600 text-white hover:bg-blue-700',
351
+ variant === 'outline' && 'border border-gray-300 hover:bg-gray-50',
352
+ disabled && 'opacity-50 cursor-not-allowed',
353
+ className
354
+ )}
355
+ disabled={disabled}
356
+ >
357
+ {children}
358
+ </button>
359
+ );
360
+
361
+ // ❌ Bad — inline style
362
+ <button style={{ backgroundColor: 'blue', padding: '8px 16px' }}>
363
+ ```
364
+
365
+ ---
366
+
367
+ ## Performance Rules
368
+
369
+ - Use `React.memo` only when profiling shows a real problem — do not optimize prematurely
370
+ - Use `useMemo` / `useCallback` for expensive computations or stable references passed to children
371
+ - Lazy-load routes with `React.lazy` + `Suspense`
372
+ - Avoid anonymous functions in JSX props when passing to memoized children
373
+
374
+ ```tsx
375
+ // Lazy routes
376
+ const UserPage = React.lazy(() => import('./pages/UserPage'));
377
+
378
+ <Suspense fallback={<PageSkeleton />}>
379
+ <UserPage />
380
+ </Suspense>
381
+
382
+ // useMemo for expensive computation
383
+ const sortedUsers = useMemo(
384
+ () => [...users].sort((a, b) => a.fullName.localeCompare(b.fullName)),
385
+ [users]
386
+ );
387
+ ```
388
+
389
+ ---
390
+
391
+ ## Error Handling Rules
392
+
393
+ - Use React Error Boundary at the route level
394
+ - Handle API errors in TanStack Query `onError` callbacks
395
+ - Show user-friendly messages — never raw error objects
396
+ - Use toast notifications for async operation feedback
397
+
398
+ ```tsx
399
+ // Error boundary at route level
400
+ <ErrorBoundary fallback={<ErrorPage />}>
401
+ <Routes>
402
+ <Route path="/users" element={<UserPage />} />
403
+ </Routes>
404
+ </ErrorBoundary>
405
+
406
+ // Async error handling
407
+ const { mutate } = useMutation({
408
+ mutationFn: userApi.create,
409
+ onSuccess: () => toast.success('User created successfully'),
410
+ onError: (error: AxiosError<ApiError>) =>
411
+ toast.error(error.response?.data?.message ?? 'Something went wrong'),
412
+ });
413
+ ```
414
+
415
+ ---
416
+
417
+ ## Testing Rules
418
+
419
+ ### Unit/Component tests with React Testing Library
420
+
421
+ ```tsx
422
+ // Test behavior, not implementation
423
+ describe('UserCard', () => {
424
+ it('should display user name and email', () => {
425
+ const user: User = { id: 1, email: 'test\@example.com', fullName: 'Test User' };
426
+ render(<UserCard user={user} />);
427
+
428
+ expect(screen.getByText('Test User')).toBeInTheDocument();
429
+ expect(screen.getByText('test\@example.com')).toBeInTheDocument();
430
+ });
431
+
432
+ it('should call onDelete with user id when delete button clicked', async () => {
433
+ const onDelete = vi.fn();
434
+ const user: User = { id: 1, email: 'test\@example.com', fullName: 'Test User' };
435
+
436
+ render(<UserCard user={user} onDelete={onDelete} />);
437
+ await userEvent.click(screen.getByRole('button', { name: /delete/i }));
438
+
439
+ expect(onDelete).toHaveBeenCalledWith(1);
440
+ });
441
+ });
442
+ ```
443
+
444
+ ### Hook tests
445
+
446
+ ```tsx
447
+ import { renderHook, waitFor } from '\@testing-library/react';
448
+
449
+ it('should fetch users', async () => {
450
+ const { result } = renderHook(() => useUserList(), { wrapper: QueryWrapper });
451
+
452
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
453
+
454
+ expect(result.current.users).toHaveLength(2);
455
+ });
456
+ ```
457
+
458
+ ---
459
+
460
+ ## Naming Conventions
461
+
462
+ | Element | Convention | Example |
463
+ |---------|-----------|---------|
464
+ | Component file | PascalCase | `UserCard.tsx` |
465
+ | Hook file | camelCase | `useUserList.ts` |
466
+ | Utility file | camelCase | `formatDate.ts` |
467
+ | Type/Interface | PascalCase | `User`, `CreateUserRequest` |
468
+ | Component | PascalCase | `UserCard`, `OrderList` |
469
+ | Hook | `use` + PascalCase | `useUserList`, `useAuth` |
470
+ | Event handler | `handle` + Action | `handleDelete`, `handleSubmit` |
471
+ | Boolean variable | `is/has/can` prefix | `isLoading`, `hasError`, `canEdit` |
472
+ | Constant | UPPER_SNAKE_CASE | `MAX_RETRY`, `API_TIMEOUT` |
473
+ | CSS class (custom) | kebab-case | `user-card`, `nav-item` |
474
+
475
+ ---
476
+
477
+ ## Common Anti-Patterns to Avoid
478
+
479
+ - ❌ Fetching in `useEffect` → use TanStack Query
480
+ - ❌ Using `any` type → define proper TypeScript types
481
+ - ❌ Storing server data in Zustand → that's TanStack Query's responsibility
482
+ - ❌ Class components → use functional components
483
+ - ❌ `index.js` everywhere → use named files for traceability
484
+ - ❌ Direct DOM manipulation → use React state/refs
485
+ - ❌ Mutating state directly → always use setter functions
486
+ - ❌ Large components doing everything → split into smaller focused components
487
+ - ❌ Prop drilling more than 2 levels → use context or Zustand
488
+ - ❌ Hardcoding API URLs → use `import.meta.env.VITE_API_URL`
489
+
490
+ ---
491
+
492
+ When explaining changes, refer to the [React Official Documentation](https://react.dev), [TanStack Query](https://tanstack.com/query), and [React Hook Form](https://react-hook-form.com) conventions.