tribunal-kit 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/.agent/ARCHITECTURE.md +99 -99
  2. package/.agent/GEMINI.md +52 -52
  3. package/.agent/agents/accessibility-reviewer.md +187 -220
  4. package/.agent/agents/ai-code-reviewer.md +199 -233
  5. package/.agent/agents/backend-specialist.md +215 -238
  6. package/.agent/agents/code-archaeologist.md +161 -181
  7. package/.agent/agents/database-architect.md +184 -207
  8. package/.agent/agents/debugger.md +191 -218
  9. package/.agent/agents/dependency-reviewer.md +103 -136
  10. package/.agent/agents/devops-engineer.md +218 -238
  11. package/.agent/agents/documentation-writer.md +201 -221
  12. package/.agent/agents/explorer-agent.md +160 -180
  13. package/.agent/agents/frontend-reviewer.md +160 -194
  14. package/.agent/agents/frontend-specialist.md +248 -237
  15. package/.agent/agents/game-developer.md +48 -52
  16. package/.agent/agents/logic-reviewer.md +116 -149
  17. package/.agent/agents/mobile-developer.md +200 -223
  18. package/.agent/agents/mobile-reviewer.md +162 -195
  19. package/.agent/agents/orchestrator.md +181 -211
  20. package/.agent/agents/penetration-tester.md +157 -174
  21. package/.agent/agents/performance-optimizer.md +183 -203
  22. package/.agent/agents/performance-reviewer.md +178 -211
  23. package/.agent/agents/precedence-reviewer.md +213 -0
  24. package/.agent/agents/product-manager.md +142 -162
  25. package/.agent/agents/product-owner.md +6 -25
  26. package/.agent/agents/project-planner.md +142 -162
  27. package/.agent/agents/qa-automation-engineer.md +225 -242
  28. package/.agent/agents/security-auditor.md +174 -194
  29. package/.agent/agents/seo-specialist.md +193 -213
  30. package/.agent/agents/sql-reviewer.md +161 -194
  31. package/.agent/agents/supervisor-agent.md +184 -203
  32. package/.agent/agents/swarm-worker-contracts.md +17 -17
  33. package/.agent/agents/swarm-worker-registry.md +46 -46
  34. package/.agent/agents/test-coverage-reviewer.md +160 -193
  35. package/.agent/agents/test-engineer.md +0 -21
  36. package/.agent/agents/type-safety-reviewer.md +175 -208
  37. package/.agent/patterns/generator.md +9 -9
  38. package/.agent/patterns/inversion.md +12 -12
  39. package/.agent/patterns/pipeline.md +9 -9
  40. package/.agent/patterns/reviewer.md +13 -13
  41. package/.agent/patterns/tool-wrapper.md +9 -9
  42. package/.agent/rules/GEMINI.md +63 -63
  43. package/.agent/scripts/append_flow.js +72 -0
  44. package/.agent/scripts/case_law_manager.py +525 -0
  45. package/.agent/scripts/compress_skills.py +167 -0
  46. package/.agent/scripts/consolidate_skills.py +173 -0
  47. package/.agent/scripts/deep_compress.py +202 -0
  48. package/.agent/scripts/minify_context.py +80 -0
  49. package/.agent/scripts/security_scan.py +1 -1
  50. package/.agent/scripts/skill_evolution.py +563 -0
  51. package/.agent/scripts/strip_tribunal.py +41 -0
  52. package/.agent/skills/agent-organizer/SKILL.md +100 -126
  53. package/.agent/skills/agentic-patterns/SKILL.md +0 -70
  54. package/.agent/skills/ai-prompt-injection-defense/SKILL.md +134 -160
  55. package/.agent/skills/api-patterns/SKILL.md +123 -215
  56. package/.agent/skills/api-security-auditor/SKILL.md +143 -177
  57. package/.agent/skills/app-builder/SKILL.md +334 -50
  58. package/.agent/skills/app-builder/templates/SKILL.md +13 -15
  59. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +16 -16
  60. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +22 -22
  61. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +18 -18
  62. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +20 -20
  63. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +17 -17
  64. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +18 -18
  65. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +21 -21
  66. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +19 -19
  67. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +26 -26
  68. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +26 -26
  69. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +19 -19
  70. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +18 -18
  71. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +20 -20
  72. package/.agent/skills/appflow-wireframe/SKILL.md +95 -121
  73. package/.agent/skills/architecture/SKILL.md +169 -331
  74. package/.agent/skills/authentication-best-practices/SKILL.md +139 -173
  75. package/.agent/skills/bash-linux/SKILL.md +129 -154
  76. package/.agent/skills/behavioral-modes/SKILL.md +8 -69
  77. package/.agent/skills/brainstorming/SKILL.md +436 -104
  78. package/.agent/skills/building-native-ui/SKILL.md +152 -174
  79. package/.agent/skills/clean-code/SKILL.md +331 -360
  80. package/.agent/skills/code-review-checklist/SKILL.md +0 -62
  81. package/.agent/skills/config-validator/SKILL.md +115 -141
  82. package/.agent/skills/csharp-developer/SKILL.md +468 -528
  83. package/.agent/skills/database-design/SKILL.md +104 -369
  84. package/.agent/skills/deployment-procedures/SKILL.md +119 -145
  85. package/.agent/skills/devops-engineer/SKILL.md +295 -332
  86. package/.agent/skills/devops-incident-responder/SKILL.md +87 -113
  87. package/.agent/skills/doc.md +5 -5
  88. package/.agent/skills/documentation-templates/SKILL.md +27 -63
  89. package/.agent/skills/edge-computing/SKILL.md +131 -157
  90. package/.agent/skills/extract-design-system/SKILL.md +108 -134
  91. package/.agent/skills/framer-motion-expert/SKILL.md +111 -855
  92. package/.agent/skills/frontend-design/SKILL.md +151 -499
  93. package/.agent/skills/game-design-expert/SKILL.md +79 -105
  94. package/.agent/skills/game-engineering-expert/SKILL.md +96 -122
  95. package/.agent/skills/geo-fundamentals/SKILL.md +97 -124
  96. package/.agent/skills/github-operations/SKILL.md +279 -314
  97. package/.agent/skills/gsap-expert/SKILL.md +119 -826
  98. package/.agent/skills/i18n-localization/SKILL.md +113 -138
  99. package/.agent/skills/intelligent-routing/SKILL.md +167 -127
  100. package/.agent/skills/lint-and-validate/SKILL.md +16 -52
  101. package/.agent/skills/llm-engineering/SKILL.md +344 -357
  102. package/.agent/skills/local-first/SKILL.md +128 -154
  103. package/.agent/skills/mcp-builder/SKILL.md +92 -118
  104. package/.agent/skills/mobile-design/SKILL.md +213 -219
  105. package/.agent/skills/motion-engineering/SKILL.md +184 -0
  106. package/.agent/skills/nextjs-react-expert/SKILL.md +99 -698
  107. package/.agent/skills/nodejs-best-practices/SKILL.md +498 -559
  108. package/.agent/skills/observability/SKILL.md +293 -330
  109. package/.agent/skills/parallel-agents/SKILL.md +96 -122
  110. package/.agent/skills/performance-profiling/SKILL.md +217 -254
  111. package/.agent/skills/plan-writing/SKILL.md +92 -118
  112. package/.agent/skills/platform-engineer/SKILL.md +97 -123
  113. package/.agent/skills/playwright-best-practices/SKILL.md +137 -162
  114. package/.agent/skills/powershell-windows/SKILL.md +112 -146
  115. package/.agent/skills/project-idioms/SKILL.md +87 -0
  116. package/.agent/skills/python-patterns/SKILL.md +15 -35
  117. package/.agent/skills/python-pro/SKILL.md +148 -754
  118. package/.agent/skills/react-specialist/SKILL.md +123 -827
  119. package/.agent/skills/readme-builder/SKILL.md +23 -85
  120. package/.agent/skills/realtime-patterns/SKILL.md +269 -304
  121. package/.agent/skills/red-team-tactics/SKILL.md +18 -51
  122. package/.agent/skills/rust-pro/SKILL.md +623 -701
  123. package/.agent/skills/seo-fundamentals/SKILL.md +129 -154
  124. package/.agent/skills/server-management/SKILL.md +164 -190
  125. package/.agent/skills/shadcn-ui-expert/SKILL.md +181 -206
  126. package/.agent/skills/skill-creator/SKILL.md +24 -56
  127. package/.agent/skills/sql-pro/SKILL.md +579 -633
  128. package/.agent/skills/supabase-postgres-best-practices/SKILL.md +35 -66
  129. package/.agent/skills/swiftui-expert/SKILL.md +151 -176
  130. package/.agent/skills/systematic-debugging/SKILL.md +92 -118
  131. package/.agent/skills/tailwind-patterns/SKILL.md +516 -576
  132. package/.agent/skills/tdd-workflow/SKILL.md +111 -137
  133. package/.agent/skills/test-result-analyzer/SKILL.md +33 -73
  134. package/.agent/skills/testing-patterns/SKILL.md +512 -573
  135. package/.agent/skills/trend-researcher/SKILL.md +30 -71
  136. package/.agent/skills/ui-ux-pro-max/SKILL.md +8 -41
  137. package/.agent/skills/ui-ux-researcher/SKILL.md +51 -91
  138. package/.agent/skills/vue-expert/SKILL.md +127 -866
  139. package/.agent/skills/vulnerability-scanner/SKILL.md +354 -269
  140. package/.agent/skills/web-accessibility-auditor/SKILL.md +168 -193
  141. package/.agent/skills/web-design-guidelines/SKILL.md +25 -61
  142. package/.agent/skills/webapp-testing/SKILL.md +119 -145
  143. package/.agent/skills/whimsy-injector/SKILL.md +58 -132
  144. package/.agent/skills/workflow-optimizer/SKILL.md +28 -68
  145. package/.agent/workflows/api-tester.md +151 -151
  146. package/.agent/workflows/audit.md +127 -138
  147. package/.agent/workflows/brainstorm.md +110 -110
  148. package/.agent/workflows/changelog.md +112 -112
  149. package/.agent/workflows/create.md +124 -124
  150. package/.agent/workflows/debug.md +165 -189
  151. package/.agent/workflows/deploy.md +180 -189
  152. package/.agent/workflows/enhance.md +128 -151
  153. package/.agent/workflows/fix.md +114 -135
  154. package/.agent/workflows/generate.md +13 -4
  155. package/.agent/workflows/migrate.md +160 -160
  156. package/.agent/workflows/orchestrate.md +168 -168
  157. package/.agent/workflows/performance-benchmarker.md +114 -123
  158. package/.agent/workflows/plan.md +173 -173
  159. package/.agent/workflows/preview.md +80 -80
  160. package/.agent/workflows/refactor.md +161 -183
  161. package/.agent/workflows/review-ai.md +101 -129
  162. package/.agent/workflows/review.md +116 -116
  163. package/.agent/workflows/session.md +94 -94
  164. package/.agent/workflows/status.md +79 -79
  165. package/.agent/workflows/strengthen-skills.md +138 -139
  166. package/.agent/workflows/swarm.md +179 -179
  167. package/.agent/workflows/test.md +189 -211
  168. package/.agent/workflows/tribunal-backend.md +94 -113
  169. package/.agent/workflows/tribunal-database.md +95 -115
  170. package/.agent/workflows/tribunal-frontend.md +96 -118
  171. package/.agent/workflows/tribunal-full.md +93 -133
  172. package/.agent/workflows/tribunal-mobile.md +95 -119
  173. package/.agent/workflows/tribunal-performance.md +110 -133
  174. package/.agent/workflows/ui-ux-pro-max.md +122 -143
  175. package/README.md +30 -1
  176. package/bin/tribunal-kit.js +175 -12
  177. package/package.json +25 -4
  178. package/.agent/skills/api-patterns/api-style.md +0 -42
  179. package/.agent/skills/api-patterns/auth.md +0 -24
  180. package/.agent/skills/api-patterns/documentation.md +0 -26
  181. package/.agent/skills/api-patterns/graphql.md +0 -41
  182. package/.agent/skills/api-patterns/rate-limiting.md +0 -31
  183. package/.agent/skills/api-patterns/response.md +0 -37
  184. package/.agent/skills/api-patterns/rest.md +0 -40
  185. package/.agent/skills/api-patterns/security-testing.md +0 -122
  186. package/.agent/skills/api-patterns/trpc.md +0 -41
  187. package/.agent/skills/api-patterns/versioning.md +0 -22
  188. package/.agent/skills/app-builder/agent-coordination.md +0 -71
  189. package/.agent/skills/app-builder/feature-building.md +0 -53
  190. package/.agent/skills/app-builder/project-detection.md +0 -34
  191. package/.agent/skills/app-builder/scaffolding.md +0 -118
  192. package/.agent/skills/app-builder/tech-stack.md +0 -40
  193. package/.agent/skills/architecture/context-discovery.md +0 -43
  194. package/.agent/skills/architecture/examples.md +0 -94
  195. package/.agent/skills/architecture/pattern-selection.md +0 -68
  196. package/.agent/skills/architecture/patterns-reference.md +0 -50
  197. package/.agent/skills/architecture/trade-off-analysis.md +0 -77
  198. package/.agent/skills/brainstorming/dynamic-questioning.md +0 -360
  199. package/.agent/skills/database-design/database-selection.md +0 -43
  200. package/.agent/skills/database-design/indexing.md +0 -39
  201. package/.agent/skills/database-design/migrations.md +0 -48
  202. package/.agent/skills/database-design/optimization.md +0 -36
  203. package/.agent/skills/database-design/orm-selection.md +0 -30
  204. package/.agent/skills/database-design/schema-design.md +0 -56
  205. package/.agent/skills/frontend-design/animation-guide.md +0 -331
  206. package/.agent/skills/frontend-design/color-system.md +0 -329
  207. package/.agent/skills/frontend-design/decision-trees.md +0 -418
  208. package/.agent/skills/frontend-design/motion-graphics.md +0 -306
  209. package/.agent/skills/frontend-design/typography-system.md +0 -363
  210. package/.agent/skills/frontend-design/ux-psychology.md +0 -1116
  211. package/.agent/skills/frontend-design/visual-effects.md +0 -383
  212. package/.agent/skills/intelligent-routing/router-manifest.md +0 -65
  213. package/.agent/skills/mobile-design/decision-trees.md +0 -516
  214. package/.agent/skills/mobile-design/mobile-backend.md +0 -491
  215. package/.agent/skills/mobile-design/mobile-color-system.md +0 -420
  216. package/.agent/skills/mobile-design/mobile-debugging.md +0 -122
  217. package/.agent/skills/mobile-design/mobile-design-thinking.md +0 -357
  218. package/.agent/skills/mobile-design/mobile-navigation.md +0 -458
  219. package/.agent/skills/mobile-design/mobile-performance.md +0 -767
  220. package/.agent/skills/mobile-design/mobile-testing.md +0 -356
  221. package/.agent/skills/mobile-design/mobile-typography.md +0 -433
  222. package/.agent/skills/mobile-design/platform-android.md +0 -666
  223. package/.agent/skills/mobile-design/platform-ios.md +0 -561
  224. package/.agent/skills/mobile-design/touch-psychology.md +0 -537
  225. package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +0 -312
  226. package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +0 -240
  227. package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +0 -490
  228. package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +0 -264
  229. package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +0 -581
  230. package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +0 -432
  231. package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +0 -684
  232. package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +0 -150
  233. package/.agent/skills/vulnerability-scanner/checklists.md +0 -121
