Skip to content

Commit 335060b

Browse files
committed
chore: add claude code configuration
Shared plugin settings and path-scoped convention rules for JavaScript, TypeScript, React, and React testing. Per-user settings.local.json stays untracked via .gitignore.
1 parent 6066c93 commit 335060b

6 files changed

Lines changed: 358 additions & 0 deletions

File tree

.claude/rules/javascript.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
paths:
3+
- "**/*.js"
4+
- "**/*.jsx"
5+
- "**/*.mjs"
6+
- "**/*.cjs"
7+
- "**/*.ts"
8+
- "**/*.tsx"
9+
- "**/*.mts"
10+
- "**/*.cts"
11+
---
12+
13+
# JavaScript Conventions
14+
15+
Clarity is the highest virtue — if control flow needs a comment, rewrite it. Prefer boring patterns over clever
16+
tricks. If existing code contradicts a rule below, follow the code and flag the divergence. For TypeScript files these
17+
rules are the base layer; [typescript.md](typescript.md) stacks on top.
18+
19+
## Declarations & Naming
20+
21+
- `const` by default, `let` only when reassigned, never `var`. One declaration per line; `const` group before `let`.
22+
`const` prevents reassignment, not mutation.
23+
- camelCase for variables/functions, PascalCase for classes, `#private` class fields (not `_` convention),
24+
`is`/`has`/`can`/`should` prefixes for booleans, kebab-case file names.
25+
- SCREAMING_SNAKE_CASE only for true compile-time constants — a variable holding a computed value is camelCase.
26+
- Descriptive names; short names (`i`, `x`) only in tiny scopes. Accepted abbreviations: `url`, `id`, `err`, `ctx`,
27+
`req`, `res` — avoid others. No redundant context (`car.make`, not `car.carMake`). Same word for the same concept
28+
across the codebase.
29+
30+
## Equality & Safety
31+
32+
- Always `===`/`!==`; the only valid `==` is `value == null` (catches both `null` and `undefined`).
33+
- `??` over `||` for defaults — `||` swallows `0`, `""`, `false`.
34+
- `?.` sparingly: data you expect to exist should throw, not silently yield `undefined`.
35+
- Falsy values: `false`, `0`, `-0`, `0n`, `""`, `null`, `undefined`, `NaN` — everything else is truthy, including
36+
`[]`, `{}`, `"0"`.
37+
- Ternaries: one-liner or one-branch-per-line only; anything else becomes `if`/`else` or early return. Nested
38+
ternaries are banned — no exceptions.
39+
40+
## Syntax
41+
42+
- Template literals only when interpolating; plain quotes otherwise.
43+
- Spread for copies (`{ ...obj }`, `[...arr]`), never `Object.assign`. Shorthand properties, grouped at the top of
44+
the literal. Computed keys `{ [key]: value }`. Logical assignment: `opts.timeout ??= 5000`.
45+
46+
## Functions
47+
48+
- Arrows for callbacks/anonymous functions; function declarations when hoisting or `this` binding is needed.
49+
Parentheses around single arrow params. Never arrows as object methods or on prototypes (lexical `this`).
50+
- Destructured options object for 3+ parameters. Default parameters over `||`/manual checks. Rest params over
51+
`arguments`.
52+
- Early return, guard clauses first, happy path flat. One function, one job — a name containing "and" means split.
53+
Keep under ~30 lines; extract helpers.
54+
- Prefer pure functions; isolate side effects (DOM, network, logging) — don't hide them inside data transformations.
55+
- Closures retain references, not copies — beware large objects captured unintentionally.
56+
57+
## Async
58+
59+
- `async`/`await` over `.then()` chains. Always `await` promises — a floating promise is a silent failure.
60+
Fire-and-forget must attach `.catch(reportError)`.
61+
- `return await` only inside `try` where you catch the error; otherwise return the promise directly.
62+
- Parallel independent work: `Promise.all`; all results regardless of failure: `Promise.allSettled`; timeout:
63+
`Promise.race`; fallback: `Promise.any`. No sequential awaits in loops — `Promise.all(items.map(...))`, or a
64+
concurrency limiter (`p-map`) for large arrays.
65+
- Throw `Error` objects, never strings. Custom error classes (extend `Error`, set `name`, add context) when callers
66+
must distinguish.
67+
- Never swallow errors — every `catch` handles, rethrows, or reports; `console.log(err)` is not handling. Let errors
68+
propagate to a top-level handler; don't wrap every `await` in `try`/`catch`.
69+
- `new Promise()` only to wrap callback APIs. `AbortController`/`{ signal }` for cancellation. `for await...of` for
70+
async iterables.
71+
72+
## Modules
73+
74+
- ES modules only; CommonJS is legacy. Named exports over default (exceptions: framework-required conventions).
75+
Never export mutable `let` — export accessor functions.
76+
- Imports at top, grouped with blank lines: built-in (`node:fs`), external, internal. Merge imports from the same
77+
module. Namespace import (`import * as x`) at 5+ items, named below that.
78+
- Include file extensions in relative import paths (`"./user.js"`); no directory imports resolving to `index.js`.
79+
- No barrel files in subdirectories — acceptable only as a package entry point enforced via `package.json` `exports`.
80+
No wildcard re-exports. No circular dependencies — extract a third module or inject.
81+
- Dynamic `import()` for code splitting: routes, large conditional deps, feature flags. One concern per module.
82+
Side-effect imports are rare and documented.
83+
84+
## Objects, Arrays, Classes
85+
86+
- Literals (`{}`, `[]`), never constructors. Method shorthand. Dot notation for static keys, brackets for dynamic.
87+
`Object.hasOwn(obj, key)` over `hasOwnProperty`.
88+
- Functional methods (`map`/`filter`/`find`/`some`/`every`/`flatMap`/`reduce`) for transforms — always return in the
89+
callback; `for...of` for side-effect loops; never `for...in` on arrays.
90+
- Don't mutate inputs — return new objects/arrays: add `[...arr, item]`, remove `arr.filter`, update `arr.map`.
91+
Return objects (not arrays) for multiple values. `Array.from(arrayLike)`; `Array.from(iterable, mapFn)` over
92+
`[...iterable].map()`.
93+
- `Map` when keys aren't strings or are user-provided (prototype pollution); `Set` for dedup; generators for lazy
94+
sequences. Never extend built-in prototypes.
95+
- ES `class` only; `#private` fields; composition over inheritance (`extends` only for true is-a); no empty
96+
constructors; `static` for state-free operations. Don't force classes — one method with state is a closure, one
97+
method without is a function.
98+
99+
## Docs & JSDoc
100+
101+
- Doc comments (`/** */`) are API documentation — the "no comments" default does not apply. Every exported function,
102+
class, and module-level constant gets one; `@param`/`@returns`/`@throws` for non-trivial signatures. Behavior and
103+
intent, not implementation. Update the doc in the same edit that changes behavior.
104+
- Pure-JS (non-TS) code: type via JSDoc with `// @ts-check`; annotate exported boundaries, `@typedef` for shared
105+
shapes, skip the obvious.

