dev-booster 1.18.0 → 1.18.1

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 (46) hide show
  1. package/package.json +1 -1
  2. package/template/.devbooster/MANIFEST.md +34 -1
  3. package/template/.devbooster/boosters/advisor.md +5 -0
  4. package/template/.devbooster/boosters/audit.md +20 -0
  5. package/template/.devbooster/boosters/auto-triage.md +5 -0
  6. package/template/.devbooster/boosters/backend.md +12 -0
  7. package/template/.devbooster/boosters/builder.md +5 -0
  8. package/template/.devbooster/boosters/code-audit.md +19 -0
  9. package/template/.devbooster/boosters/coder.md +5 -0
  10. package/template/.devbooster/boosters/create.md +5 -0
  11. package/template/.devbooster/boosters/debug.md +19 -0
  12. package/template/.devbooster/boosters/deploy.md +12 -0
  13. package/template/.devbooster/boosters/discovery.md +5 -0
  14. package/template/.devbooster/boosters/frontend.md +12 -0
  15. package/template/.devbooster/boosters/implementation.md +5 -0
  16. package/template/.devbooster/boosters/investigation.md +5 -0
  17. package/template/.devbooster/boosters/performance.md +12 -0
  18. package/template/.devbooster/boosters/planning.md +5 -0
  19. package/template/.devbooster/boosters/refactor.md +12 -0
  20. package/template/.devbooster/boosters/review.md +12 -0
  21. package/template/.devbooster/boosters/security.md +14 -0
  22. package/template/.devbooster/boosters/smart-task.md +27 -16
  23. package/template/.devbooster/boosters/stack-refresh.md +20 -0
  24. package/template/.devbooster/boosters/testing.md +12 -0
  25. package/template/.devbooster/hub/knowledge/angular-patterns.md +185 -0
  26. package/template/.devbooster/hub/knowledge/dependency-guide.md +175 -0
  27. package/template/.devbooster/hub/knowledge/eslint-migration.md +206 -0
  28. package/template/.devbooster/hub/knowledge/index.md +91 -0
  29. package/template/.devbooster/hub/knowledge/migration-guides.md +137 -0
  30. package/template/.devbooster/hub/knowledge/monorepo-patterns.md +121 -0
  31. package/template/.devbooster/hub/knowledge/nestjs-patterns.md +185 -0
  32. package/template/.devbooster/hub/knowledge/nextjs-pitfalls.md +226 -0
  33. package/template/.devbooster/hub/knowledge/nodejs-patterns.md +148 -0
  34. package/template/.devbooster/hub/knowledge/package-manager-patterns.md +143 -0
  35. package/template/.devbooster/hub/knowledge/prisma-postgresql-patterns.md +188 -0
  36. package/template/.devbooster/hub/knowledge/react-patterns.md +500 -0
  37. package/template/.devbooster/hub/knowledge/tailwind-shadcn-patterns.md +146 -0
  38. package/template/.devbooster/hub/knowledge/tanstack-patterns.md +278 -0
  39. package/template/.devbooster/hub/knowledge/testing-patterns.md +164 -0
  40. package/template/.devbooster/hub/knowledge/trpc-patterns.md +212 -0
  41. package/template/.devbooster/hub/knowledge/typescript-patterns.md +219 -0
  42. package/template/.devbooster/hub/knowledge/upgrade-fallout.md +154 -0
  43. package/template/.devbooster/hub/knowledge/vite-patterns.md +177 -0
  44. package/template/.devbooster/rules/GUIDE.md +24 -0
  45. package/template/.devbooster/rules/PROTOCOL.md +3 -2
  46. package/template/.devbooster/rules/TRIGGERS.md +14 -0