@@ -1,931 +1,227 @@
1
1
  ---
2
2
  name: react-specialist
3
- description: Senior React specialist (React 19+) focusing on advanced patterns, hooks mastery, React Compiler, Server Components, state management (Zustand/Jotai/React Query), performance optimization, and production architectures (Next.js/Remix). Use when building React components, optimizing renders, managing state, or implementing modern React 19 patterns.
3
+ description: React 19+ specialist. use(), useActionState, useOptimistic, React Compiler, Server/Client Components, Zustand/Jotai, React Query. Use when building components, managing state, optimizing renders.
4
4
  allowed-tools: Read, Write, Edit, Glob, Grep
5
- version: 2.0.0
6
- last-updated: 2026-03-30
7
- applies-to-model: gemini-2.5-pro, claude-3-7-sonnet
5
+ version: 3.1.0
6
+ last-updated: 2026-04-06
8
7
  ---
9
8
 
10
- # React SpecialistReact 19+ Mastery
9
+ # React 19+Dense Reference
11
10
 
12
- > React 19 is a paradigm shift. Server Components are the default. The React Compiler handles memoization. `use()` replaces `useEffect` data fetching. If you're still writing React 18 patterns, you're writing legacy code.
11
+ ## Hallucination Traps (Read First)
12
+ - ❌ `useFormState` → ✅ `useActionState` (stable name)
13
+ - ❌ `useContext()` in conditionals → ✅ `use(Context)` CAN be conditional
14
+ - ❌ `useMemo/useCallback/React.memo` in React 19+ projects → ✅ React Compiler handles this
15
+ - ❌ `exitBeforeEnter` (Framer) → ✅ `mode="wait"` on `<AnimatePresence>`
16
+ - ❌ `next/router` → ✅ `next/navigation` in App Router
17
+ - ❌ Server Components using `useState/useEffect` → must be `"use client"`
13
18
 