.claude/rules/react-testing.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
paths:
3+
- "**/*.test.jsx"
4+
- "**/*.test.tsx"
5+
- "**/*.spec.jsx"
6+
- "**/*.spec.tsx"
7+
---
8+
9+
# React Testing (Testing Library)
10+
11+
Tests resemble how users interact with the app: query by what users see (roles, labels, text), never by
12+
implementation details (class names, internals, test IDs).
13+
14+
## Setup & Interactions
15+
16+
- Always query via `screen` — never destructure from `render()`. `userEvent.setup()` before rendering.
17+
- `userEvent` over `fireEvent` — it simulates real behavior (focus, blur, full key event sequences).
18+
- Don't call `cleanup` manually — it's automatic. Don't wrap in `act``render()` and interactions already do; an
19+
`act` warning means a real bug (state update after the test finished), fix the cause.
20+
21+
## Queries
22+
23+
- Priority: `getByRole` > `getByLabelText` > `getByText` > `getByTestId` (last resort). If you can't query by role,
24+
the element is probably inaccessible to screen readers — fix the markup, not the test.
25+
- Variants: `getBy` for elements that must be present, `queryBy` only for asserting absence, `findBy` for async
26+
appearance.
27+
- Don't add `role` attributes to native elements (`<button>` already has `role="button"`). Inputs get `type` and a
28+
`<label>` — that's what makes them queryable by role.
29+
30+
## Async
31+
32+
- Prefer `findBy` over `waitFor` + `getBy`.
33+
- `waitFor`: one assertion per callback (faster failure diagnosis); no side effects inside (the callback retries);
34+
never an empty callback.
35+
36+
## Assertions
37+
38+
- Use `@testing-library/jest-dom` matchers: `toBeInTheDocument()`, `toBeVisible()`, `toBeDisabled()`,
39+
`toHaveTextContent()`, `toHaveAttribute()`, `toHaveValue()` — not raw attribute/DOM poking.
40+
- Forms with `useFormStatus`: the component under test must render inside a `<form>` with an `action`.