@@ -0,0 +1,500 @@
1
+ # ⚛️ React Patterns
2
+
3
+ > **Purpose:** Problematic patterns and fixes for React 19 + React Hooks
4
+ > **Primary official sources:** [React documentation](https://react.dev) · [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) · [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)
5
+
6
+ ## Project Convention Decision Rule
7
+
8
+ Before applying this guidance, verify the installed versions, local rules, rendering model, existing component and hook abstractions, data-fetching conventions, and tests. Preserve a valid established project convention; do not replace it only because another documented approach is also valid. Use official sources to verify API behavior, compatibility, constraints, and migrations. Recommend a change only when the developer requests it or evidence shows the current approach is incompatible, unsafe, deprecated, broken, or responsible for a verified issue.
9
+
10
+ ---
11
+
12
+ ## Index
13
+
14
+ 1. [Components Created During Render](#components-created-during-render)
15
+ 2. [Decorative useMemo](#decorative-usememo)
16
+ 3. [Initial State from localStorage](#initial-state-from-localstorage)
17
+ 4. [Fetching Data in Effects](#fetching-data-in-effects)
18
+ 5. [Sequential Fetches with State Dependencies](#sequential-fetches-with-state-dependencies)
19
+ 6. [Legitimate Fetch in an Effect](#legitimate-fetch-in-an-effect)
20
+ 7. [Derived State That Should Be Computed](#derived-state-that-should-be-computed)
21
+ 8. [Lazy State Initializer](#lazy-state-initializer)
22
+ 9. [Key to Reset State](#key-to-reset-state)
23
+ 10. [Effect with Two Responsibilities](#effect-with-two-responsibilities)
24
+ 11. [Keyed Animation Reset](#keyed-animation-reset)
25
+ 12. [Function Placement Around Effects](#function-placement-around-effects)
26
+ 13. [exhaustive-deps Suppressions](#exhaustive-deps-suppressions)
27
+ 14. [useRef Without Initial Value in React 19](#useref-without-initial-value-in-react-19)
28
+ 15. [State Mutation](#state-mutation)
29
+ 16. [Choose an Async UI Strategy Before Adding an Effect](#choose-an-async-ui-strategy-before-adding-an-effect)
30
+ 17. [Suspense Boundaries Are Not Data Fetchers](#suspense-boundaries-are-not-data-fetchers)
31
+ 18. [Extract a Custom Hook Only for a Reusable Stateful Contract](#extract-a-custom-hook-only-for-a-reusable-stateful-contract)
32
+
33
+ ---
34
+
35
+ ## Components Created During Render
36
+
37
+ ### Problem
38
+ Defining a React component inside the body of another component. On each render, a new component function/type is created. Because its identity changes, React unmounts and remounts that subtree, losing local state and defeating memoization.
39
+
40
+ *Source: [react.dev — Keeping Components Pure](https://react.dev/learn/keeping-components-pure)*
41
+
42
+ ### Code (symptom)
43
+ ```tsx
44
+ const Card = () => {
45
+ const Icon = () => <svg>{/* ... */}</svg> // ← RECREATED ON EVERY RENDER
46
+ return <Icon />
47
+ }
48
+ ```
49
+
50
+ ### Fix
51
+ ```tsx
52
+ // Option 1: Inline the element (simplest)
53
+ const Card = () => <svg>{/* ... */}</svg>
54
+
55
+ // Option 2: Extract outside the component
56
+ const Icon = () => <svg>{/* ... */}</svg>
57
+ const Card = () => <Icon />
58
+
59
+ // Option 3: Separate file (when reusable)
60
+ ```
61
+ ### When NOT to apply
62
+ - Rare — in practice, inline or extract always resolves it
63
+ - If the subcomponent needs many props from the parent scope, consider passing it as `children`
64
+
65
+ ---
66
+
67
+ ## Decorative useMemo
68
+
69
+ ### Problem
70
+ `useMemo` wrapping trivial computations where the cost of the hook (dependency comparison + closure allocation) is greater than the computation itself.
71
+
72
+ *Source: [react.dev — useMemo](https://react.dev/reference/react/useMemo)*
73
+
74
+ > "You should only rely on useMemo as a performance optimization. If your code doesn't work without it, find the underlying problem and fix it first."
75
+
76
+ ### Code (symptom)
77
+ ```tsx
78
+ const colorClass = useMemo(() => {
79
+ switch (status) {
80
+ case 'active': return 'text-green-600'
81
+ case 'inactive': return 'text-red-600'
82
+ }
83
+ }, [status])
84
+ ```
85
+
86
+ ### Fix
87
+ ```tsx
88
+ const colorClass = status === 'active' ? 'text-green-600' : 'text-red-600'
89
+ ```
90
+
91
+ ### When to keep `useMemo`
92
+ - Profiling shows that the calculation is meaningfully expensive
93
+ - A stable reference is required by a memoized child and avoids a measured re-render
94
+
95
+ Do not use `useMemo` merely because a calculation contains an array or several lines. For Effects, prefer removing unnecessary object or function dependencies—for example, create them inside the Effect—rather than memoizing solely to control when an Effect runs.
96
+
97
+ ### Note on React Compiler
98
+ The React Compiler can automatically memoize eligible values and functions. It does not make every existing manual memo safe to remove: confirm the compiler is enabled for the code and validate behavior and performance before changing manual memoization.
99
+
100
+ ---
101
+
102
+ ## Initial State from localStorage
103
+
104
+ ### Problem
105
+ Using an Effect only to read a synchronous browser value such as `localStorage` adds a render after mount in client-only applications.
106
+
107
+ *Source: [react.dev — useState: Avoiding recreating the initial state](https://react.dev/reference/react/useState#avoiding-recreating-the-initial-state)*
108
+
109
+ ### Fix for client-only rendering
110
+ ```tsx
111
+ const [data, setData] = useState(() => {
112
+ try {
113
+ const stored = localStorage.getItem('config')
114
+ return stored ? JSON.parse(stored) : []
115
+ } catch {
116
+ return []
117
+ }
118
+ })
119
+ ```
120
+
121
+ ### SSR caveat
122
+ `typeof window === 'undefined'` prevents a server-side exception, but it can still produce different initial server and client output when persisted data exists. For SSR, use the same initial value on server and client, or isolate the browser-only state behind a client-only boundary; hydrate persisted state after mount when matching initial HTML is required.
123
+
124
+ ---
125
+
126
+ ## Fetching Data in Effects
127
+
128
+ ### Problem
129
+ Fetching in an Effect is supported when a component must synchronize with an external system, but it needs complete dependencies and stale-response handling. A loop occurs only when an Effect changes a dependency to a new value that makes the Effect run again—not simply because it calls a state setter.
130
+
131
+ *Source: [react.dev — Fetching data with Effects](https://react.dev/reference/react/useEffect#fetching-data-with-effects)*
132
+
133
+ ### Dependency-correct example
134
+ ```tsx
135
+ useEffect(() => {
136
+ const controller = new AbortController()
137
+
138
+ async function loadDetails() {
139
+ setLoading(true)
140
+ try {
141
+ const response = await fetch(`/api/items/${selectedId}`, {
142
+ signal: controller.signal,
143
+ })
144
+ if (!response.ok) throw new Error('Failed to load item')
145
+ const details = await response.json()
146
+ if (!controller.signal.aborted) setDetails(details)
147
+ } catch (error) {
148
+ if (!controller.signal.aborted) setError(error)
149
+ } finally {
150
+ if (!controller.signal.aborted) setLoading(false)
151
+ }
152
+ }
153
+
154
+ if (selectedId) void loadDetails()
155
+ return () => controller.abort()
156
+ }, [selectedId])
157
+ ```
158
+
159
+ If an initial request selects an ID, keep that request separate from the details Effect; the details Effect can then react to `selectedId` without also changing it. Define async helpers inside the Effect when they are only used there, so their dependencies are explicit.
160
+
161
+ ### Architectural choice
162
+ Effect-based fetching is valid, but it does not provide caching, request deduplication, or server preloading by itself. Prefer framework-integrated data loading or a caching library when those capabilities are needed.
163
+
164
+ ---
165
+
166
+ ## Sequential Fetches with State Dependencies
167
+
168
+ **Evidence:** Field-validated in a real audit.
169
+
170
+ ### Problem
171
+ One request updates state required by a second request, while both requests are placed in the same Effect that depends on that state. This can re-run the first request after it changes the dependency and create repeated requests or a loop.
172
+
173
+ ### Code (symptom)
174
+ ```tsx
175
+ useEffect(() => {
176
+ fetchPrimaryData() // updates selectedId
177
+ fetchDetails() // reads selectedId
178
+ }, [selectedId])
179
+ ```
180
+
181
+ ### Fix
182
+ Separate the responsibilities so that the second Effect reacts to the value produced by the first:
183
+ ```tsx
184
+ useEffect(() => {
185
+ void fetchPrimaryData()
186
+ }, [])
187
+
188
+ useEffect(() => {
189
+ if (selectedId) void fetchDetails(selectedId)
190
+ }, [selectedId])
191
+ ```
192
+
193
+ If both requests must run exactly once as one workflow, keep the selected value local to a single async function instead of using state as the handoff.
194
+
195
+ ### Verify first
196
+ Confirm whether `selectedId` is intentionally refreshed later. If it is, use complete dependencies, stale-response protection, and a deliberate refresh strategy rather than an empty dependency array.
197
+
198
+ ---
199
+
200
+ ## Legitimate Fetch in an Effect
201
+
202
+ **Evidence:** Field-validated in a real audit.
203
+
204
+ ### Context
205
+ A fetch on mount is not automatically an error. It can be the correct way to synchronize visible client UI with a network resource, browser API, or another external system.
206
+
207
+ *Source: [react.dev — Fetching data with Effects](https://react.dev/reference/react/useEffect#fetching-data-with-effects)*
208
+
209
+ ### Decision guidance
210
+ Keep the Effect when the data must be synchronized while the component is visible and no framework-level data-loading path is available. The Effect still needs complete dependencies, loading/error handling, and protection against stale responses.
211
+
212
+ For server-rendered applications or data that benefits from caching, preloading, or request deduplication, prefer the framework's data-loading mechanism or a client cache. This is an architectural choice, not an automatic lint fix.
213
+
214
+ ---
215
+
216
+ ## Derived State That Should Be Computed
217
+
218
+ ### Problem
219
+ State that is purely derived from props or other state, but is being synchronized via `useEffect` instead of being computed directly.
220
+
221
+ *Source: [react.dev — You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)*
222
+
223
+ > "When something can be calculated from the existing props or state, don't put it in state. Instead, calculate it during rendering."
224
+
225
+ ### Code (symptom)
226
+ ```tsx
227
+ const [isActive, setIsActive] = useState(false)
228
+ useEffect(() => {
229
+ setIsActive(statusCode === 200)
230
+ }, [statusCode])
231
+ ```
232
+
233
+ ### Fix
234
+ ```tsx
235
+ const isActive = statusCode === 200
236
+ ```
237
+
238
+ ### When not to apply
239
+ - The value needs to be set independently (it is not purely derived)
240
+
241
+ An expensive derived value should still be calculated from existing state or props, optionally with `useMemo` when profiling justifies it; it should not be synchronized into separate state.
242
+
243
+ ---
244
+
245
+ ## Lazy State Initializer
246
+
247
+ ### Problem
248
+ Initial state calculation function being called on every render, even though the result is only used on mount.
249
+
250
+ *Source: [react.dev — useState: Avoiding recreating the initial state](https://react.dev/reference/react/useState#avoiding-recreating-the-initial-state)*
251
+
252
+ ### Code (symptom)
253
+ ```tsx
254
+ const [items, setItems] = useState(createInitialItems()) // called on every render
255
+ ```
256
+
257
+ ### Fix
258
+ ```tsx
259
+ const [items, setItems] = useState(createInitialItems) // passing the function, not the result
260
+ ```
261
+
262
+ ### When to use
263
+ - Expensive initial computation (large arrays, complex transformations)
264
+ - Reading from `localStorage` / `sessionStorage` on mount
265
+
266
+ ---
267
+
268
+ ## Key to Reset State
269
+
270
+ ### Problem
271
+ Effect resetting internal state when props change, typically in modals, forms, or tabs.
272
+
273
+ *Source: [react.dev — useState: Resetting state with a key](https://react.dev/reference/react/useState#resetting-state-with-a-key)*
274
+
275
+ ### Code (symptom)
276
+ ```tsx
277
+ <Profile userId={userId} />
278
+ // useEffect inside Profile: if (prev.userId !== userId) { setState(initial) }
279
+ ```
280
+
281
+ ### Fix
282
+ ```tsx
283
+ <Profile key={userId} userId={userId} />
284
+ ```
285
+
286
+ ### Why it works
287
+ React unmounts/remounts the component when the `key` changes. Local state starts fresh without an Effect.
288
+
289
+ ### Caveat
290
+ A key reset also runs cleanup and resets the entire subtree, including focus, pending work, and every local state value. Use it when a full reset is intentional.
291
+
292
+ ---
293
+
294
+ ## Effect with Two Responsibilities
295
+
296
+ ### Problem
297
+ A single `useEffect` managing unrelated responsibilities (for example, navigation and data fetching) is harder to reason about. Splitting it must preserve each responsibility's original trigger.
298
+
299
+ *Source: [react.dev — You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)*
300
+
301
+ ### Fix
302
+ ```tsx
303
+ useEffect(() => {
304
+ setScreen('details')
305
+ }, [itemId]) // preserves the original behavior
306
+
307
+ useEffect(() => {
308
+ async function load() {
309
+ const response = await fetch(`/api/items/${itemId}`)
310
+ setData(await response.json())
311
+ }
312
+ void load()
313
+ }, [itemId])
314
+ ```
315
+
316
+ When navigation is caused by a user action, prefer updating screen state in that event handler. If the actions must occur in a specific order, keep that orchestration in the event or a single deliberate workflow instead of relying on Effect ordering.
317
+
318
+ ---
319
+
320
+ ## Keyed Animation Reset
321
+
322
+ ### Problem
323
+ An animation with local progress may need to restart when an index changes.
324
+
325
+ ### Example
326
+ ```tsx
327
+ function ProgressBar() {
328
+ const [progress, setProgress] = useState(0)
329
+
330
+ useEffect(() => {
331
+ if (progress >= 100) return
332
+ const timer = setTimeout(() => {
333
+ setProgress(value => Math.min(value + 2, 100))
334
+ }, 100)
335
+ return () => clearTimeout(timer)
336
+ }, [progress])
337
+
338
+ return <div style={{ width: `${progress}%` }} />
339
+ }
340
+
341
+ // Usage: <ProgressBar key={currentIndex} />
342
+ ```
343
+
344
+ ### Why it works
345
+ The `key` remounts the component and restarts its local progress. React runs the previous Effect cleanup during that unmount; the timeout also stops once progress reaches `100`.
346
+
347
+ ---
348
+
349
+ ## Function Placement Around Effects
350
+
351
+ **Evidence:** Field-validated in a real audit.
352
+
353
+ ### Context
354
+ A function declared with `const` after an Effect is not automatically a JavaScript temporal-dead-zone error: an Effect runs after the component body finishes. However, source order can make a callback harder to inspect and can interact with Hook lint/compiler analysis when the function is recreated during render or omitted from dependencies.
355
+
356
+ *Source: [react.dev — Specifying reactive dependencies](https://react.dev/reference/react/useEffect#specifying-reactive-dependencies)*
357
+
358
+ ### Safer structure
359
+ When a helper is used only by one Effect, define it inside that Effect:
360
+ ```tsx
361
+ useEffect(() => {
362
+ async function load() {
363
+ const response = await fetch(`/api/items/${itemId}`)
364
+ if (!response.ok) throw new Error('Failed to load item')
365
+ setData(await response.json())
366
+ }
367
+
368
+ void load()
369
+ }, [itemId])
370
+ ```
371
+
372
+ When a helper is shared, define it before its consumers and make its reactive dependencies explicit with `useCallback`, or restructure the flow so the Effect has complete dependencies.
373
+
374
+ ### Verify first
375
+ Read the exact lint/compiler finding before reordering code. The fix may be source order, dependency management, or moving a helper into the Effect; it is not automatically a hoisting fix.
376
+
377
+ ---
378
+
379
+ ## exhaustive-deps Suppressions
380
+
381
+ ### Problem
382
+ `eslint-disable-next-line react-hooks/exhaustive-deps` scattered throughout the code. Each suppression hides a specific reason.
383
+
384
+ *Source: [react.dev — useEffect: Specifying reactive dependencies](https://react.dev/reference/react/useEffect#specifying-reactive-dependencies)*
385
+
386
+ ### Principle
387
+ Dependencies are determined by the reactive values read by the Effect; they are not selected to control when it runs. If adding a required dependency causes a loop or repeated request, restructure the Effect or its object/function inputs instead of retaining a suppression.
388
+
389
+ A suppression should be rare, documented with the proven invariant, and revisited when the Effect changes.
390
+
391
+ ---
392
+
393
+
394
+
395
+ ## useRef Without Initial Value in React 19
396
+
397
+ ### Problem
398
+ React 19 requires `useRef` to have an explicit initial value.
399
+
400
+ ### Code (symptom)
401
+ ```ts
402
+ const ref = useRef<HTMLDivElement>() // ❌ error in React 19
403
+ ```
404
+
405
+ ### Fix
406
+ ```ts
407
+ const ref = useRef<HTMLDivElement>(null) // ✅
408
+ ```
409
+
410
+ ### Variations
411
+ | Type | Fix |
412
+ |---|---|
413
+ | DOM Element | `useRef<HTMLDivElement>(null)` |
414
+ | Numeric value | `useRef<number>(0)` |
415
+ | String value | `useRef<string>('')` |
416
+
417
+ ---
418
+
419
+ ## State Mutation
420
+
421
+ ### Problem
422
+ Directly mutating objects or arrays in state instead of replacing them with new ones.
423
+
424
+ *Source: [react.dev — Updating Objects in State](https://react.dev/learn/updating-objects-in-state)*
425
+
426
+ ### Code (symptom)
427
+ ```tsx
428
+ const [user, setUser] = useState({ name: 'Alex', email: 'alex@example.com' })
429
+ user.name = 'Morgan' // ← direct mutation
430
+ setUser(user) // ← React may bail out (same reference)
431
+ ```
432
+
433
+ ### Fix
434
+ ```tsx
435
+ setUser({ ...user, name: 'Morgan' })
436
+ ```
437
+
438
+ ### Why it is unsafe
439
+ React may bail out when the reference is unchanged, but a later unrelated render can expose the mutated value. Never mutate a value held in React state directly; use a new object/array or a library such as Immer for complex nested updates.
440
+
441
+ ---
442
+
443
+ ## Choose an Async UI Strategy Before Adding an Effect
444
+
445
+ ### Decision
446
+ A request for loading UI does not automatically require `useEffect`, `Suspense`, or a new data-fetching library. First inspect the established project approach: framework data loading, query hooks, shared API client, and existing loading, error, empty, and success-state components.
447
+
448
+ ### Use the existing project pattern when
449
+ - The project already has query hooks, route loaders, or a shared async-state abstraction for the same boundary.
450
+ - Existing screens expose loading, error, empty, and retry behavior consistently.
451
+ - The pattern is compatible with the installed framework and is not the verified cause of the issue.
452
+
453
+ ### Choose deliberately
454
+ - Use a framework-integrated loading path when the active framework provides one for the route or server-rendered boundary.
455
+ - Use an existing client cache/query abstraction when caching, deduplication, invalidation, or shared server state is required.
456
+ - Use an Effect to synchronize a client component with an external system when no established data layer owns that work. Include cancellation or stale-response protection, loading, error, and cleanup behavior.
457
+ - Compute synchronous derived values during render rather than creating an async state machine for them.
458
+
459
+ Do not introduce a second fetching strategy beside an established one merely because it is also supported by React.
460
+
461
+ *Sources: [React — Fetching data with Effects](https://react.dev/reference/react/useEffect#fetching-data-with-effects) · [React — You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)*
462
+
463
+ ---
464
+
465
+ ## Suspense Boundaries Are Not Data Fetchers
466
+
467
+ ### Decision
468
+ `Suspense` renders a fallback while a descendant suspends. It does not initiate an API request, provide cache invalidation, or replace loading/error handling by itself.
469
+
470
+ ### Appropriate uses
471
+ - Code splitting with `lazy`.
472
+ - Framework or library data sources explicitly documented as Suspense-compatible.
473
+ - A deliberate loading boundary whose fallback and reveal behavior match the existing UI architecture.
474
+
475
+ ### Verify first
476
+ - Confirm the installed framework or data library documents the exact Suspense integration in use.
477
+ - Check whether route-level loading or the existing query layer already owns the loading state.
478
+ - Place boundaries around a meaningful visual unit; avoid a single fallback that blocks unrelated content unless that is the intended experience.
479
+ - Provide an error boundary or the project’s existing error treatment for failures. A Suspense fallback does not render request errors.
480
+
481
+ Do not replace a working project loading convention with Suspense only because React supports it.
482
+
483
+ *Sources: [React — Suspense](https://react.dev/reference/react/Suspense) · [React — lazy](https://react.dev/reference/react/lazy)*
484
+
485
+ ---
486
+
487
+ ## Extract a Custom Hook Only for a Reusable Stateful Contract
488
+
489
+ ### Decision
490
+ Extract a custom hook when multiple consumers need the same stateful behavior, lifecycle handling, or integration contract—not simply because a component contains several lines of code.
491
+
492
+ ### Verify first
493
+ - Identify at least one current or credible near-term consumer with the same inputs, outputs, and lifecycle.
494
+ - Keep UI markup, labels, and component-specific presentation in the component.
495
+ - Preserve the project’s naming, folder, testing, and return-shape conventions for hooks.
496
+ - Prefer a local helper when the logic has one consumer and extraction would obscure the flow.
497
+
498
+ A custom hook should expose a cohesive behavior contract, such as a subscription lifecycle or form interaction state. It must still follow the Rules of Hooks and keep reactive dependencies explicit.
499
+
500
+ *Sources: [React — Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) · [React — Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks)*
@@ -0,0 +1,146 @@
1
+ # 🎨 Tailwind CSS and shadcn/ui Patterns
2
+
3
+ > **Purpose:** Diagnose migration, scanning, token, theme, and component-installation problems.
4
+ > **Primary official sources:** [Tailwind v4 Upgrade Guide](https://tailwindcss.com/docs/upgrade-guide) · [Tailwind Detecting Classes](https://tailwindcss.com/docs/detecting-classes-in-source-files) · [Tailwind Theme Variables](https://tailwindcss.com/docs/theme) · [shadcn/ui Documentation](https://ui.shadcn.com/docs) · [Radix Primitives](https://www.radix-ui.com/primitives/docs/overview/introduction)
5
+
6
+ ## Project Convention Decision Rule
7
+
8
+ Before applying this guidance, verify the installed versions, local rules, design tokens, component abstractions, class conventions, configuration, and tests. Preserve a valid established project convention; do not replace it only because another documented approach is also valid. Use official sources to verify API behavior, compatibility, constraints, and migrations. Recommend a change only when the developer requests it or evidence shows the current approach is incompatible, unsafe, deprecated, broken, or responsible for a verified issue.
9
+
10
+ ---
11
+
12
+ ## Table of Contents
13
+
14
+ 1. [Tailwind v3 to v4 Is a Migration Boundary](#tailwind-v3-to-v4-is-a-migration-boundary)
15
+ 2. [Classes Are Missing from Generated CSS](#classes-are-missing-from-generated-css)
16
+ 3. [CSS Variables and Utility Tokens Drift](#css-variables-and-utility-tokens-drift)
17
+ 4. [shadcn/ui Component Dependencies Are Missing](#shadcnui-component-dependencies-are-missing)
18
+ 5. [Radix Unified and Individual Imports Are Mixed](#radix-unified-and-individual-imports-are-mixed)
19
+ 6. [Dark Mode or Theme Tokens Do Not Apply](#dark-mode-or-theme-tokens-do-not-apply)
20
+ 7. [Extend the Existing Design System Before Adding Local Styles](#extend-the-existing-design-system-before-adding-local-styles)
21
+
22
+ ---
23
+
24
+ ## Tailwind v3 to v4 Is a Migration Boundary
25
+
26
+ ### Symptom
27
+ A version upgrade leaves utilities ungenerated, configuration ignored, or build integration broken.
28
+
29
+ ### Verify first
30
+ Identify the installed major version and the active integration. Tailwind v3 uses a JavaScript configuration and `content` array; Tailwind v4 has a CSS-first configuration model and different installation/migration guidance. Do not apply v4 directives or package setup piecemeal to a v3 build.
31
+
32
+ ### Fix
33
+ Follow the official upgrade guide as one migration, including the appropriate PostCSS or Vite integration, import syntax, and source-detection approach. Preserve a working v3 setup until the v4 migration is complete and validated.
34
+
35
+ *Source: [Tailwind CSS v4.0 Upgrade Guide](https://tailwindcss.com/docs/upgrade-guide)*
36
+
37
+ ---
38
+
39
+ ## Classes Are Missing from Generated CSS
40
+
41
+ ### Symptom
42
+ A class appears in source but has no effect in the browser, especially in a component package, monorepo, or dynamically constructed class string.
43
+
44
+ ### Verify first
45
+ Confirm the exact class is present as a complete static string in a scanned source file. Tailwind scans source as plain text; it does not infer class names assembled at runtime.
46
+
47
+ ### Fix
48
+ For Tailwind v3, include every relevant source path in `content`. For Tailwind v4, use automatic detection where applicable and explicitly register ignored or external sources with `@source` when needed. Map runtime choices to complete class names rather than interpolating fragments.
49
+
50
+ ```tsx
51
+ const variants = {
52
+ success: 'bg-green-600 text-white',
53
+ danger: 'bg-red-600 text-white',
54
+ }
55
+
56
+ return <button className={variants[state]} />
57
+ ```
58
+
59
+ *Sources: [Tailwind — Content Configuration (v3)](https://v3.tailwindcss.com/docs/content-configuration), [Tailwind — Detecting Classes](https://tailwindcss.com/docs/detecting-classes-in-source-files)*
60
+
61
+ ---
62
+
63
+ ## CSS Variables and Utility Tokens Drift
64
+
65
+ ### Symptom
66
+ A semantic utility such as `bg-primary` uses an unexpected color, or a component looks correct in one theme and incorrect in another.
67
+
68
+ ### Verify first
69
+ Trace the value from the utility name to its CSS variable and then to the active theme selector. Confirm the variable format matches how the utility consumes it (for example, a raw color value versus channel values used with an alpha placeholder).
70
+
71
+ ### Fix
72
+ Define one semantic token contract and use it consistently across base and alternate themes. In Tailwind v4, expose theme values with `@theme` when a utility namespace is required; retain ordinary CSS custom properties for runtime theme switching where appropriate. Avoid duplicating literal colors in component classes when a semantic token exists.
73
+
74
+ *Sources: [Tailwind — Theme Variables](https://tailwindcss.com/docs/theme), [MDN — Using CSS custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascading_variables/Using_CSS_custom_properties)*
75
+
76
+ ---
77
+
78
+ ## shadcn/ui Component Dependencies Are Missing
79
+
80
+ ### Symptom
81
+ A copied or added component fails to compile because an import, utility, primitive package, or shared file is absent.
82
+
83
+ ### Verify first
84
+ Check the component documentation and generated source, then inspect `components.json` aliases and the installed package manifest. shadcn/ui components are code added to the application, not a single opaque runtime package; a component can rely on utilities, icons, Radix primitives, and other components.
85
+
86
+ ### Fix
87
+ Use the CLI command documented for the installed shadcn/ui version to add the component and its declared dependencies. If integrating manually, copy every documented dependency deliberately and preserve imports that match the project’s aliases. Run typecheck after installation; do not remove an apparently unused primitive without tracing transitive component use.
88
+
89
+ *Sources: [shadcn/ui — CLI](https://ui.shadcn.com/docs/cli), [shadcn/ui — components.json](https://ui.shadcn.com/docs/components-json)*
90
+
91
+ ---
92
+
93
+ ## Radix Unified and Individual Imports Are Mixed
94
+
95
+ ### Symptom
96
+ Primitive imports resolve inconsistently, dependency versions duplicate, or generated component code no longer matches installed packages.
97
+
98
+ ### Verify first
99
+ Inspect the actual import paths in component source and the package manifest. Radix offers documented package entry points; different installation styles can have different import paths and version constraints.
100
+
101
+ ### Fix
102
+ Choose the import style documented for the installed Radix packages and keep generated component imports consistent with it. Do not mechanically rewrite individual `@radix-ui/react-*` imports to a unified package, or the reverse, without verifying the installed API, exports, peer dependencies, and build output.
103
+
104
+ *Sources: [Radix Primitives — Introduction](https://www.radix-ui.com/primitives/docs/overview/introduction), [Radix Primitives — Releases](https://www.radix-ui.com/primitives/docs/overview/releases)*
105
+
106
+ ---
107
+
108
+ ## Dark Mode or Theme Tokens Do Not Apply
109
+
110
+ ### Symptom
111
+ The theme toggle changes state but colors do not change, or only part of the interface switches.
112
+
113
+ ### Verify first
114
+ In browser developer tools, verify: (1) the expected class or data attribute is applied to the intended root element, (2) the alternate-theme CSS variables match that selector, and (3) rendered elements use semantic token utilities rather than hard-coded colors.
115
+
116
+ ### Fix
117
+ Make the theme selector strategy match the Tailwind configuration or CSS variant strategy. Define the same semantic variable names in every theme, including foreground and border tokens, and ensure portal-rendered content inherits or is covered by the selector. Test the initial theme as well as toggling to detect a flash of incorrect theme.
118
+
119
+ *Sources: [Tailwind — Dark Mode](https://tailwindcss.com/docs/dark-mode), [Tailwind — Colors](https://tailwindcss.com/docs/colors)*
120
+
121
+ ---
122
+
123
+ ## Validation Checklist
124
+
125
+ 1. Confirm the Tailwind major version before changing configuration or directives.
126
+ 2. Build CSS and inspect whether a known expected utility is emitted.
127
+ 3. Verify token values and active theme selectors in browser developer tools.
128
+ 4. Typecheck after adding a shadcn/ui component to catch missing dependencies and alias failures.
129
+ 5. Test keyboard interaction and focus behavior for installed Radix-based components.
130
+
131
+ ---
132
+
133
+ ## Extend the Existing Design System Before Adding Local Styles
134
+
135
+ ### Decision
136
+ Before creating a one-off component, utility combination, color, radius, or spacing scale, inspect the design tokens, reusable primitives, variants, and nearby comparable UI. A working design system is a project convention, not an obstacle to bypass.
137
+
138
+ ### Verify first
139
+ - Reuse an existing component or variant when it serves the same semantic role.
140
+ - Prefer semantic token utilities over literal colors when the project defines them.
141
+ - Follow the project’s existing class-composition and variant conventions.
142
+ - Preserve accessibility behavior, focus handling, and responsive states supplied by an existing primitive.
143
+
144
+ Create a local style only when the existing system cannot express the requirement without distortion. If a new primitive or token is needed, introduce it deliberately at the same ownership layer as comparable project primitives rather than embedding a private design system in one feature.
145
+
146
+ *Sources: [Tailwind — Theme Variables](https://tailwindcss.com/docs/theme) · [shadcn/ui — Theming](https://ui.shadcn.com/docs/theming) · [Radix Primitives — Accessibility](https://www.radix-ui.com/primitives/docs/overview/accessibility)*