14
19
  ---
15
20
 
16
- ## React 19 Core API Changes
17
-
18
- ### The `use()` Hook
21
+ ## React 19 Core APIs
19
22
 
23
+ ### `use()` — Replaces many useEffect patterns
20
24
  ```tsx
21
- // use() can read promises and context — replaces many useEffect patterns
22
25
  import { use } from "react";
23
-
26
+ // Reads promises (suspends until resolved)
24
27
  function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
25
- const user = use(userPromise); // suspends until resolved
28
+ const user = use(userPromise); // suspends
26
29
  return <h1>{user.name}</h1>;
27
30
  }
28
-
29
- // use() with context (replaces useContext can be used conditionally)
30
- function Theme({ isAdmin }: { isAdmin: boolean }) {
31
- if (isAdmin) {
32
- const theme = use(ThemeContext); // ✅ conditional context read
33
- return <AdminPanel theme={theme} />;
34
- }
31
+ // Reads context — CAN be called conditionally (unlike useContext)
32
+ function Admin({ isAdmin }: { isAdmin: boolean }) {
33
+ if (isAdmin) return <Panel theme={use(ThemeContext)} />;
35
34
  return <PublicPanel />;
36
35
  }
37
-
38
- // ❌ HALLUCINATION TRAP: use() is NOT useContext()
39
- // useContext cannot be called inside conditionals or loops
40
- // use() CAN be called inside conditionals (it's a new primitive)
41
36
  ```
42
37
 
43
- ### `useActionState` (Forms)
44
-
38
+ ### `useActionState` — Form actions with state
45
39
  ```tsx
46
- import { useActionState } from "react";
40
+ import { useActionState } from "react"; // NOT useFormState
47
41
 
48
42
  async function submitForm(prevState: FormState, formData: FormData) {
49
43
  const email = formData.get("email") as string;
50
-
51
- if (!email.includes("@")) {
52
- return { error: "Invalid email", success: false };
53
- }
54
-
44
+ if (!email.includes("@")) return { error: "Invalid email" };
55
45
  await saveToDatabase(email);
56
46
  return { error: null, success: true };
57
47
  }
58
48
 
59
49
  function SignupForm() {
60
- const [state, formAction, isPending] = useActionState(submitForm, {
61
- error: null,
62
- success: false,
63
- });
64
-
50
+ const [state, formAction, isPending] = useActionState(submitForm, { error: null });
65
51
  return (
66
52
  <form action={formAction}>
67
- <input name="email" type="email" disabled={isPending} />
68
- {state.error && <p className="error">{state.error}</p>}
69
- <button type="submit" disabled={isPending}>
70
- {isPending ? "Submitting..." : "Sign Up"}
71
- </button>
53
+ <input name="email" disabled={isPending} />
54
+ {state.error && <p>{state.error}</p>}
55
+ <button disabled={isPending}>{isPending ? "Saving..." : "Submit"}</button>
72
56
  </form>
73
57
  );
74
58
  }
75
-
76
- // ❌ HALLUCINATION TRAP: useActionState was briefly named useFormState
77
- // in React canaries. The STABLE name is useActionState.
78
- // ❌ HALLUCINATION TRAP: The signature is (action, initialState)
79
- // The action receives (prevState, formData), NOT just formData
80
59
  ```
81
60
 
