@dynokostya/just-works 1.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 (46) hide show
  1. package/.claude/agents/csharp-code-writer.md +32 -0
  2. package/.claude/agents/diagrammer.md +49 -0
  3. package/.claude/agents/frontend-code-writer.md +36 -0
  4. package/.claude/agents/prompt-writer.md +38 -0
  5. package/.claude/agents/python-code-writer.md +32 -0
  6. package/.claude/agents/swift-code-writer.md +32 -0
  7. package/.claude/agents/typescript-code-writer.md +32 -0
  8. package/.claude/commands/git-sync.md +96 -0
  9. package/.claude/commands/project-docs.md +287 -0
  10. package/.claude/settings.json +112 -0
  11. package/.claude/settings.json.default +15 -0
  12. package/.claude/skills/csharp-coding/SKILL.md +368 -0
  13. package/.claude/skills/ddd-architecture-python/SKILL.md +288 -0
  14. package/.claude/skills/feature-driven-architecture-python/SKILL.md +302 -0
  15. package/.claude/skills/gemini-3-prompting/SKILL.md +483 -0
  16. package/.claude/skills/gpt-5-2-prompting/SKILL.md +295 -0
  17. package/.claude/skills/opus-4-6-prompting/SKILL.md +315 -0
  18. package/.claude/skills/plantuml-diagramming/SKILL.md +758 -0
  19. package/.claude/skills/python-coding/SKILL.md +293 -0
  20. package/.claude/skills/react-coding/SKILL.md +264 -0
  21. package/.claude/skills/rest-api/SKILL.md +421 -0
  22. package/.claude/skills/shadcn-ui-coding/SKILL.md +454 -0
  23. package/.claude/skills/swift-coding/SKILL.md +401 -0
  24. package/.claude/skills/tailwind-css-coding/SKILL.md +268 -0
  25. package/.claude/skills/typescript-coding/SKILL.md +464 -0
  26. package/.claude/statusline-command.sh +34 -0
  27. package/.codex/prompts/plan-reviewer.md +162 -0
  28. package/.codex/prompts/project-docs.md +287 -0
  29. package/.codex/skills/ddd-architecture-python/SKILL.md +288 -0
  30. package/.codex/skills/feature-driven-architecture-python/SKILL.md +302 -0
  31. package/.codex/skills/gemini-3-prompting/SKILL.md +483 -0
  32. package/.codex/skills/gpt-5-2-prompting/SKILL.md +295 -0
  33. package/.codex/skills/opus-4-6-prompting/SKILL.md +315 -0
  34. package/.codex/skills/plantuml-diagramming/SKILL.md +758 -0
  35. package/.codex/skills/python-coding/SKILL.md +293 -0
  36. package/.codex/skills/react-coding/SKILL.md +264 -0
  37. package/.codex/skills/rest-api/SKILL.md +421 -0
  38. package/.codex/skills/shadcn-ui-coding/SKILL.md +454 -0
  39. package/.codex/skills/tailwind-css-coding/SKILL.md +268 -0
  40. package/.codex/skills/typescript-coding/SKILL.md +464 -0
  41. package/AGENTS.md +57 -0
  42. package/CLAUDE.md +98 -0
  43. package/LICENSE +201 -0
  44. package/README.md +114 -0
  45. package/bin/cli.mjs +291 -0
  46. package/package.json +39 -0
