@vpxa/aikit 0.1.54 → 0.1.56
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/package.json +1 -1
- package/packages/cli/dist/commands/init/templates.js +5 -1
- package/packages/server/dist/tools/graph.tool.js +20 -1
- package/packages/server/dist/tools/status.tool.js +1 -1
- package/scaffold/adapters/copilot.mjs +4 -1
- package/scaffold/definitions/agents.mjs +40 -0
- package/scaffold/definitions/bodies.mjs +72 -4
- package/scaffold/definitions/protocols.mjs +77 -0
- package/scaffold/definitions/tools.mjs +6 -0
- package/scaffold/general/agents/Architect-Reviewer-Alpha.agent.md +44 -0
- package/scaffold/general/agents/Architect-Reviewer-Beta.agent.md +44 -0
- package/scaffold/general/agents/Code-Reviewer-Alpha.agent.md +23 -0
- package/scaffold/general/agents/Code-Reviewer-Beta.agent.md +23 -0
- package/scaffold/general/agents/Frontend.agent.md +31 -0
- package/scaffold/general/agents/Implementer.agent.md +14 -0
- package/scaffold/general/agents/Orchestrator.agent.md +5 -3
- package/scaffold/general/agents/Planner.agent.md +1 -1
- package/scaffold/general/agents/Refactor.agent.md +21 -0
- package/scaffold/general/agents/Researcher-Alpha.agent.md +20 -0
- package/scaffold/general/agents/Researcher-Beta.agent.md +21 -0
- package/scaffold/general/agents/Researcher-Delta.agent.md +22 -0
- package/scaffold/general/agents/Researcher-Gamma.agent.md +21 -0
- package/scaffold/general/agents/_shared/architect-reviewer-base.md +44 -0
- package/scaffold/general/agents/_shared/code-reviewer-base.md +23 -0
- package/scaffold/general/agents/_shared/researcher-base.md +10 -0
- package/scaffold/general/skills/adr-skill/SKILL.md +6 -6
- package/scaffold/general/skills/aikit/SKILL.md +22 -0
- package/scaffold/general/skills/frontend-design/SKILL.md +237 -237
- package/scaffold/general/skills/lesson-learned/SKILL.md +7 -7
- package/scaffold/general/skills/react/SKILL.md +309 -309
- package/scaffold/general/skills/requirements-clarity/SKILL.md +6 -6
- package/scaffold/general/skills/session-handoff/SKILL.md +7 -7
- package/scaffold/general/skills/typescript/SKILL.md +405 -405
|
@@ -1,309 +1,309 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: react
|
|
3
|
-
description: "Comprehensive React development patterns — component architecture, React 19 APIs, Server Components, TypeScript integration, and performance optimization."
|
|
4
|
-
metadata:
|
|
5
|
-
category: domain
|
|
6
|
-
domain: react
|
|
7
|
-
applicability: on-demand
|
|
8
|
-
inputs: [codebase, requirements]
|
|
9
|
-
outputs: [component-patterns, code]
|
|
10
|
-
relatedSkills: [typescript, frontend-design]
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# React
|
|
14
|
-
|
|
15
|
-
> Comprehensive React development patterns — component architecture, React 19 APIs, Server Components, TypeScript integration, and performance optimization. Synthesized from react-dev patterns, react-patterns (React 19), and Vercel React best practices.
|
|
16
|
-
|
|
17
|
-
## When to Use
|
|
18
|
-
|
|
19
|
-
**MANDATORY** for the Frontend agent on every React task. Also use when:
|
|
20
|
-
- Building React components, hooks, or pages
|
|
21
|
-
- Working with Server Components or Server Actions
|
|
22
|
-
- Optimizing React performance (re-renders, bundle size, data fetching)
|
|
23
|
-
- Reviewing React code for correctness and best practices
|
|
24
|
-
|
|
25
|
-
## React 19 Quick Reference
|
|
26
|
-
|
|
27
|
-
| API | Use Case | Key Change |
|
|
28
|
-
|-----|----------|------------|
|
|
29
|
-
| `ref` as prop | Access DOM/component instance | No more `forwardRef` wrapper |
|
|
30
|
-
| `use()` | Read promises/context in render | Replaces `useContext`, enables async |
|
|
31
|
-
| `useActionState` | Form submission state + error | Replaces `useFormState` |
|
|
32
|
-
| `useOptimistic` | Instant UI feedback | Optimistic updates before server confirms |
|
|
33
|
-
| `useTransition` | Non-urgent state updates | `isPending` for loading states |
|
|
34
|
-
| `<form action>` | Server Actions in forms | Direct server function binding |
|
|
35
|
-
| Server Components | Zero-bundle components | Default in App Router, no `"use client"` |
|
|
36
|
-
| Server Actions | Server mutations | `"use server"` functions, form actions |
|
|
37
|
-
|
|
38
|
-
## Component Patterns
|
|
39
|
-
|
|
40
|
-
### Extend Native Elements
|
|
41
|
-
```tsx
|
|
42
|
-
type ButtonProps = React.ComponentProps<"button"> & {
|
|
43
|
-
variant?: "primary" | "secondary" | "ghost";
|
|
44
|
-
size?: "sm" | "md" | "lg";
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
function Button({ variant = "primary", size = "md", className, ...props }: ButtonProps) {
|
|
48
|
-
return <button className={cn(variants[variant], sizes[size], className)} {...props} />;
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Discriminated Union Props
|
|
53
|
-
```tsx
|
|
54
|
-
type ModalProps =
|
|
55
|
-
| { variant: "alert"; message: string; onConfirm: () => void }
|
|
56
|
-
| { variant: "form"; children: React.ReactNode; onSubmit: (data: FormData) => void };
|
|
57
|
-
|
|
58
|
-
function Modal(props: ModalProps) {
|
|
59
|
-
switch (props.variant) {
|
|
60
|
-
case "alert": return <AlertModal {...props} />;
|
|
61
|
-
case "form": return <FormModal {...props} />;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Generic Components
|
|
67
|
-
```tsx
|
|
68
|
-
type TableProps<T> = {
|
|
69
|
-
data: T[];
|
|
70
|
-
columns: { key: keyof T; header: string; render?: (value: T[keyof T], row: T) => React.ReactNode }[];
|
|
71
|
-
onRowClick?: (row: T) => void;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
function Table<T extends Record<string, unknown>>({ data, columns, onRowClick }: TableProps<T>) {
|
|
75
|
-
// ...
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Children Typing
|
|
80
|
-
```tsx
|
|
81
|
-
// Specific children
|
|
82
|
-
type Props = { children: React.ReactElement<TabProps>[] };
|
|
83
|
-
|
|
84
|
-
// Render function children
|
|
85
|
-
type Props = { children: (data: T) => React.ReactNode };
|
|
86
|
-
|
|
87
|
-
// String only
|
|
88
|
-
type Props = { children: string };
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Ref as Prop (React 19)
|
|
92
|
-
|
|
93
|
-
```tsx
|
|
94
|
-
// React 19: pass ref directly, no forwardRef needed
|
|
95
|
-
function Input({ ref, ...props }: React.ComponentProps<"input">) {
|
|
96
|
-
return <input ref={ref} {...props} />;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Cleanup function (new in React 19)
|
|
100
|
-
function MeasuredDiv({ ref, ...props }: React.ComponentProps<"div">) {
|
|
101
|
-
return (
|
|
102
|
-
<div
|
|
103
|
-
ref={(node) => {
|
|
104
|
-
if (node) measureElement(node);
|
|
105
|
-
return () => cleanupMeasurement(); // cleanup on unmount
|
|
106
|
-
}}
|
|
107
|
-
{...props}
|
|
108
|
-
/>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## Server Components & Actions
|
|
114
|
-
|
|
115
|
-
### Server Component (default — no directive needed)
|
|
116
|
-
```tsx
|
|
117
|
-
// app/users/page.tsx — Server Component (default)
|
|
118
|
-
import { db } from "@/lib/db";
|
|
119
|
-
|
|
120
|
-
export default async function UsersPage() {
|
|
121
|
-
const users = await db.query.users.findMany(); // Direct DB access
|
|
122
|
-
return <UserList users={users} />; // Pass data to client
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Client Component (explicit opt-in)
|
|
127
|
-
```tsx
|
|
128
|
-
"use client";
|
|
129
|
-
import { useState, useOptimistic, useTransition } from "react";
|
|
130
|
-
|
|
131
|
-
export function Counter({ initialCount }: { initialCount: number }) {
|
|
132
|
-
const [count, setCount] = useState(initialCount);
|
|
133
|
-
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Server Action with Form
|
|
138
|
-
```tsx
|
|
139
|
-
// actions.ts
|
|
140
|
-
"use server";
|
|
141
|
-
import { z } from "zod";
|
|
142
|
-
|
|
143
|
-
const schema = z.object({ name: z.string().min(1), email: z.string().email() });
|
|
144
|
-
|
|
145
|
-
export async function createUser(_prev: unknown, formData: FormData) {
|
|
146
|
-
const result = schema.safeParse(Object.fromEntries(formData));
|
|
147
|
-
if (!result.success) return { error: result.error.flatten().fieldErrors };
|
|
148
|
-
await db.insert(users).values(result.data);
|
|
149
|
-
return { success: true };
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
```tsx
|
|
154
|
-
"use client";
|
|
155
|
-
import { useActionState } from "react";
|
|
156
|
-
import { createUser } from "./actions";
|
|
157
|
-
|
|
158
|
-
export function CreateUserForm() {
|
|
159
|
-
const [state, action, isPending] = useActionState(createUser, null);
|
|
160
|
-
return (
|
|
161
|
-
<form action={action}>
|
|
162
|
-
<input name="name" required />
|
|
163
|
-
{state?.error?.name && <p className="text-red-500">{state.error.name}</p>}
|
|
164
|
-
<input name="email" type="email" required />
|
|
165
|
-
{state?.error?.email && <p className="text-red-500">{state.error.email}</p>}
|
|
166
|
-
<button disabled={isPending}>{isPending ? "Creating..." : "Create"}</button>
|
|
167
|
-
</form>
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
### Optimistic Updates
|
|
173
|
-
```tsx
|
|
174
|
-
"use client";
|
|
175
|
-
import { useOptimistic } from "react";
|
|
176
|
-
|
|
177
|
-
function TodoList({ todos, addTodo }: { todos: Todo[]; addTodo: (text: string) => Promise<void> }) {
|
|
178
|
-
const [optimisticTodos, addOptimistic] = useOptimistic(
|
|
179
|
-
todos,
|
|
180
|
-
(state, newTodo: string) => [...state, { id: "temp", text: newTodo, pending: true }]
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
async function handleAdd(formData: FormData) {
|
|
184
|
-
const text = formData.get("text") as string;
|
|
185
|
-
addOptimistic(text);
|
|
186
|
-
await addTodo(text);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return (
|
|
190
|
-
<form action={handleAdd}>
|
|
191
|
-
<input name="text" />
|
|
192
|
-
<button type="submit">Add</button>
|
|
193
|
-
<ul>
|
|
194
|
-
{optimisticTodos.map(t => (
|
|
195
|
-
<li key={t.id} style={{ opacity: t.pending ? 0.5 : 1 }}>{t.text}</li>
|
|
196
|
-
))}
|
|
197
|
-
</ul>
|
|
198
|
-
</form>
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
## Event Handler Types
|
|
204
|
-
|
|
205
|
-
```tsx
|
|
206
|
-
// Always use specific event types
|
|
207
|
-
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
|
|
208
|
-
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
|
|
209
|
-
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void
|
|
210
|
-
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void
|
|
211
|
-
onFocus: (e: React.FocusEvent<HTMLInputElement>) => void
|
|
212
|
-
onDrag: (e: React.DragEvent<HTMLDivElement>) => void
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
## Hooks Typing
|
|
216
|
-
|
|
217
|
-
```tsx
|
|
218
|
-
// useState with union
|
|
219
|
-
const [status, setStatus] = useState<"idle" | "loading" | "error">("idle");
|
|
220
|
-
|
|
221
|
-
// useRef for DOM — pass null, get RefObject
|
|
222
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
223
|
-
|
|
224
|
-
// useRef for mutable value — no null, get MutableRefObject
|
|
225
|
-
const intervalRef = useRef<number>(0);
|
|
226
|
-
|
|
227
|
-
// useReducer with discriminated actions
|
|
228
|
-
type Action = { type: "increment" } | { type: "set"; payload: number };
|
|
229
|
-
const [count, dispatch] = useReducer((state: number, action: Action) => {
|
|
230
|
-
switch (action.type) {
|
|
231
|
-
case "increment": return state + 1;
|
|
232
|
-
case "set": return action.payload;
|
|
233
|
-
}
|
|
234
|
-
}, 0);
|
|
235
|
-
|
|
236
|
-
// Custom hook: return as const for tuple types
|
|
237
|
-
function useToggle(initial = false) {
|
|
238
|
-
const [value, setValue] = useState(initial);
|
|
239
|
-
const toggle = useCallback(() => setValue(v => !v), []);
|
|
240
|
-
return [value, toggle] as const;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// useContext with null guard
|
|
244
|
-
const ctx = useContext(ThemeContext);
|
|
245
|
-
if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
## Performance Rules
|
|
249
|
-
|
|
250
|
-
### Prevent Async Waterfalls
|
|
251
|
-
```tsx
|
|
252
|
-
// BAD — sequential fetching
|
|
253
|
-
const user = await getUser(id);
|
|
254
|
-
const posts = await getPosts(user.id); // Waits for user first
|
|
255
|
-
const comments = await getComments(posts[0].id); // Waits for posts
|
|
256
|
-
|
|
257
|
-
// GOOD — parallel where possible
|
|
258
|
-
const [user, posts] = await Promise.all([getUser(id), getPosts(id)]);
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
### Bundle Size
|
|
262
|
-
- `dynamic(() => import("./HeavyComponent"))` — lazy load below-fold components
|
|
263
|
-
- Tree-shake: use named exports, avoid `import *`
|
|
264
|
-
- Analyze bundle: `@next/bundle-analyzer` to find bloat
|
|
265
|
-
- Code-split by route — each page loads only its own JS
|
|
266
|
-
- Avoid importing entire libraries: `import { debounce } from "lodash-es"` not `import _ from "lodash"`
|
|
267
|
-
|
|
268
|
-
### Server-Side Performance
|
|
269
|
-
- Default to Server Components — zero JS shipped to client
|
|
270
|
-
- Stream with Suspense: wrap slow data fetchers in `<Suspense fallback={<Skeleton />}>`
|
|
271
|
-
- Cache expensive computations with `"use cache"` directive
|
|
272
|
-
- Avoid serializing large objects across server/client boundary
|
|
273
|
-
|
|
274
|
-
### Client-Side Data Fetching
|
|
275
|
-
- Use SWR or React Query — automatic caching, revalidation, error retry
|
|
276
|
-
- Stale-while-revalidate: show cached data instantly, refresh in background
|
|
277
|
-
- Error boundaries: `<ErrorBoundary>` wrapping data-dependent trees
|
|
278
|
-
- Prefetch on hover/viewport for likely next navigations
|
|
279
|
-
|
|
280
|
-
### Re-render Optimization
|
|
281
|
-
- React Compiler (React 19): auto-memoizes — try this first before manual `useMemo`/`useCallback`
|
|
282
|
-
- If no compiler: `useMemo` for expensive computations, `useCallback` for stable function references
|
|
283
|
-
- Extract static JSX outside components — constants don't re-create
|
|
284
|
-
- Split context: separate frequently-changing state from rarely-changing state
|
|
285
|
-
- `React.memo()` on components receiving only primitive props
|
|
286
|
-
|
|
287
|
-
### Rendering Performance
|
|
288
|
-
- **Virtualize** lists > 100 items: `@tanstack/react-virtual`, `react-window`
|
|
289
|
-
- Defer heavy computations with `useDeferredValue`
|
|
290
|
-
- Batch state updates (React 18+ does this automatically in event handlers)
|
|
291
|
-
- Avoid reading DOM layout during renders (force sync layout = jank)
|
|
292
|
-
|
|
293
|
-
### Advanced Patterns
|
|
294
|
-
- Progressive hydration: load and hydrate components as they enter viewport
|
|
295
|
-
- Concurrent features: `useTransition` for non-blocking state updates
|
|
296
|
-
- Suspense caching: combine `<Suspense>` with data cache for instant page transitions
|
|
297
|
-
- React Compiler adoption: enable gradually, check for unsafe patterns first
|
|
298
|
-
|
|
299
|
-
## Decision Flow
|
|
300
|
-
|
|
301
|
-
When implementing a React feature:
|
|
302
|
-
|
|
303
|
-
1. **Server or Client?** → If it uses browser APIs, event handlers, or useState → Client. Everything else → Server.
|
|
304
|
-
2. **Data fetching?** → Server Component for initial data. SWR/React Query for client-side mutations.
|
|
305
|
-
3. **Form?** → Server Action + `useActionState` + Zod validation.
|
|
306
|
-
4. **Optimistic?** → `useOptimistic` for instant feedback.
|
|
307
|
-
5. **Loading?** → `<Suspense>` with skeleton fallback.
|
|
308
|
-
6. **Performance?** → Try React Compiler first. Then manual memo. Then virtualization. Then code splitting.
|
|
309
|
-
7. **Error?** → Error boundaries for rendering errors. try/catch for Server Actions.
|
|
1
|
+
---
|
|
2
|
+
name: react
|
|
3
|
+
description: "Comprehensive React development patterns — component architecture, React 19 APIs, Server Components, TypeScript integration, and performance optimization."
|
|
4
|
+
metadata:
|
|
5
|
+
category: domain
|
|
6
|
+
domain: react
|
|
7
|
+
applicability: on-demand
|
|
8
|
+
inputs: [codebase, requirements]
|
|
9
|
+
outputs: [component-patterns, code]
|
|
10
|
+
relatedSkills: [typescript, frontend-design]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# React
|
|
14
|
+
|
|
15
|
+
> Comprehensive React development patterns — component architecture, React 19 APIs, Server Components, TypeScript integration, and performance optimization. Synthesized from react-dev patterns, react-patterns (React 19), and Vercel React best practices.
|
|
16
|
+
|
|
17
|
+
## When to Use
|
|
18
|
+
|
|
19
|
+
**MANDATORY** for the Frontend agent on every React task. Also use when:
|
|
20
|
+
- Building React components, hooks, or pages
|
|
21
|
+
- Working with Server Components or Server Actions
|
|
22
|
+
- Optimizing React performance (re-renders, bundle size, data fetching)
|
|
23
|
+
- Reviewing React code for correctness and best practices
|
|
24
|
+
|
|
25
|
+
## React 19 Quick Reference
|
|
26
|
+
|
|
27
|
+
| API | Use Case | Key Change |
|
|
28
|
+
|-----|----------|------------|
|
|
29
|
+
| `ref` as prop | Access DOM/component instance | No more `forwardRef` wrapper |
|
|
30
|
+
| `use()` | Read promises/context in render | Replaces `useContext`, enables async |
|
|
31
|
+
| `useActionState` | Form submission state + error | Replaces `useFormState` |
|
|
32
|
+
| `useOptimistic` | Instant UI feedback | Optimistic updates before server confirms |
|
|
33
|
+
| `useTransition` | Non-urgent state updates | `isPending` for loading states |
|
|
34
|
+
| `<form action>` | Server Actions in forms | Direct server function binding |
|
|
35
|
+
| Server Components | Zero-bundle components | Default in App Router, no `"use client"` |
|
|
36
|
+
| Server Actions | Server mutations | `"use server"` functions, form actions |
|
|
37
|
+
|
|
38
|
+
## Component Patterns
|
|
39
|
+
|
|
40
|
+
### Extend Native Elements
|
|
41
|
+
```tsx
|
|
42
|
+
type ButtonProps = React.ComponentProps<"button"> & {
|
|
43
|
+
variant?: "primary" | "secondary" | "ghost";
|
|
44
|
+
size?: "sm" | "md" | "lg";
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function Button({ variant = "primary", size = "md", className, ...props }: ButtonProps) {
|
|
48
|
+
return <button className={cn(variants[variant], sizes[size], className)} {...props} />;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Discriminated Union Props
|
|
53
|
+
```tsx
|
|
54
|
+
type ModalProps =
|
|
55
|
+
| { variant: "alert"; message: string; onConfirm: () => void }
|
|
56
|
+
| { variant: "form"; children: React.ReactNode; onSubmit: (data: FormData) => void };
|
|
57
|
+
|
|
58
|
+
function Modal(props: ModalProps) {
|
|
59
|
+
switch (props.variant) {
|
|
60
|
+
case "alert": return <AlertModal {...props} />;
|
|
61
|
+
case "form": return <FormModal {...props} />;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Generic Components
|
|
67
|
+
```tsx
|
|
68
|
+
type TableProps<T> = {
|
|
69
|
+
data: T[];
|
|
70
|
+
columns: { key: keyof T; header: string; render?: (value: T[keyof T], row: T) => React.ReactNode }[];
|
|
71
|
+
onRowClick?: (row: T) => void;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function Table<T extends Record<string, unknown>>({ data, columns, onRowClick }: TableProps<T>) {
|
|
75
|
+
// ...
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Children Typing
|
|
80
|
+
```tsx
|
|
81
|
+
// Specific children
|
|
82
|
+
type Props = { children: React.ReactElement<TabProps>[] };
|
|
83
|
+
|
|
84
|
+
// Render function children
|
|
85
|
+
type Props = { children: (data: T) => React.ReactNode };
|
|
86
|
+
|
|
87
|
+
// String only
|
|
88
|
+
type Props = { children: string };
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Ref as Prop (React 19)
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
// React 19: pass ref directly, no forwardRef needed
|
|
95
|
+
function Input({ ref, ...props }: React.ComponentProps<"input">) {
|
|
96
|
+
return <input ref={ref} {...props} />;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Cleanup function (new in React 19)
|
|
100
|
+
function MeasuredDiv({ ref, ...props }: React.ComponentProps<"div">) {
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
ref={(node) => {
|
|
104
|
+
if (node) measureElement(node);
|
|
105
|
+
return () => cleanupMeasurement(); // cleanup on unmount
|
|
106
|
+
}}
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Server Components & Actions
|
|
114
|
+
|
|
115
|
+
### Server Component (default — no directive needed)
|
|
116
|
+
```tsx
|
|
117
|
+
// app/users/page.tsx — Server Component (default)
|
|
118
|
+
import { db } from "@/lib/db";
|
|
119
|
+
|
|
120
|
+
export default async function UsersPage() {
|
|
121
|
+
const users = await db.query.users.findMany(); // Direct DB access
|
|
122
|
+
return <UserList users={users} />; // Pass data to client
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Client Component (explicit opt-in)
|
|
127
|
+
```tsx
|
|
128
|
+
"use client";
|
|
129
|
+
import { useState, useOptimistic, useTransition } from "react";
|
|
130
|
+
|
|
131
|
+
export function Counter({ initialCount }: { initialCount: number }) {
|
|
132
|
+
const [count, setCount] = useState(initialCount);
|
|
133
|
+
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Server Action with Form
|
|
138
|
+
```tsx
|
|
139
|
+
// actions.ts
|
|
140
|
+
"use server";
|
|
141
|
+
import { z } from "zod";
|
|
142
|
+
|
|
143
|
+
const schema = z.object({ name: z.string().min(1), email: z.string().email() });
|
|
144
|
+
|
|
145
|
+
export async function createUser(_prev: unknown, formData: FormData) {
|
|
146
|
+
const result = schema.safeParse(Object.fromEntries(formData));
|
|
147
|
+
if (!result.success) return { error: result.error.flatten().fieldErrors };
|
|
148
|
+
await db.insert(users).values(result.data);
|
|
149
|
+
return { success: true };
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
"use client";
|
|
155
|
+
import { useActionState } from "react";
|
|
156
|
+
import { createUser } from "./actions";
|
|
157
|
+
|
|
158
|
+
export function CreateUserForm() {
|
|
159
|
+
const [state, action, isPending] = useActionState(createUser, null);
|
|
160
|
+
return (
|
|
161
|
+
<form action={action}>
|
|
162
|
+
<input name="name" required />
|
|
163
|
+
{state?.error?.name && <p className="text-red-500">{state.error.name}</p>}
|
|
164
|
+
<input name="email" type="email" required />
|
|
165
|
+
{state?.error?.email && <p className="text-red-500">{state.error.email}</p>}
|
|
166
|
+
<button disabled={isPending}>{isPending ? "Creating..." : "Create"}</button>
|
|
167
|
+
</form>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Optimistic Updates
|
|
173
|
+
```tsx
|
|
174
|
+
"use client";
|
|
175
|
+
import { useOptimistic } from "react";
|
|
176
|
+
|
|
177
|
+
function TodoList({ todos, addTodo }: { todos: Todo[]; addTodo: (text: string) => Promise<void> }) {
|
|
178
|
+
const [optimisticTodos, addOptimistic] = useOptimistic(
|
|
179
|
+
todos,
|
|
180
|
+
(state, newTodo: string) => [...state, { id: "temp", text: newTodo, pending: true }]
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
async function handleAdd(formData: FormData) {
|
|
184
|
+
const text = formData.get("text") as string;
|
|
185
|
+
addOptimistic(text);
|
|
186
|
+
await addTodo(text);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<form action={handleAdd}>
|
|
191
|
+
<input name="text" />
|
|
192
|
+
<button type="submit">Add</button>
|
|
193
|
+
<ul>
|
|
194
|
+
{optimisticTodos.map(t => (
|
|
195
|
+
<li key={t.id} style={{ opacity: t.pending ? 0.5 : 1 }}>{t.text}</li>
|
|
196
|
+
))}
|
|
197
|
+
</ul>
|
|
198
|
+
</form>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Event Handler Types
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
// Always use specific event types
|
|
207
|
+
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
|
|
208
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
|
|
209
|
+
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void
|
|
210
|
+
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void
|
|
211
|
+
onFocus: (e: React.FocusEvent<HTMLInputElement>) => void
|
|
212
|
+
onDrag: (e: React.DragEvent<HTMLDivElement>) => void
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Hooks Typing
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
// useState with union
|
|
219
|
+
const [status, setStatus] = useState<"idle" | "loading" | "error">("idle");
|
|
220
|
+
|
|
221
|
+
// useRef for DOM — pass null, get RefObject
|
|
222
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
223
|
+
|
|
224
|
+
// useRef for mutable value — no null, get MutableRefObject
|
|
225
|
+
const intervalRef = useRef<number>(0);
|
|
226
|
+
|
|
227
|
+
// useReducer with discriminated actions
|
|
228
|
+
type Action = { type: "increment" } | { type: "set"; payload: number };
|
|
229
|
+
const [count, dispatch] = useReducer((state: number, action: Action) => {
|
|
230
|
+
switch (action.type) {
|
|
231
|
+
case "increment": return state + 1;
|
|
232
|
+
case "set": return action.payload;
|
|
233
|
+
}
|
|
234
|
+
}, 0);
|
|
235
|
+
|
|
236
|
+
// Custom hook: return as const for tuple types
|
|
237
|
+
function useToggle(initial = false) {
|
|
238
|
+
const [value, setValue] = useState(initial);
|
|
239
|
+
const toggle = useCallback(() => setValue(v => !v), []);
|
|
240
|
+
return [value, toggle] as const;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// useContext with null guard
|
|
244
|
+
const ctx = useContext(ThemeContext);
|
|
245
|
+
if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Performance Rules
|
|
249
|
+
|
|
250
|
+
### Prevent Async Waterfalls
|
|
251
|
+
```tsx
|
|
252
|
+
// BAD — sequential fetching
|
|
253
|
+
const user = await getUser(id);
|
|
254
|
+
const posts = await getPosts(user.id); // Waits for user first
|
|
255
|
+
const comments = await getComments(posts[0].id); // Waits for posts
|
|
256
|
+
|
|
257
|
+
// GOOD — parallel where possible
|
|
258
|
+
const [user, posts] = await Promise.all([getUser(id), getPosts(id)]);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Bundle Size
|
|
262
|
+
- `dynamic(() => import("./HeavyComponent"))` — lazy load below-fold components
|
|
263
|
+
- Tree-shake: use named exports, avoid `import *`
|
|
264
|
+
- Analyze bundle: `@next/bundle-analyzer` to find bloat
|
|
265
|
+
- Code-split by route — each page loads only its own JS
|
|
266
|
+
- Avoid importing entire libraries: `import { debounce } from "lodash-es"` not `import _ from "lodash"`
|
|
267
|
+
|
|
268
|
+
### Server-Side Performance
|
|
269
|
+
- Default to Server Components — zero JS shipped to client
|
|
270
|
+
- Stream with Suspense: wrap slow data fetchers in `<Suspense fallback={<Skeleton />}>`
|
|
271
|
+
- Cache expensive computations with `"use cache"` directive
|
|
272
|
+
- Avoid serializing large objects across server/client boundary
|
|
273
|
+
|
|
274
|
+
### Client-Side Data Fetching
|
|
275
|
+
- Use SWR or React Query — automatic caching, revalidation, error retry
|
|
276
|
+
- Stale-while-revalidate: show cached data instantly, refresh in background
|
|
277
|
+
- Error boundaries: `<ErrorBoundary>` wrapping data-dependent trees
|
|
278
|
+
- Prefetch on hover/viewport for likely next navigations
|
|
279
|
+
|
|
280
|
+
### Re-render Optimization
|
|
281
|
+
- React Compiler (React 19): auto-memoizes — try this first before manual `useMemo`/`useCallback`
|
|
282
|
+
- If no compiler: `useMemo` for expensive computations, `useCallback` for stable function references
|
|
283
|
+
- Extract static JSX outside components — constants don't re-create
|
|
284
|
+
- Split context: separate frequently-changing state from rarely-changing state
|
|
285
|
+
- `React.memo()` on components receiving only primitive props
|
|
286
|
+
|
|
287
|
+
### Rendering Performance
|
|
288
|
+
- **Virtualize** lists > 100 items: `@tanstack/react-virtual`, `react-window`
|
|
289
|
+
- Defer heavy computations with `useDeferredValue`
|
|
290
|
+
- Batch state updates (React 18+ does this automatically in event handlers)
|
|
291
|
+
- Avoid reading DOM layout during renders (force sync layout = jank)
|
|
292
|
+
|
|
293
|
+
### Advanced Patterns
|
|
294
|
+
- Progressive hydration: load and hydrate components as they enter viewport
|
|
295
|
+
- Concurrent features: `useTransition` for non-blocking state updates
|
|
296
|
+
- Suspense caching: combine `<Suspense>` with data cache for instant page transitions
|
|
297
|
+
- React Compiler adoption: enable gradually, check for unsafe patterns first
|
|
298
|
+
|
|
299
|
+
## Decision Flow
|
|
300
|
+
|
|
301
|
+
When implementing a React feature:
|
|
302
|
+
|
|
303
|
+
1. **Server or Client?** → If it uses browser APIs, event handlers, or useState → Client. Everything else → Server.
|
|
304
|
+
2. **Data fetching?** → Server Component for initial data. SWR/React Query for client-side mutations.
|
|
305
|
+
3. **Form?** → Server Action + `useActionState` + Zod validation.
|
|
306
|
+
4. **Optimistic?** → `useOptimistic` for instant feedback.
|
|
307
|
+
5. **Loading?** → `<Suspense>` with skeleton fallback.
|
|
308
|
+
6. **Performance?** → Try React Compiler first. Then manual memo. Then virtualization. Then code splitting.
|
|
309
|
+
7. **Error?** → Error boundaries for rendering errors. try/catch for Server Actions.
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
name: requirements-clarity
|
|
3
3
|
description: Clarify ambiguous requirements through focused dialogue before implementation. Use when requirements are unclear, features are complex (>2 days), or involve cross-team coordination. Ask two core questions - Why? (YAGNI check) and Simpler? (KISS check) - to ensure clarity before coding.
|
|
4
4
|
metadata:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
category: cross-cutting
|
|
6
|
+
domain: general
|
|
7
|
+
applicability: on-demand
|
|
8
|
+
inputs: [requirements]
|
|
9
|
+
outputs: [scored-requirements, clarifications]
|
|
10
|
+
relatedSkills: [brainstorming]
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Requirements Clarity Skill
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
name: session-handoff
|
|
3
3
|
description: "Creates comprehensive handoff documents for seamless AI agent session transfers. Triggered when: (1) user requests handoff/memory/context save, (2) context window approaches capacity, (3) major task milestone completed, (4) work session ending, (5) user says 'save state', 'create handoff', 'I need to pause', 'context is getting full', (6) resuming work with 'load handoff', 'resume from', 'continue where we left off'. Proactively suggests handoffs after substantial work (multiple file edits, complex debugging, architecture decisions). Solves long-running agent context exhaustion by enabling fresh agents to continue with zero ambiguity."
|
|
4
4
|
metadata:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
category: cross-cutting
|
|
6
|
+
domain: general
|
|
7
|
+
applicability: always
|
|
8
|
+
inputs: [session-state, decisions]
|
|
9
|
+
outputs: [handoff-document]
|
|
10
|
+
requires: [aikit]
|
|
11
|
+
relatedSkills: [lesson-learned]
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# Handoff
|