82
- ### `useOptimistic`
83
-
61
+ ### `useOptimistic` — Instant UI feedback
84
62
  ```tsx
85
63
  import { useOptimistic } from "react";
86
-
87
64
  function TodoList({ todos }: { todos: Todo[] }) {
88
- const [optimisticTodos, addOptimisticTodo] = useOptimistic(
65
+ const [optimistic, addOptimistic] = useOptimistic(
89
66
  todos,
90
- (currentTodos, newTodo: Todo) => [...currentTodos, newTodo]
67
+ (current, newTodo: Todo) => [...current, newTodo]
91
68
  );
92
-
93
69
  async function handleAdd(formData: FormData) {
94
- const title = formData.get("title") as string;
95
- const tempTodo = { id: crypto.randomUUID(), title, pending: true };
96
-
97
- addOptimisticTodo(tempTodo); // instantly updates UI
98
-
99
- await saveTodo(title); // actual API call
100
- // When server responds, `todos` prop updates and optimistic state resets
70
+ addOptimistic({ id: crypto.randomUUID(), title: formData.get("title"), pending: true });
71
+ await saveTodo(formData.get("title") as string);
101
72
  }
102
-
103
73
  return (
104
- <div>
105
- <form action={handleAdd}>
106
- <input name="title" />
107
- <button type="submit">Add</button>
108
- </form>
109
- <ul>
110
- {optimisticTodos.map((todo) => (
111
- <li key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}>
112
- {todo.title}
113
- </li>
114
- ))}
115
- </ul>
116
- </div>
74
+ <form action={handleAdd}>
75
+ {optimistic.map(t => <li key={t.id} style={{ opacity: t.pending ? 0.5 : 1 }}>{t.title}</li>)}
76
+ </form>
117
77
  );
118
78
  }
119
79
  ```
120
80
 
121
- ### `useFormStatus`
122
-
81
+ ### `useFormStatus` — Button pending state (must be INSIDE `<form>`)
123
82
  ```tsx
124
83
  import { useFormStatus } from "react-dom";
125
-
126
- // ❌ HALLUCINATION TRAP: useFormStatus must be called from a component
127
- // INSIDE a <form> — it reads the nearest parent form's status.
128
- // It does NOT work if called in the same component that renders the <form>.
129
-
84
+ // ❌ TRAP: Cannot be called in the same component as <form>
130
85
  function SubmitButton() {
131
- const { pending, data, method, action } = useFormStatus();
132
-
133
- return (
134
- <button type="submit" disabled={pending}>
135
- {pending ? "Saving..." : "Save"}
136
- </button>
137
- );
138
- }
139
-
140
- // Usage:
141
- function MyForm() {
142
- return (
143
- <form action={serverAction}>
144
- <input name="name" />
145
- <SubmitButton /> {/* useFormStatus works here — inside the form */}
146
- </form>
147
- );
86
+ const { pending } = useFormStatus(); // reads nearest parent form
87
+ return <button disabled={pending}>{pending ? "Saving..." : "Save"}</button>;
148
88
  }
149
89
  ```
150
90
 
151
- ### `useTransition` (Non-Blocking State Updates)
152
-
91
+ ### `useTransition` Non-blocking updates (React 19: supports async)
153
92
  ```tsx
154
- import { useTransition } from "react";
155
-
156
- function SearchPage() {
157
- const [query, setQuery] = useState("");
158
- const [results, setResults] = useState<Item[]>([]);
159
- const [isPending, startTransition] = useTransition();
160
-
161
- function handleSearch(e: React.ChangeEvent<HTMLInputElement>) {
162
- const value = e.target.value;
163
- setQuery(value); // urgent — update input immediately
164
-
165
- startTransition(async () => {
166
- // non-urgent — React can interrupt this if user types again
167
- const data = await search(value);
168
- setResults(data);
169
- });
170
- }
171
-
172
- return (
173
- <div>
174
- <input value={query} onChange={handleSearch} />
175
- {isPending && <Spinner />}
176
- <ResultsList results={results} />
177
- </div>
178
- );
179
- }
180
-
181
- // React 19: startTransition now supports async functions
182
- // React 18: startTransition was synchronous only
93
+ const [isPending, startTransition] = useTransition();
94
+ startTransition(async () => {
95
+ const data = await search(query); // async supported in React 19 only
96
+ setResults(data);
97
+ });
183
98
  ```
184
99
 
185
- ### `useDeferredValue`
186
-
100
+ ### `useDeferredValue` — Stale-while-rerender
187
101
  ```tsx
188
- import { useDeferredValue, memo } from "react";
189
-
190
- function SearchResults({ query }: { query: string }) {
191
- const deferredQuery = useDeferredValue(query);
192
-
193
- // Shows stale results while the new ones compute
194
- const isStale = query !== deferredQuery;
195
-
196
- return (
197
- <div style={{ opacity: isStale ? 0.6 : 1 }}>
198
- <ExpensiveList query={deferredQuery} />
199
- </div>
200
- );
201
- }
202
-
203
- // Initial value support (React 19):
204
- const value = useDeferredValue(fetchedData, initialFallback);
102
+ const deferredQuery = useDeferredValue(query);
103
+ const isStale = query !== deferredQuery;
104
+ return <div style={{ opacity: isStale ? 0.6 : 1 }}><ExpensiveList query={deferredQuery} /></div>;
105
+ // React 19: useDeferredValue(value, initialFallback) — 2-arg form
205
106
  ```
206
107
 
207
108
  ---
208
109
 
209
- ## The React Compiler
210
-
211
- ```tsx
212
- // React 19 ships the React Compiler (formerly React Forget)
213
- // It automatically memoizes components, values, and callbacks
214
-
215
- // ❌ LEGACY — do NOT write this in React 19+
216
- const memoizedValue = useMemo(() => expensiveCalc(a, b), [a, b]);
217
- const memoizedFn = useCallback(() => handleClick(id), [id]);
218
- const MemoizedComp = React.memo(MyComponent);
219
-
220
- // ✅ REACT 19 — just write normal code
221
- const value = expensiveCalc(a, b);
222
- function handleClick() { /* ... */ }
223
- function MyComponent() { /* ... */ }
224
- // The compiler figures out what needs memoization automatically
225
-
226
- // ❌ HALLUCINATION TRAP: Do NOT manually memoize in React 19+ projects
227
- // The compiler is smarter than manual memoization and handles:
228
- // - Component memoization (replaces React.memo)
229
- // - Value memoization (replaces useMemo)
230
- // - Callback memoization (replaces useCallback)
231
- //
232
- // EXCEPTION: If the React Compiler is explicitly disabled in the project
233
- // config, then manual memoization is still appropriate.
234
- ```
235
-
236
- ### When Manual Memoization Is Still Valid
237
-
238
- ```tsx
239
- // 1. React Compiler is disabled in project config
240
- // 2. Working with React 18 (no compiler)
241
- // 3. Library code that must support React 17/18/19
242
- // 4. Performance-critical code where compiler output is insufficient
243
- // (measure first with React DevTools Profiler)
244
-
245
- // Always add a comment explaining why:
246
- // MANUAL_MEMO: React Compiler disabled in this project
247
- const cached = useMemo(() => heavyComputation(data), [data]);
248
- ```
110
+ ## React Compiler (React 19)
111
+ - Auto-memoizes components/values/callbacks. **Don't manually memoize in React 19 projects.**
112
+ - ❌ `useMemo(() => calc(a), [a])` / `useCallback(fn, [id])` / `React.memo(Comp)` — legacy
113
+ - Write plain functions/values. Compiler optimizes automatically.
114
+ - Exception: still use manual memo if compiler is explicitly disabled in config.
249
115
 
250
116
  ---
251
117
 
252
- ## Component Architecture Patterns
253
-
254
- ### Compound Components
118
+ ## Component Patterns
255
119
 