.claude/rules/react.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
paths:
3+
- "**/*.jsx"
4+
- "**/*.tsx"
5+
---
6+
7+
# React Conventions
8+
9+
Components are pure functions. State is minimal. Effects are escape hatches — before reaching for `useEffect`, verify
10+
you actually need it. Rules target React 19. Testing conventions in [react-testing.md](react-testing.md) (loads for
11+
`*.test.tsx`/`*.spec.tsx`).
12+
13+
## Component Design
14+
15+
- Break UI into a hierarchy where each component does one thing; build static (props-only) first; then find minimal
16+
state — if it's passed from a parent, never changes, or can be computed, it is not state; put state in the closest
17+
common parent of everything that renders from it; children update it through callbacks.
18+
- Purity: same props + state = same JSX. Never mutate props, state, or anything declared before the render. Local
19+
mutation within a render is fine; event handlers don't need to be pure.
20+
- One component per file; co-located small helpers acceptable until reused. Function declarations for components.
21+
Never `React.FC` — implicit `children`, breaks generics; use `function Button(props: ButtonProps)`.
22+
- Every component with props gets a dedicated named type (`ButtonProps`) — never inline prop types in the signature.
23+
Type events explicitly when needed (`React.ChangeEvent<HTMLInputElement>`).
24+
25+
## Component Body Organization
26+
27+
Logic in the body, declarative JSX in the return.
28+
29+
- Group all event handlers in a single `handle` object (`handle.submit()`, `onChange={handle.inputChange}`). Never
30+
inline handler logic in JSX.
31+
- Compute derived JSX and element arrays in the body; `.map()` inside the return statement is discouraged —
32+
build the array first, reference it in JSX.
33+
- Inline conditionals (`{isVisible && <X />}`) only when simple; multi-branch or complex conditions are computed in
34+
the body.
35+
36+
## JSX
37+
38+
- Self-closing tags without children; boolean attributes bare (`disabled`, not `disabled={true}`); fragments over
39+
wrapper divs.
40+
- `count && <List />` renders `0` — use `count > 0 &&` or a ternary.
41+
- `key` on every list item: stable unique IDs, never array index when items can reorder. `key` also resets component
42+
state when the entity changes (`<Profile key={userId} />`) — cleaner than an Effect.
43+
- Spread props sparingly — explicit props over `{...props}`. No side effects during render.
44+
45+
## Composition
46+
47+
- Props down, events up. Composition over configuration — pass JSX as `children`/render props instead of stacking
48+
boolean flags. A stateful wrapper's unchanged `children` skip re-rendering.
49+
- Compound components (shared context between sub-components) for tabs, menus, accordions. Controlled when a parent
50+
coordinates siblings; uncontrolled for isolated UI.
51+
- Refs: `ref` is a regular prop — never `forwardRef` (deprecated). Ref callbacks may return a cleanup; use block
52+
bodies, not implicit returns.
53+
54+
## Hooks
55+
56+
- Top level only — never in conditions, loops, or nested functions. Exception: `use()` may be called conditionally —
57+
prefer `use(Ctx)` over `useContext(Ctx)`.
58+
- Exhaustive deps always — the linter is right; if a dependency causes unwanted re-runs, restructure, don't suppress.
59+
- One Effect per concern; separate Effects for separate external systems.
60+
- Custom hooks share stateful logic, not state — each call is independent. `use` prefix only for functions that call
61+
hooks. Name by use case (`useOnlineStatus`), not lifecycle (`useMount`). Don't wrap a lone `useState` in a hook.
62+
Return: single value directly; value+setter as `[value, setValue] as const`; several related values as an object.
63+
- External stores: `useSyncExternalStore` over manual Effect + state.
64+
65+
## Effects Are Escape Hatches
66+
67+
Effects synchronize with external systems only — browser events, WebSockets, third-party widgets, non-React DOM,
68+
prop/state-dependent fetching (with cleanup).
69+
70+
You do NOT need an Effect to: transform data for rendering (compute in render), handle user events (event handler),
71+
reset or adjust state on prop change (`key` or compute), notify the parent (call the callback in the handler), share
72+
logic between handlers (extract a function), or chain state updates (compute everything in one handler).
73+
74+
Every subscribing Effect returns a cleanup. Fetching in an Effect uses an `ignore` flag against races — but prefer
75+
react-query (already used in this repo's frontends) over raw fetch Effects.
76+
77+
## State Management
78+
79+
- Placement chain: local `useState` → lift to common parent → composition (restructure to pass `children`) → Context
80+
→ external library. Context is not the first fix for prop drilling.
81+
- Server cache (API data) is not UI state: react-query/SWR — never hand-rolled `useState` + `useEffect` caching. UI
82+
state (modal open, form input, selected tab) stays in `useState`/`useReducer`/Context.
83+
- `useState`: don't store what you can compute; colocate; updater function when next depends on previous
84+
(`setCount(c => c + 1)`); lazy init for expensive values (`useState(() => build())`).
85+
- `useReducer` when many handlers touch the same state or transitions are non-trivial. Reducers are pure; actions
86+
describe what happened (`'added_task'`, not `'set_tasks'`), one action per user interaction; `default` case throws;
87+
`as const` on action types.
88+
- Context: consumption always wrapped in a custom hook that throws outside the provider. Keep providers close to
89+
usage; split logically (data vs dispatch contexts so dispatch-only consumers don't re-render). `<Context value>`
90+
directly, not `<Context.Provider value>`.
91+
- Actions: `useActionState` for form submissions/mutations (pending, errors, queuing built in); return
92+
error states instead of throwing; `useOptimistic` for instant feedback; `useFormStatus` only from a component
93+
rendered inside the `<form>`.
94+
95+
## Performance
96+
97+
- Decision tree: no perceptible lag → don't optimize. Slow render → profile and fix the computation. Unnecessary
98+
re-renders → restructure first (push state down, lift content up). Only then memoize. Fix the slow render before
99+
the re-render.
100+
- `memo` when a component re-renders often with identical props and that's expensive; `useMemo` for genuinely
101+
expensive computation or reference stability into memoized children; `useCallback` for callbacks into memoized
102+
children, custom hook returns, and Effect deps. If a project enables React Compiler, stop memoizing manually but
103+
leave existing memoization in place.
104+
- Import from concrete files, not barrels. `lazy(() => import('./Chart'))` + `<Suspense>` for heavy components.
105+
Independent fetches through `Promise.all`, never sequential awaits.

.claude/rules/typescript.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
paths:
3+
- "**/*.ts"
4+
- "**/*.tsx"
5+
- "**/*.mts"
6+
- "**/*.cts"
7+
---
8+
9+
# TypeScript Conventions
10+
11+
Extends [javascript.md](javascript.md). Types encode intent — let the compiler prove the rest. If you need `as` or
12+
`any`, the types are wrong; fix the types, don't fight the checker.
13+
14+
## Type Safety
15+
16+
- `strict: true` always; also `noUncheckedIndexedAccess` and `noImplicitOverride`.
17+
- `unknown` over `any`: external values (API responses, `JSON.parse`, user input) and pass-through parameters are
18+
`unknown`, narrowed with guards. `any` only for incremental JS migration or test mocks that deliberately bypass
19+
checking — with a comment saying why.
20+
- No non-null assertions (`!`) without a comment explaining why the value cannot be null — prefer narrowing.
21+
- No `as` on object literals — annotate (`: Foo`); assertions hide missing/extra property errors. `as` is justified
22+
only when you genuinely know more than the compiler (DOM APIs returning wide types, trusted external data). Always
23+
`as` syntax, never angle brackets. Double assertion goes through `unknown`, never `any`.
24+
- Never `@ts-ignore` or `@ts-nocheck`. `@ts-expect-error` in tests only, with a comment.
25+
- `{}` means "any non-nullish value" — almost never what you want. Opaque value: `unknown`; dict-like:
26+
`Record<string, unknown>`; any non-primitive: `object`.
27+
28+
## Annotations & Shapes
29+
30+
- Omit trivially inferred types (`const x = 5`). Annotate exported function signatures (params and return) and
31+
complex return types where inference goes opaque or wide. Annotate at declaration so errors surface where the bug
32+
is, not at distant call sites.
33+
- `import type` / `export type` for type-only imports and re-exports (`verbatimModuleSyntax`).
34+
- `interface` for object shapes; `type` for everything else (unions, intersections, tuples, functions,
35+
mapped/conditional). Stay consistent within a project.
36+
- No empty interfaces, no `namespace`, no wrapper types (`String`, `Number`). Data shapes are interfaces, not
37+
classes.
38+
- Nullability: optional `?` over `| undefined`; keep `| null` at the use site (`getUser(): User | null`), not baked
39+
into type aliases. `!= null` checks both null and undefined.
40+
41+
## Generics
42+
43+
- Constrain with `extends`, and keep constraints tight. `T` is fine for one parameter; `TKey`/`TValue`/`TItem` for
44+
several. Every type parameter must appear in the signature — no unused ones, no return-type-only generics (they
45+
can't be inferred).
46+
- Let inference work: don't pass type arguments the compiler can infer. `NoInfer<T>` when a parameter should be
47+
constrained by others rather than drive inference. Defaults: `interface Container<T, U = T[]>`.
48+
- Built-in utility types over hand-rolled equivalents: `Partial`, `Pick`, `Omit`, `Record`, `Exclude`, `Extract`,
49+
`ReturnType`, `Parameters`, `Awaited`. An explicit interface when the type is a distinct domain concept.
50+
- Complexity budget: unions/interfaces always; utility types for well-known transformations; conditional/mapped/
51+
template-literal types for library code only; recursive `infer` chains last resort. If you can't explain a type in
52+
one sentence, split or simplify it.
53+
54+
## Narrowing
55+
56+
- Discriminated unions (`kind`/`type` literal field) for variants. Exhaustive switch with
57+
`default: { const _exhaustive: never = value; ... }` to catch new variants at compile time.
58+
- Type predicates (`pet is Fish`) for reusable guards; assertion functions (`asserts value is Error`) for boundary
59+
validation. `typeof`/`instanceof`/`in` narrow natively.
60+
- Pitfalls: `typeof null === "object"` — check null first; truthiness narrowing fails on `""`, `0`, `NaN`, `false`
61+
explicit null checks when those are valid values.
62+
63+
## Enums
64+
65+
- Union of string literals over enum (`type Status = "active" | "inactive"`). If an enum is warranted (runtime
66+
object, iteration, namespace for constants), use a string enum; never numeric with implicit values, never mixed
67+
members, never coerced to boolean (`level !== Level.NONE`, not `!!level`).
68+
69+
## Functions & Classes
70+
71+
- Union types or optional parameters over overloads; when overloads are unavoidable, specific signatures before
72+
general.
73+
- Callbacks: `void` return when the result is ignored; no optional parameters in callback types — callers may ignore
74+
extra args.
75+
- Classes: omit `public` (default); `readonly` on never-reassigned properties; constructor parameter properties
76+
(`constructor(private readonly db: Database)`); initialize fields at declaration; `override` keyword on overrides.
77+
- Branded types for nominal safety where structural typing is too permissive (domain IDs, validated strings):
78+
`type UserId = string & { readonly __brand: unique symbol }` — keep the mechanism consistent project-wide.
79+
- Array syntax: `T[]` for simple element types, `Array<T>` for complex ones (`Array<string | number>`).
80+
- Doc comments describe behavior and semantics, not types already visible in the signature.
81+
82+
## tsconfig
83+
84+
- `module: "NodeNext"` when transpiling with tsc; `module: "preserve"` with bundlers (Vite, esbuild);
85+
`verbatimModuleSyntax: true` in both. Target `ESNext`; `lib` includes `dom` for browser projects.
86+
- Keep configs minimal: `extends` for sharing; separate `tsconfig.build.json` excluding tests/scripts.

0 commit comments

Comments
 (0)