@@ -0,0 +1,293 @@
1
+ ---
2
+ name: python-coding
3
+ description: Apply when writing or editing Python (.py) files. Behavioral corrections for error handling, resource management, async patterns, data modeling, type safety, security defaults, and common antipatterns. Project conventions always override these defaults.
4
+ ---
5
+
6
+ # Python Coding
7
+
8
+ Match the project's existing conventions. When uncertain, read 2-3 existing modules to infer the local style. Check `pyproject.toml` for Python version target, linter config, and tooling. These defaults apply only when the project has no established convention.
9
+
10
+ ## Never rules
11
+
12
+ These are unconditional. They prevent bugs and vulnerabilities regardless of project style.
13
+
14
+ - **Never `except: pass`** or bare `except Exception` without re-raise. Catch specific exception types. Broad catches silently swallow bugs — a `KeyError` from a typo looks the same as a network failure, and you'll spend hours debugging something the traceback would have told you instantly.
15
+ - **Never `datetime.now()` or `datetime.utcnow()`** -- both produce naive datetimes that lose timezone info. Naive datetimes cause subtle bugs when code crosses timezone boundaries (servers, users, DST). Use `datetime.now(tz=timezone.utc)`. Use `zoneinfo.ZoneInfo` for other timezones, not `pytz`.
16
+ - **Never `random` for security** -- `random` uses a predictable PRNG; an attacker who observes a few outputs can predict future ones. Use `secrets.token_hex()`, `secrets.token_urlsafe()`, or `secrets.token_bytes()` for tokens, keys, session IDs.
17
+ - **Never `shell=True` in `subprocess`** -- shell interpretation enables command injection if any argument contains user input. Use argument lists: `subprocess.run(["cmd", arg1, arg2])`.
18
+ - **Never string formatting in SQL** -- SQL injection is one of the most exploited vulnerabilities. Use parameterized queries only. `f"SELECT * FROM users WHERE id = {uid}"` is always a defect.
19
+ - **Never `yaml.load()`** without `SafeLoader` -- use `yaml.safe_load()`. Unsafe YAML loading deserializes arbitrary Python objects, enabling remote code execution from a crafted YAML file.
20
+ - **Never `pickle.load()` on untrusted data** -- pickle executes arbitrary code during deserialization by design. Use JSON, MessagePack, or Protocol Buffers for data interchange.
21
+ - **Never `eval()` or `exec()` on external input** -- use `ast.literal_eval()` for safe evaluation of literals.
22
+ - **Never mutable default arguments** -- `def f(items=[])` shares one list across all calls. Appending in one call mutates the default for every subsequent call. Use `None` sentinel: `def f(items: list[str] | None = None)` then `items = items or []`.
23
+ - **Never shadow builtins** -- don't use `list`, `dict`, `id`, `type`, `input`, `hash`, `map`, `set`, `filter` as variable names. Shadowing causes confusing errors when you later need the builtin in the same scope.
24
+ - **Never blocking calls in async** -- no `time.sleep()`, bare `open()`, or `requests.get()` inside `async def`. These block the entire event loop, freezing all concurrent tasks. Use `asyncio.sleep()`, `aiofiles`, `httpx`.
25
+ - **Never `+=` string concatenation in loops** -- use `"".join(parts)`. Python strings are immutable, so each `+=` allocates a new string and copies all previous content — O(n²) at scale. At 10k iterations this turns milliseconds into seconds.
26
+
27
+ ## Error handling
28
+
29
+ Always use `raise ... from e` when re-raising at I/O boundaries. This preserves the original traceback -- essential for production debugging. Use `raise ... from None` only when the original exception is genuinely irrelevant to the caller.
30
+
31
+ ```python
32
+ async def get_user(user_id: int) -> User:
33
+ try:
34
+ result = await db.fetchrow("SELECT * FROM users WHERE id = $1", user_id)
35
+ except asyncpg.PostgresError as e:
36
+ raise DatabaseError(f"Failed to query user {user_id}") from e
37
+ if result is None:
38
+ raise UserNotFoundError(user_id)
39
+ return User(**result)
40
+ ```
41
+
42
+ Create custom exception types when callers need to distinguish failure modes:
43
+
44
+ ```python
45
+ class AppError(Exception):
46
+ """Base exception."""
47
+
48
+ class NotFoundError(AppError):
49
+ def __init__(self, resource: str, id: Any) -> None:
50
+ self.resource = resource
51
+ self.id = id
52
+ super().__init__(f"{resource} not found: {id}")
53
+ ```
54
+
55
+ When logging a caught exception, always preserve the traceback:
56
+
57
+ ```python
58
+ # Wrong: traceback lost
59
+ except httpx.HTTPStatusError as e:
60
+ logger.error(f"API call failed: {e}")
61
+ raise
62
+
63
+ # Correct: logger.exception() includes full traceback
64
+ except httpx.HTTPStatusError:
65
+ logger.exception("API call failed")
66
+ raise
67
+ ```
68
+
69
+ Use `exc_info=True` for non-error log levels: `logger.warning("retrying", exc_info=True)`.
70
+
71
+ ## Resource cleanup
72
+
73
+ Use context managers for anything that needs cleanup -- clients, connections, file handles. Never instantiate `httpx.AsyncClient()`, database pools, or similar without a context manager or explicit `finally` cleanup.
74
+
75
+ For managing multiple async resources, use `AsyncExitStack`:
76
+
77
+ ```python
78
+ from contextlib import AsyncExitStack
79
+
80
+ async def setup_resources() -> AsyncIterator[Resources]:
81
+ async with AsyncExitStack() as stack:
82
+ db = await stack.enter_async_context(create_pool(dsn))
83
+ cache = await stack.enter_async_context(create_redis(url))
84
+ yield Resources(db=db, cache=cache)
85
+ ```
86
+
87
+ For custom resource lifecycles where no built-in context manager exists:
88
+
89
+ ```python
90
+ from contextlib import asynccontextmanager
91
+
92
+ @asynccontextmanager
93
+ async def managed_session(config: Config) -> AsyncIterator[Session]:
94
+ session = await Session.connect(config)
95
+ try:
96
+ yield session
97
+ finally:
98
+ await session.disconnect()
99
+ ```
100
+
101
+ ## Async patterns
102
+
103
+ **Prefer `TaskGroup` (3.11+) over `gather` for most concurrent work.** TaskGroup enforces structured concurrency: if one task fails, siblings are cancelled and errors are raised as `ExceptionGroup`. `gather(return_exceptions=True)` silently mixes exceptions into results, which is error-prone. Use `gather` when you genuinely need partial results despite failures, or when targeting Python < 3.11.
104
+
105
+ ```python
106
+ async with asyncio.TaskGroup() as tg:
107
+ task1 = tg.create_task(fetch_users())
108
+ task2 = tg.create_task(fetch_orders())
109
+ # Both guaranteed complete here. If either failed, ExceptionGroup is raised.
110
+
111
+ # Handle multiple failure types with except*
112
+ try:
113
+ async with asyncio.TaskGroup() as tg:
114
+ tg.create_task(operation_a())
115
+ tg.create_task(operation_b())
116
+ except* ConnectionError as eg:
117
+ for exc in eg.exceptions:
118
+ logger.error(f"Connection failed: {exc}")
119
+ except* ValueError as eg:
120
+ for exc in eg.exceptions:
121
+ logger.error(f"Validation failed: {exc}")
122
+ ```
123
+
124
+ Share a single `AsyncClient` across concurrent requests -- don't create one per call. Configure timeouts explicitly:
125
+
126
+ ```python
127
+ async with httpx.AsyncClient(base_url="https://api.example.com", timeout=30.0) as client:
128
+ async with asyncio.TaskGroup() as tg:
129
+ task1 = tg.create_task(client.get("/users"))
130
+ task2 = tg.create_task(client.get("/orders"))
131
+ ```
132
+
133
+ ## Type hints
134
+
135
+ Use modern syntax: `list[str]`, `dict[str, Any]`, `X | None`. Use native type parameter syntax when the project targets 3.12+; fall back to `TypeVar` for older targets.
136
+
137
+ ```python
138
+ class Repository[T]:
139
+ def __init__(self, model_class: type[T]) -> None:
140
+ self._model_class = model_class
141
+ self._items: dict[int, T] = {}
142
+
143
+ def get(self, id: int) -> T | None:
144
+ return self._items.get(id)
145
+ ```
146
+
147
+ **Use `object` instead of `Any`** when you mean "accepts anything." `Any` silently disables type checking -- it's a hole in type safety. Reserve `Any` for when the type system genuinely cannot express something.
148
+
149
+ **Covariant inputs, concrete outputs.** Function parameters should accept abstract types (`Sequence`, `Mapping`, `Iterable`); return types should be concrete (`list`, `dict`):
150
+
151
+ ```python
152
+ from collections.abc import Sequence, Mapping
153
+
154
+ def process_items(items: Sequence[str]) -> list[str]: # accepts tuple, list, etc.
155
+ return [item.upper() for item in items]
156
+
157
+ def merge_configs(base: Mapping[str, Any], override: Mapping[str, Any]) -> dict[str, Any]:
158
+ return {**base, **override}
159
+ ```
160
+
161
+ **Use `TYPE_CHECKING` for import-only types.** When a type is only needed for annotations (not at runtime), import it under `TYPE_CHECKING` to avoid circular imports and reduce startup cost:
162
+
163
+ ```python
164
+ from __future__ import annotations
165
+ from typing import TYPE_CHECKING
166
+
167
+ if TYPE_CHECKING:
168
+ from myapp.services import PaymentService
169
+
170
+ class OrderProcessor:
171
+ def __init__(self, payments: PaymentService) -> None:
172
+ self._payments = payments
173
+ ```
174
+
175
+ ## Pattern matching
176
+
177
+ Use `match`/`case` (3.10+) when branching on structure — it's clearer than if/elif chains for destructuring dicts, tuples, or typed objects. Don't use it as a substitute for simple value comparisons where `if/elif` reads fine.
178
+
179
+ ```python
180
+ match event:
181
+ case {"type": "click", "target": target}:
182
+ handle_click(target)
183
+ case {"type": "scroll", "offset": int(offset)} if offset > 0:
184
+ handle_scroll(offset)
185
+ case _:
186
+ logger.warning("Unhandled event: %s", event.get("type"))
187
+ ```
188
+
189
+ ## Data modeling
190
+
191
+ | Use Case | Choice | Reason |
192
+ |----------|--------|--------|
193
+ | API request/response | Pydantic | Validation, serialization, OpenAPI |
194
+ | Config from env/files | Pydantic | Built-in settings management |
195
+ | Internal data transfer | dataclass | Lighter weight, no runtime validation |
196
+ | Simple value objects | dataclass | Minimal boilerplate |
197
+
198
+ Pydantic at system boundaries:
199
+
200
+ ```python
201
+ class UserCreate(BaseModel):
202
+ email: str = Field(..., min_length=5)
203
+ name: str = Field(..., min_length=1, max_length=100)
204
+
205
+ class UserResponse(BaseModel):
206
+ id: int
207
+ email: str
208
+ model_config = {"from_attributes": True}
209
+ ```
210
+
211
+ Dataclasses internally. Use `frozen=True` for value objects, `slots=True` when you have many instances:
212
+
213
+ ```python
214
+ @dataclass(frozen=True, slots=True)
215
+ class CacheKey:
216
+ namespace: str
217
+ id: str
218
+ ```
219
+
220
+ ## Dependency injection
221
+
222
+ Constructor injection. Dependencies are explicit, testable, and swappable.
223
+
224
+ ```python
225
+ class UserService:
226
+ def __init__(self, repository: UserRepository, email_client: EmailClient) -> None:
227
+ self._repository = repository
228
+ self._email_client = email_client
229
+ ```
230
+
231
+ ## Protocol
232
+
233
+ Use Protocol for structural interfaces -- duck typing with full type safety, no inheritance required.
234
+
235
+ ```python
236
+ from typing import Protocol, runtime_checkable
237
+
238
+ @runtime_checkable
239
+ class Serializable(Protocol):
240
+ def to_dict(self) -> dict[str, Any]: ...
241
+ ```
242
+
243
+ ## Enums
244
+
245
+ Use `StrEnum` instead of raw string literals for known value sets. Catches typos at type-check time. Use `IntEnum` only when interfacing with systems that require integer codes.
246
+
247
+ ```python
248
+ from enum import StrEnum, unique
249
+
250
+ @unique
251
+ class OrderStatus(StrEnum):
252
+ PENDING = "pending"
253
+ CONFIRMED = "confirmed"
254
+ SHIPPED = "shipped"
255
+ ```
256
+
257
+ ## Imports
258
+
259
+ Import order: standard library, third-party, local. Define `__all__` if the project convention uses it. Use `pathlib.Path` over `os.path`. No import-time side effects -- don't connect to databases, read files, or run computations at module level. Defer to first use.
260
+
261
+ ## Retry logic
262
+
263
+ Use tenacity for transient errors: network failures, rate limits, 5xx responses. Let 4xx errors propagate -- they indicate a request problem, not a transient failure.
264
+
265
+ ```python
266
+ @retry(
267
+ retry=retry_if_exception_type((httpx.RequestError, httpx.HTTPStatusError)),
268
+ stop=stop_after_attempt(3),
269
+ wait=wait_exponential(multiplier=1, min=2, max=10),
270
+ before_sleep=before_sleep_log(logger, logging.WARNING),
271
+ reraise=True,
272
+ )
273
+ async def fetch_with_retry(url: str) -> dict[str, Any]:
274
+ async with httpx.AsyncClient() as client:
275
+ response = await client.get(url, timeout=30.0)
276
+ response.raise_for_status()
277
+ return response.json()
278
+ ```
279
+
280
+ ## Logging
281
+
282
+ Match the project's logging library (stdlib `logging`, `structlog`, etc.). With stdlib, use `__name__` and pass structured data via `extra`:
283
+
284
+ ```python
285
+ logger = logging.getLogger(__name__)
286
+ logger.info("User created", extra={"user_id": user.id})
287
+ ```
288
+
289
+ ## Testing
290
+
291
+ Match the project's test runner and async plugin. Check for `pytest-asyncio` `mode = "auto"` (no manual `@pytest.mark.asyncio` needed) or `anyio`-based setup.
292
+
293
+ When to mock: external APIs with rate limits/costs, error paths, deterministic results. When to use real services: verifying actual integration behavior, sandbox/test modes available. Test behavior, not implementation -- test what a function returns, not how it does it internally.
@@ -0,0 +1,264 @@
1
+ ---
2
+ name: react-coding
3
+ description: Apply when writing or editing React (.tsx/.jsx) files. Behavioral corrections for hooks, state management, component structure, memoization, security, and common antipatterns. Project conventions always override these defaults.
4
+ ---
5
+
6
+ # React Coding
7
+
8
+ Match the project's existing conventions. When uncertain, read 2-3 existing components to infer the local style. Check `tsconfig.json` for strictness settings, `package.json` for React version, and framework config (Next.js, Remix, Vite) for routing and rendering conventions. These defaults apply only when the project has no established convention.
9
+
10
+ ## Never rules
11
+
12
+ These are unconditional. They prevent bugs, wasted renders, and vulnerabilities regardless of project style.
13
+
14
+ - **Never `useEffect` for derived state** -- if a value can be computed from props or state, compute it during render. `useEffect` + `setState` for derivable values causes an unnecessary extra render cycle. This is the most common React mistake.
15
+
16
+ ```tsx
17
+ // Wrong: extra render cycle for a value computable from props
18
+ const [fullName, setFullName] = useState("");
19
+ useEffect(() => {
20
+ setFullName(`${first} ${last}`);
21
+ }, [first, last]);
22
+
23
+ // Correct: compute during render
24
+ const fullName = `${first} ${last}`;
25
+ ```
26
+
27
+ - **Never `useEffect` for event-driven logic** -- user-triggered actions belong in event handlers, not effects keyed to state flags. Effects are for synchronizing with external systems.
28
+
29
+ ```tsx
30
+ // Wrong: effect triggered by state flag
31
+ const [submitted, setSubmitted] = useState(false);
32
+ useEffect(() => {
33
+ if (submitted) sendAnalytics();
34
+ }, [submitted]);
35
+
36
+ // Correct: logic in the event handler
37
+ function handleSubmit() {
38
+ sendAnalytics();
39
+ navigate("/done");
40
+ }
41
+ ```
42
+
43
+ - **Never fetch data in `useEffect` without cleanup** -- missing `AbortController` causes race conditions on rapid prop changes. Prefer TanStack Query or framework data fetching over raw `useEffect` fetching entirely.
44
+
45
+ ```tsx
46
+ // Wrong: no abort, race condition on rapid userId changes
47
+ useEffect(() => {
48
+ fetch(`/api/users/${userId}`).then(r => r.json()).then(setUser);
49
+ }, [userId]);
50
+
51
+ // Correct: AbortController for cleanup
52
+ useEffect(() => {
53
+ const controller = new AbortController();
54
+ fetch(`/api/users/${userId}`, { signal: controller.signal })
55
+ .then(r => r.json())
56
+ .then(setUser)
57
+ .catch(e => { if (e.name !== "AbortError") throw e; });
58
+ return () => controller.abort();
59
+ }, [userId]);
60
+
61
+ // Best: TanStack Query handles caching, deduplication, and cancellation
62
+ const { data: user } = useQuery({
63
+ queryKey: ["user", userId],
64
+ queryFn: () => fetchUser(userId),
65
+ });
66
+ ```
67
+
68
+ - **Never chain `useEffect` calls that sync state to other state** -- cascading effects create render waterfalls (render, effect, setState, render, effect, setState…). Each link in the chain adds a full render cycle, so three chained effects means four renders for one user action — visible jank and wasted CPU. Compute derived values inline or batch updates in event handlers.
69
+
70
+ - **Never mutate state directly** -- React detects changes by reference. `array.push()` and `obj.prop = x` won't trigger re-renders. Use spread, `structuredClone`, or non-mutating methods like `.toSorted()` (not `.sort()` which mutates).
71
+
72
+ ```tsx
73
+ // Wrong: mutates existing array, React sees same reference
74
+ items.push(newItem);
75
+ setItems(items);
76
+
77
+ // Correct: new array reference, non-mutating sort
78
+ setItems([...items, newItem].toSorted((a, b) => a.name.localeCompare(b.name)));
79
+ ```
80
+
81
+ - **Never use array index as `key` for dynamic lists** -- index keys cause React to reuse DOM nodes incorrectly when items are reordered, inserted, or removed, corrupting component state. Use stable unique IDs.
82
+
83
+ ```tsx
84
+ // Wrong: index key breaks on reorder/insert/delete
85
+ {items.map((item, i) => <ListItem key={i} item={item} />)}
86
+
87
+ // Correct: stable unique ID
88
+ {items.map(item => <ListItem key={item.id} item={item} />)}
89
+ ```
90
+
91
+ - **Never add `"use client"` by default** -- in Next.js App Router, components are Server Components by default. Adding `"use client"` unnecessarily pushes components and their entire subtree to the client bundle, losing server-side rendering, data fetching, and streaming benefits. Only add it when the component uses hooks (`useState`, `useEffect`), event handlers, or browser APIs. Place the boundary as low in the tree as possible.
92
+
93
+ - **Never use `forwardRef` in React 19+** -- `ref` is a regular prop in React 19+. `forwardRef` is deprecated. In React 18, `forwardRef` is still required.
94
+
95
+ ```tsx
96
+ // Wrong (React 19): forwardRef is deprecated
97
+ const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
98
+ <input ref={ref} {...props} />
99
+ ));
100
+
101
+ // Correct (React 19): ref is a regular prop
102
+ function Input({ ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
103
+ return <input ref={ref} {...props} />;
104
+ }
105
+
106
+ // React 18: forwardRef is still required
107
+ const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
108
+ <input ref={ref} {...props} />
109
+ ));
110
+ ```
111
+
112
+ - **Never define components inside other components** -- the inner component is recreated every render, destroying all state and DOM on each cycle.
113
+
114
+ ```tsx
115
+ // Wrong: Child is a new component identity every render
116
+ function Parent() {
117
+ function Child() { return <div>child</div>; }
118
+ return <Child />;
119
+ }
120
+
121
+ // Correct: Child defined outside
122
+ function Child() { return <div>child</div>; }
123
+ function Parent() { return <Child />; }
124
+ ```
125
+
126
+ - **Never suppress `react-hooks/exhaustive-deps`** -- `eslint-disable` for this rule masks stale closures, where an effect captures an old value of a prop or state and silently operates on outdated data. The resulting bugs are intermittent and hard to trace because the component appears to work until a specific re-render order exposes the stale value. Fix the code: extract functions, use updater functions for state, or move objects inside the effect.
127
+
128
+ - **Never mirror props in state** -- `useState(prop)` captures the initial value only. Subsequent prop changes are silently ignored. Use the prop directly, or name it `initialX` to signal intent.
129
+
130
+ ```tsx
131
+ // Wrong: color prop changes are silently ignored after mount
132
+ function Badge({ color }: { color: string }) {
133
+ const [badgeColor, setBadgeColor] = useState(color);
134
+ return <span style={{ color: badgeColor }}>badge</span>;
135
+ }
136
+
137
+ // Correct: use the prop directly
138
+ function Badge({ color }: { color: string }) {
139
+ return <span style={{ color }}>badge</span>;
140
+ }
141
+ ```
142
+
143
+ - **Never use `useEffect` to reset state on prop change** -- use the `key` prop to unmount/remount the component, which resets all state automatically.
144
+
145
+ ```tsx
146
+ // Wrong: effect to reset state when userId changes
147
+ function Profile({ userId }: { userId: string }) {
148
+ const [comment, setComment] = useState("");
149
+ useEffect(() => { setComment(""); }, [userId]);
150
+ return <textarea value={comment} onChange={e => setComment(e.target.value)} />;
151
+ }
152
+
153
+ // Correct: key forces remount, all state resets
154
+ <Profile key={userId} userId={userId} />
155
+ ```
156
+
157
+ - **Never generate class components** -- all modern React features (Server Components, Suspense, hooks, React Compiler) require function components. The only exception: error boundaries (which still require class components or `react-error-boundary`).
158
+
159
+ - **Never use `dangerouslySetInnerHTML` without sanitization** -- renders raw HTML, enabling XSS attacks. Always sanitize with DOMPurify or similar. Prefer React's built-in escaping over raw HTML injection entirely.
160
+
161
+ ```tsx
162
+ // Wrong: unsanitized HTML injection
163
+ <div dangerouslySetInnerHTML={{ __html: userContent }} />
164
+
165
+ // Correct: sanitize first
166
+ import DOMPurify from "dompurify";
167
+ <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} />
168
+ ```
169
+
170
+ - **Never use `React.lazy` without a `<Suspense>` boundary** -- lazy components suspend during loading. Without `<Suspense>`, the thrown promise is unhandled and crashes the app. Wrap with `<Suspense>` and an error boundary.
171
+
172
+ ```tsx
173
+ // Wrong: no Suspense boundary, crashes on load
174
+ const Dashboard = lazy(() => import("./Dashboard"));
175
+ function App() { return <Dashboard />; }
176
+
177
+ // Correct: Suspense with fallback and error boundary
178
+ const Dashboard = lazy(() => import("./Dashboard"));
179
+ function App() {
180
+ return (
181
+ <ErrorBoundary fallback={<p>Something went wrong.</p>}>
182
+ <Suspense fallback={<p>Loading...</p>}>
183
+ <Dashboard />
184
+ </Suspense>
185
+ </ErrorBoundary>
186
+ );
187
+ }
188
+ ```
189
+
190
+ ## Memoization
191
+
192
+ **With React Compiler (v19.x + compiler enabled):** The compiler auto-memoizes components, hooks, and JSX elements. Remove manual `useMemo`, `useCallback`, and `React.memo` -- they add noise and the compiler may deopt on components where it cannot preserve manual memoization. Let the compiler handle it.
193
+
194
+ **Without React Compiler:** Memoize at measured bottlenecks only. Don't wrap everything in `useMemo`/`useCallback`/`React.memo` preemptively. When you do memoize, avoid creating new object/array literals in JSX props to memoized children -- `style={{ color: "red" }}` creates a new reference every render, defeating `React.memo`. Hoist static objects outside the component.
195
+
196
+ ```tsx
197
+ // Wrong: new style object every render defeats React.memo on Child
198
+ function Parent() {
199
+ return <MemoizedChild style={{ color: "red" }} />;
200
+ }
201
+
202
+ // Correct: hoist static objects outside the component
203
+ const childStyle = { color: "red" } as const;
204
+ function Parent() {
205
+ return <MemoizedChild style={childStyle} />;
206
+ }
207
+ ```
208
+
209
+ ## Hooks
210
+
211
+ Rules of hooks -- no exceptions:
212
+ - Call hooks at the top level of function components and custom hooks only.
213
+ - Never call hooks inside conditions, loops, nested functions, `try`/`catch`, or after early returns.
214
+ - The `use()` API (React 19) is the exception -- it can be called conditionally.
215
+
216
+ **`useEffect` decision tree -- ask before writing any effect:**
217
+
218
+ 1. Can the value be computed from existing props/state? Compute during render.
219
+ 2. Is this responding to a user event? Put it in the event handler.
220
+ 3. Do you need to reset state when a prop changes? Use `key` on the component.
221
+ 4. Do you need to sync with an external system (DOM, network, third-party widget)? This is a valid `useEffect`. Add cleanup.
222
+ 5. Do you need to fetch data? Prefer framework data fetching or TanStack Query. If raw `useEffect`, always use `AbortController`.
223
+
224
+ ## Component typing
225
+
226
+ **Prefer typed function declarations over `React.FC`.** `React.FC` is no longer broken (implicit `children` was removed in React 18 types), but plain function declarations are clearer, support generics naturally, and are the community standard.
227
+
228
+ ```tsx
229
+ interface UserCardProps {
230
+ user: User;
231
+ onSelect: (id: string) => void;
232
+ variant?: "compact" | "full";
233
+ }
234
+
235
+ function UserCard({ user, onSelect, variant = "full" }: UserCardProps) {
236
+ return ( /* JSX */ );
237
+ }
238
+ ```
239
+
240
+ Generic components:
241
+
242
+ ```tsx
243
+ function List<T>({ items, renderItem, keyExtractor }: {
244
+ items: T[];
245
+ renderItem: (item: T) => React.ReactNode;
246
+ keyExtractor: (item: T) => string;
247
+ }) {
248
+ return (
249
+ <ul>
250
+ {items.map(item => (
251
+ <li key={keyExtractor(item)}>{renderItem(item)}</li>
252
+ ))}
253
+ </ul>
254
+ );
255
+ }
256
+ ```
257
+
258
+ Use discriminated unions for impossible states:
259
+
260
+ ```tsx
261
+ type ButtonProps =
262
+ | { variant: "link"; href: string; onClick?: never }
263
+ | { variant: "button"; onClick: () => void; href?: never };
264
+ ```