120
+ ### Compound Components (shared state via context)
256
121
  ```tsx
257
- // Compound components share implicit state via context
258
- const TabsContext = createContext<{
259
- activeTab: string;
260
- setActiveTab: (id: string) => void;
261
- } | null>(null);
262
-
122
+ const TabsContext = createContext<{ active: string; setActive: (id: string) => void } | null>(null);
263
123
  function Tabs({ children, defaultTab }: { children: ReactNode; defaultTab: string }) {
264
- const [activeTab, setActiveTab] = useState(defaultTab);
265
- return (
266
- <TabsContext value={{ activeTab, setActiveTab }}>
267
- <div className="tabs">{children}</div>
268
- </TabsContext>
269
- );
270
- }
271
-
272
- function TabList({ children }: { children: ReactNode }) {
273
- return <div role="tablist" className="tab-list">{children}</div>;
124
+ const [active, setActive] = useState(defaultTab);
125
+ return <TabsContext value={{ active, setActive }}><div>{children}</div></TabsContext>;
274
126
  }
275
-
276
127
  function Tab({ id, children }: { id: string; children: ReactNode }) {
277
- const ctx = use(TabsContext);
278
- if (!ctx) throw new Error("Tab must be used inside <Tabs>");
279
-
280
- return (
281
- <button
282
- role="tab"
283
- aria-selected={ctx.activeTab === id}
284
- onClick={() => ctx.setActiveTab(id)}
285
- >
286
- {children}
287
- </button>
288
- );
128
+ const ctx = use(TabsContext)!;
129
+ return <button onClick={() => ctx.setActive(id)} aria-selected={ctx.active === id}>{children}</button>;
289
130
  }
290
-
291
- function TabPanel({ id, children }: { id: string; children: ReactNode }) {
292
- const ctx = use(TabsContext);
293
- if (!ctx) throw new Error("TabPanel must be used inside <Tabs>");
294
- if (ctx.activeTab !== id) return null;
295
-
296
- return <div role="tabpanel">{children}</div>;
297
- }
298
-
299
- // Usage:
300
- <Tabs defaultTab="settings">
301
- <TabList>
302
- <Tab id="profile">Profile</Tab>
303
- <Tab id="settings">Settings</Tab>
304
- </TabList>
305
- <TabPanel id="profile"><ProfileContent /></TabPanel>
306
- <TabPanel id="settings"><SettingsContent /></TabPanel>
307
- </Tabs>
308
-
309
- // ❌ HALLUCINATION TRAP: In React 19, context uses <Ctx value={}>
310
- // NOT <Ctx.Provider value={}>. The .Provider pattern is deprecated.
311
131
  ```
312
132
 
313
- ### Custom Hooks (Composable Logic)
133
+ ### Render Props / Higher Order Hooks
134
+ Prefer custom hooks over render props for modern React.
314
135
 
136
+ ### Context Performance Pattern
315
137
  ```tsx
316
- // useFetchreusable data fetching with loading/error states
317
- function useFetch<T>(url: string) {
318
- const [data, setData] = useState<T | null>(null);
319
- const [error, setError] = useState<Error | null>(null);
320
- const [isLoading, setIsLoading] = useState(true);
321
-
322
- useEffect(() => {
323
- const controller = new AbortController();
324
-
325
- async function fetchData() {
326
- try {
327
- setIsLoading(true);
328
- const res = await fetch(url, { signal: controller.signal });
329
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
330
- const json = await res.json();
331
- setData(json);
332
- } catch (err) {
333
- if (err instanceof Error && err.name !== "AbortError") {
334
- setError(err);
335
- }
336
- } finally {
337
- setIsLoading(false);
338
- }
339
- }
340
-
341
- fetchData();
342
- return () => controller.abort(); // cleanup on unmount or URL change
343
- }, [url]);
344
-
345
- return { data, error, isLoading };
346
- }
347
-
348
- // ❌ HALLUCINATION TRAP: Always include AbortController cleanup
349
- // Without it, state updates on unmounted components cause warnings
350
- // and potential memory leaks in SPAs
351
- ```
352
-
353
- ### Error Boundaries
354
-
355
- ```tsx
356
- // React 19 error boundaries — still class-based (no hook equivalent yet)
357
- class ErrorBoundary extends Component<
358
- { children: ReactNode; fallback: ReactNode },
359
- { hasError: boolean; error: Error | null }
360
- > {
361
- state = { hasError: false, error: null };
362
-
363
- static getDerivedStateFromError(error: Error) {
364
- return { hasError: true, error };
365
- }
366
-
367
- componentDidCatch(error: Error, info: ErrorInfo) {
368
- console.error("ErrorBoundary caught:", error, info.componentStack);
369
- // Send to error tracking service (Sentry, etc.)
370
- }
371
-
372
- render() {
373
- if (this.state.hasError) return this.props.fallback;
374
- return this.props.children;
375
- }
376
- }
377
-
378
- // Usage:
379
- <ErrorBoundary fallback={<ErrorPage />}>
380
- <Suspense fallback={<Loading />}>
381
- <Dashboard />
382
- </Suspense>
383
- </ErrorBoundary>
384
-
385
- // ❌ HALLUCINATION TRAP: There is NO useErrorBoundary hook in React core.
386
- // Error boundaries MUST be class components.
387
- // react-error-boundary (npm package) provides a hook-based wrapper.
388
- ```
389
-
390
- ### Render Props & Slots
391
-
392
- ```tsx
393
- // Render prop for flexible rendering
394
- interface DataTableProps<T> {
395
- data: T[];
396
- renderRow: (item: T, index: number) => ReactNode;
397
- renderHeader?: () => ReactNode;
398
- renderEmpty?: () => ReactNode;
399
- }
400
-
401
- function DataTable<T>({ data, renderRow, renderHeader, renderEmpty }: DataTableProps<T>) {
402
- if (data.length === 0) return renderEmpty?.() ?? <p>No data</p>;
403
-
404
- return (
405
- <table>
406
- {renderHeader && <thead>{renderHeader()}</thead>}
407
- <tbody>
408
- {data.map((item, i) => (
409
- <tr key={i}>{renderRow(item, i)}</tr>
410
- ))}
411
- </tbody>
412
- </table>
413
- );
414
- }
138
+ // Split context prevents all consumers from re-rendering on every change
139
+ const CountStateCtx = createContext<number>(0);
140
+ const CountDispatchCtx = createContext<Dispatch<Action>>(() => {});
415
141
  ```
416
142
 
417
143
  ---
418
144
 
419
- ## State Management Decision Matrix
420
-
421
- ```
422
- ┌─────────────────────────────────────────────────────────────────┐
423
- │ State Type Decision Tree │
424
- ├─────────────────────────────────────────────────────────────────┤
425
- │ │
426
- │ Is it SERVER data (fetched from API/DB)? │
427
- │ ├── YES → TanStack React Query / SWR │
428
- │ │ (caching, deduplication, revalidation, optimistic) │
429
- │ │ │
430
- │ └── NO → Is it shared across many components? │
431
- │ ├── YES → Is it complex (many actions/reducers)? │
432
- │ │ ├── YES → Zustand or Redux Toolkit │
433
- │ │ └── NO → Zustand (lightweight) or Jotai (atomic) │
434
- │ │ │
435
- │ └── NO → Is it just a toggle/input/form? │
436
- │ ├── YES → useState / useReducer (local) │
437
- │ └── Is it URL-dependent? │
438
- │ └── YES → useSearchParams / nuqs │
439
- └─────────────────────────────────────────────────────────────────┘
440
- ```
441
-
442
- ### Zustand (Recommended Default)
145
+ ## State Management
443
146
 
147
+ ### Zustand (preferred for global state)
444
148
  ```tsx
445
149
  import { create } from "zustand";
446
- import { devtools, persist } from "zustand/middleware";
447
-
448
- interface CartStore {
449
- items: CartItem[];
450
- total: number;
451
- addItem: (item: CartItem) => void;
452
- removeItem: (id: string) => void;
453
- clearCart: () => void;
454
- }
455
-
456
- const useCartStore = create<CartStore>()(
457
- devtools(
458
- persist(
459
- (set, get) => ({
460
- items: [],
461
- total: 0,
462
-
463
- addItem: (item) =>
464
- set((state) => ({
465
- items: [...state.items, item],
466
- total: state.total + item.price,
467
- })),
468
-
469
- removeItem: (id) =>
470
- set((state) => ({
471
- items: state.items.filter((i) => i.id !== id),
472
- total: state.items
473
- .filter((i) => i.id !== id)
474
- .reduce((sum, i) => sum + i.price, 0),
475
- })),
476
-
477
- clearCart: () => set({ items: [], total: 0 }),
478
- }),
479
- { name: "cart-storage" } // localStorage key
480
- )
150
+ import { persist } from "zustand/middleware";
151
+ const useStore = create<Store>()(
152
+ persist(
153
+ (set, get) => ({
154
+ count: 0,
155
+ increment: () => set(s => ({ count: s.count + 1 })),
156
+ getDoubled: () => get().count * 2,
157
+ }),
158
+ { name: "my-store" }
481
159
  )
482
160
  );
483
-
484
- // Usage in component:
485
- function CartIcon() {
486
- // ✅ Selector — only re-renders when items.length changes
487
- const count = useCartStore((state) => state.items.length);
488
- return <span className="badge">{count}</span>;
489
- }
490
-
491
- // ❌ HALLUCINATION TRAP: Always use selectors with Zustand
492
- // useCartStore() without a selector subscribes to EVERYTHING
493
- // and causes unnecessary re-renders on any store change
494
- ```
495
-
496
- ### TanStack React Query (Server State)
497
-
498
- ```tsx
499
- import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
500
-
501
- function UserList() {
502
- const { data, isLoading, error } = useQuery({
503
- queryKey: ["users"],
504
- queryFn: () => fetch("/api/users").then((r) => r.json()),
505
- staleTime: 5 * 60 * 1000, // 5 min before refetch
506
- gcTime: 10 * 60 * 1000, // 10 min cache lifetime
507
- });
508
-
509
- if (isLoading) return <Skeleton />;
510
- if (error) return <ErrorDisplay error={error} />;
511
-
512
- return data.map((user: User) => <UserCard key={user.id} user={user} />);
513
- }
514
-
515
- // Mutation with optimistic update
516
- function useDeleteUser() {
517
- const queryClient = useQueryClient();
518
-
519
- return useMutation({
520
- mutationFn: (userId: string) =>
521
- fetch(`/api/users/${userId}`, { method: "DELETE" }),
522
-
523
- onMutate: async (userId) => {
524
- await queryClient.cancelQueries({ queryKey: ["users"] });
525
- const previous = queryClient.getQueryData<User[]>(["users"]);
526
-
527
- queryClient.setQueryData<User[]>(["users"], (old) =>
528
- old?.filter((u) => u.id !== userId)
529
- );
530
-
531
- return { previous }; // context for rollback
532
- },
533
-
534
- onError: (_err, _userId, context) => {
535
- queryClient.setQueryData(["users"], context?.previous); // rollback
536
- },
537
-
538
- onSettled: () => {
539
- queryClient.invalidateQueries({ queryKey: ["users"] }); // refetch
540
- },
541
- });
542
- }
543
-
544
- // ❌ HALLUCINATION TRAP: `cacheTime` was renamed to `gcTime` in React Query v5
545
- // ❌ HALLUCINATION TRAP: Import from "@tanstack/react-query", NOT "react-query"
546
- ```
547
-
548
- ---
549
-
550
- ## Performance Optimization
551
-
552
- ### Code Splitting & Lazy Loading
553
-
554
- ```tsx
555
- import { lazy, Suspense } from "react";
556
-
557
- // Lazy load heavy components
558
- const HeavyChart = lazy(() => import("./HeavyChart"));
559
- const AdminPanel = lazy(() => import("./AdminPanel"));
560
-
561
- function App() {
562
- return (
563
- <Suspense fallback={<Skeleton />}>
564
- <HeavyChart />
565
- </Suspense>
566
- );
567
- }
568
-
569
- // Named exports require:
570
- const Chart = lazy(() =>
571
- import("./Charts").then((mod) => ({ default: mod.BarChart }))
572
- );
161
+ // ❌ TRAP: Do NOT destructure the whole store — causes re-render on every change
162
+ // const count = useStore(s => s.count); // selector
573
163
  ```
574
164
 
575
- ### Virtual Scrolling (Large Lists)
576
-
165
+ ### Jotai (preferred for derived/atomic state)
577
166
  ```tsx
578
- import { useVirtualizer } from "@tanstack/react-virtual";
579
-
580
- function VirtualList({ items }: { items: Item[] }) {
581
- const parentRef = useRef<HTMLDivElement>(null);
582
-
583
- const virtualizer = useVirtualizer({
584
- count: items.length,
585
- getScrollElement: () => parentRef.current,
586
- estimateSize: () => 50, // estimated row height in px
587
- overscan: 5, // render 5 extra items above/below viewport
588
- });
589
-
590
- return (
591
- <div ref={parentRef} style={{ height: 600, overflow: "auto" }}>
592
- <div style={{ height: virtualizer.getTotalSize(), position: "relative" }}>
593
- {virtualizer.getVirtualItems().map((virtualRow) => (
594
- <div
595
- key={virtualRow.key}
596
- style={{
597
- position: "absolute",
598
- top: virtualRow.start,
599
- width: "100%",
600
- height: virtualRow.size,
601
- }}
602
- >
603
- {items[virtualRow.index].name}
604
- </div>
605
- ))}
606
- </div>
607
- </div>
608
- );
609
- }
610
-
611
- // Use when: list has 500+ items
612
- // Do NOT use for: lists under 100 items (adds complexity for no gain)
167
+ import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
168
+ const countAtom = atom(0);
169
+ const doubledAtom = atom(get => get(countAtom) * 2); // derived atom
170
+ // TRAP: atomWithStorage is from jotai/utils — NOT from jotai
171
+ import { atomWithStorage } from "jotai/utils";
613
172
  ```
614
173
 
615
- ### Ref Patterns
616
-
174
+ ### React Query / TanStack Query
617
175
  ```tsx
618
- // useRef for DOM access and mutable values
619
- function VideoPlayer() {
620
- const videoRef = useRef<HTMLVideoElement>(null);
621
-
622
- function handlePlay() {
623
- videoRef.current?.play();
624
- }
625
-
626
- return <video ref={videoRef} src="/movie.mp4" />;
627
- }
628
-
629
- // Callback refs for dynamic ref assignment
630
- function MeasuredBox() {
631
- const [height, setHeight] = useState(0);
632
-
633
- const measuredRef = useCallback((node: HTMLDivElement | null) => {
634
- if (node) {
635
- setHeight(node.getBoundingClientRect().height);
636
- }
637
- }, []);
638
-
639
- return <div ref={measuredRef}>Content with height: {height}</div>;
640
- }
641
-
642
- // Forwarding refs (for library components)
643
- const TextInput = forwardRef<HTMLInputElement, InputProps>(
644
- function TextInput(props, ref) {
645
- return <input ref={ref} {...props} />;
646
- }
647
- );
648
-
649
- // React 19: ref is a regular prop — forwardRef is being phased out
650
- function TextInput19({ ref, ...props }: InputProps & { ref?: Ref<HTMLInputElement> }) {
651
- return <input ref={ref} {...props} />;
652
- }
653
- ```
654
-
655
- ---
656
-
657
- ## TypeScript Patterns
658
-
659
- ### Component Props
660
-
661
- ```tsx
662
- // Discriminated union props
663
- type ButtonProps =
664
- | { variant: "link"; href: string; onClick?: never }
665
- | { variant: "button"; onClick: () => void; href?: never };
666
-
667
- function Button(props: ButtonProps) {
668
- if (props.variant === "link") {
669
- return <a href={props.href}>Link</a>;
670
- }
671
- return <button onClick={props.onClick}>Button</button>;
672
- }
673
-
674
- // Polymorphic component (as prop)
675
- type PolymorphicProps<E extends React.ElementType> = {
676
- as?: E;
677
- children: React.ReactNode;
678
- } & Omit<React.ComponentPropsWithoutRef<E>, "as" | "children">;
679
-
680
- function Text<E extends React.ElementType = "span">({
681
- as,
682
- children,
683
- ...props
684
- }: PolymorphicProps<E>) {
685
- const Component = as || "span";
686
- return <Component {...props}>{children}</Component>;
687
- }
688
-
689
- // Usage:
690
- <Text as="h1" className="title">Heading</Text>
691
- <Text as="p">Paragraph</Text>
692
- <Text as="a" href="/about">Link</Text>
693
- ```
694
-
695
- ### Generic Components
696
-
697
- ```tsx
698
- // Generic list component
699
- interface SelectProps<T> {
700
- items: T[];
701
- selected: T | null;
702
- onSelect: (item: T) => void;
703
- getLabel: (item: T) => string;
704
- getKey: (item: T) => string;
705
- }
706
-
707
- function Select<T>({ items, selected, onSelect, getLabel, getKey }: SelectProps<T>) {
708
- return (
709
- <ul role="listbox">
710
- {items.map((item) => (
711
- <li
712
- key={getKey(item)}
713
- role="option"
714
- aria-selected={item === selected}
715
- onClick={() => onSelect(item)}
716
- >
717
- {getLabel(item)}
718
- </li>
719
- ))}
720
- </ul>
721
- );
722
- }
176
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
177
+ const { data, isPending, error } = useQuery({
178
+ queryKey: ["user", userId],
179
+ queryFn: () => fetchUser(userId),
180
+ staleTime: 5 * 60 * 1000, // don't refetch for 5 minutes
181
+ });
182
+ // Optimistic update:
183
+ const qc = useQueryClient();
184
+ const mutation = useMutation({
185
+ mutationFn: updateUser,
186
+ onMutate: async (newUser) => {
187
+ await qc.cancelQueries({ queryKey: ["user", newUser.id] });
188
+ const prev = qc.getQueryData(["user", newUser.id]);
189
+ qc.setQueryData(["user", newUser.id], newUser);
190
+ return { prev };
191
+ },
192
+ onError: (_, __, ctx) => qc.setQueryData(["user"], ctx?.prev),
193
+ onSettled: () => qc.invalidateQueries({ queryKey: ["user"] }),
194
+ });
723
195
  ```
724
196
 
725
197
  ---
726
198
 
727
- ## Accessibility (Mandatory)
199
+ ## Performance
728
200
 
729
- ```tsx
730
- // Every interactive element MUST be accessible
731
-
732
- // Keyboard navigation
733
- <button onClick={handleClick} onKeyDown={(e) => {
734
- if (e.key === "Enter" || e.key === " ") handleClick();
735
- }}>
736
- Action
737
- </button>
738
-
739
- // ✅ ARIA for custom components
740
- <div
741
- role="dialog"
742
- aria-modal={true}
743
- aria-labelledby="dialog-title"
744
- aria-describedby="dialog-desc"
745
- >
746
- <h2 id="dialog-title">Confirm Delete</h2>
747
- <p id="dialog-desc">This action cannot be undone.</p>
748
- </div>
749
-
750
- // ✅ Focus management
751
- function Modal({ isOpen, onClose }: ModalProps) {
752
- const closeRef = useRef<HTMLButtonElement>(null);
753
-
754
- useEffect(() => {
755
- if (isOpen) closeRef.current?.focus(); // focus trap
756
- }, [isOpen]);
757
-
758
- return isOpen ? (
759
- <div role="dialog" aria-modal>
760
- <button ref={closeRef} onClick={onClose}>Close</button>
761
- </div>
762
- ) : null;
763
- }
764
-
765
- // ✅ Screen reader text
766
- <button aria-label="Close dialog">
767
- <XIcon aria-hidden="true" />
768
- </button>
769
- ```
201
+ | Technique | When |
202
+ |-----------|------|
203
+ | `useDeferredValue` | Expensive derived render (charts, filters) |
204
+ | `useTransition` | Page nav, tab switches, data load |
205
+ | `lazy()` + `Suspense` | Code-split heavy components |
206
+ | `<Virtuoso>` / `<WindowVirtualizer>` | Lists > 200 items |
207
+ | Avoid `useEffect` for state transforms | Use `useMemo` or derived atoms |
770
208
 
771
209
  ---
772
210
 
773
- ## Common Patterns
774
-
775
- ### Debounced Search Input
776
-
777
- ```tsx
778
- function useDebounce<T>(value: T, delay: number): T {
779
- const [debouncedValue, setDebouncedValue] = useState(value);
780
-
781
- useEffect(() => {
782
- const timer = setTimeout(() => setDebouncedValue(value), delay);
783
- return () => clearTimeout(timer);
784
- }, [value, delay]);
785
-
786
- return debouncedValue;
787
- }
788
-
789
- function SearchInput() {
790
- const [query, setQuery] = useState("");
791
- const debouncedQuery = useDebounce(query, 300);
792
-
793
- useEffect(() => {
794
- if (debouncedQuery) {
795
- searchAPI(debouncedQuery);
796
- }
797
- }, [debouncedQuery]);
798
-
799
- return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
800
- }
801
- ```
802
-
803
- ### Intersection Observer Hook
804
-
805
- ```tsx
806
- function useIntersectionObserver(
807
- options?: IntersectionObserverInit
808
- ): [React.RefCallback<Element>, boolean] {
809
- const [isIntersecting, setIsIntersecting] = useState(false);
810
-
811
- const ref = useCallback(
812
- (node: Element | null) => {
813
- if (!node) return;
814
-
815
- const observer = new IntersectionObserver(([entry]) => {
816
- setIsIntersecting(entry.isIntersecting);
817
- }, options);
818
-
819
- observer.observe(node);
820
- return () => observer.disconnect();
821
- },
822
- [options]
823
- );
824
-
825
- return [ref, isIntersecting];
826
- }
827
-
828
- // Usage:
829
- function LazyImage({ src }: { src: string }) {
830
- const [ref, isVisible] = useIntersectionObserver({ threshold: 0.1 });
831
- return <div ref={ref}>{isVisible && <img src={src} />}</div>;
832
- }
833
- ```
834
-
835
- ### Previous Value Hook
211
+ ## Refs & DOM
836
212
 
837
213
  ```tsx
838
- function usePrevious<T>(value: T): T | undefined {
839
- const ref = useRef<T>(undefined);
840
-
841
- useEffect(() => {
842
- ref.current = value;
843
- }, [value]);
844
-
845
- return ref.current;
846
- }
847
-
848
- // Usage: detect state changes
849
- function Counter({ count }: { count: number }) {
850
- const prevCount = usePrevious(count);
851
- const direction = prevCount !== undefined && count > prevCount ? "↑" : "↓";
852
-
853
- return <span>{direction} {count}</span>;
214
+ // React 19: ref is now a prop (no forwardRef needed)
215
+ function Input({ ref, ...props }: ComponentProps<"input"> & { ref?: Ref<HTMLInputElement> }) {
216
+ return <input ref={ref} {...props} />;
854
217
  }
218
+ // ❌ TRAP: forwardRef is deprecated in React 19 — still works but not needed
855
219
  ```
856
220
 
857
221
  ---
858
222
 
859
- ## Output Format
860
-
861
- When this skill produces or reviews code, structure your output as follows:
862
-
863
- ```
864
- ━━━ React Specialist Report ━━━━━━━━━━━━━━━━━━━━━━━━
865
- Skill: React Specialist
866
- React Ver: 19+
867
- Scope: [N files · N components]
868
- ─────────────────────────────────────────────────
869
- ✅ Passed: [checks that passed, or "All clean"]
870
- ⚠️ Warnings: [non-blocking issues, or "None"]
871
- ❌ Blocked: [blocking issues requiring fix, or "None"]
872
- ─────────────────────────────────────────────────
873
- VBC status: PENDING → VERIFIED
874
- Evidence: [test output / lint pass / compile success]
875
- ```
876
-
877
- **VBC (Verification-Before-Completion) is mandatory.**
878
- Do not mark status as VERIFIED until concrete terminal evidence is provided.
879
-
880
- ---
881
-
882
- ## 🤖 LLM-Specific Traps
883
-
884
- AI coding assistants often fall into specific bad habits when generating React code. These are strictly forbidden:
885
-
886
- 1. **Class Components:** Never generate `class extends React.Component` or lifecycle methods (`componentDidMount`, `componentDidUpdate`) in React 19+ projects. Use functional components with hooks exclusively.
887
- 2. **Manual Memoization in React 19:** Do NOT add `useMemo`, `useCallback`, or `React.memo` if the React Compiler is enabled. The compiler handles this automatically.
888
- 3. **`useFormState` (Wrong Name):** The correct hook name is `useActionState`, not `useFormState`. The canary name was changed before stable release.
889
- 4. **`<Context.Provider>`:** React 19 uses `<Context value={}>` directly. The `.Provider` pattern is deprecated.
890
- 5. **`useEffect` for Data Fetching:** Use Server Components, React Query, SWR, or the `use()` hook. `useEffect` fetch patterns cause waterfalls, have no caching, and lack error/loading states.
891
- 6. **Missing Keys in Mapped Lists:** Always use unique, stable IDs as keys. Never use array index as a key unless the list is truly static and never reorders.
892
- 7. **Prop Drilling Past 3 Levels:** If passing props through more than 3 intermediate components, use Context, Zustand, or Jotai instead.
893
- 8. **`cacheTime` in React Query v5:** The property was renamed to `gcTime`. Importing from `"react-query"` instead of `"@tanstack/react-query"` is also wrong.
894
- 9. **Zustand Without Selectors:** `useStore()` without a selector subscribes to all state changes. Always use `useStore((state) => state.specificValue)`.
895
- 10. **`forwardRef` in React 19:** Refs are regular props in React 19. `forwardRef` is being phased out. Use `ref` as a normal prop.
896
-
897
- ---
898
-
899
- ## 🏛️ Tribunal Integration (Anti-Hallucination)
900
-
901
- **Slash command: `/tribunal-frontend`**
902
- **Active reviewers: `logic` · `security` · `frontend` · `type-safety`**
903
-
904
- ### ❌ Forbidden AI Tropes
905
-
906
- 1. **Blind Assumptions:** Never make an assumption without documenting it clearly with `// VERIFY: [reason]`.
907
- 2. **Silent Degradation:** Catching and suppressing errors without logging or displaying error boundaries.
908
- 3. **Context Amnesia:** Forgetting the user's React version or framework constraints.
909
- 4. **Sloppy Layout Generation:** Never build UI without explicit dimensional boundaries — use strict 4px grid spacing and explicit flex/grid layouts.
910
-
911
- ### ✅ Pre-Flight Self-Audit
912
-
913
- Review these questions before confirming output:
914
- ```
915
- ✅ Did I use strictly functional components with hooks?
916
- ✅ Did I avoid manual memoization if React Compiler is active?
917
- ✅ Did I use useActionState (not useFormState) for form actions?
918
- ✅ Did I use <Context value={}> (not <Context.Provider>)?
919
- ✅ Are array maps using unique, stable keys (not index)?
920
- ✅ Did I handle loading, error, and empty states?
921
- ✅ Did I use Suspense + Error Boundaries for async components?
922
- ✅ Is the component accessible (ARIA, keyboard, focus mgmt)?
923
- ✅ Did I include AbortController cleanup in useEffect fetches?
924
- ✅ Did I use Zustand selectors to prevent unnecessary re-renders?
925
- ```
926
-
927
- ### 🛑 Verification-Before-Completion (VBC) Protocol
928
-
929
- **CRITICAL:** You must follow a strict "evidence-based closeout" state machine.
930
- - ❌ **Forbidden:** Assuming a React component "works" just because it compiles or because the bundler gives no immediate warnings.
931
- - ✅ **Required:** You are explicitly forbidden from completing your task without providing **concrete terminal/test evidence** (e.g., passing Jest/Vitest logs, successful build output, or specific CLI execution results) proving the build is error-free.
223
+ ## Testing Checklist
224
+ - ✅ Use React Testing Library — test behavior, not implementation
225
+ - `userEvent` over `fireEvent` (async, closer to real interaction)
226
+ - ✅ Mock server calls with MSW (Mock Service Worker)
227
+ - ❌ Never test internal state, ref values, or component instances