Senior frontend developer standards. React + TypeScript specialist. Code should communicate intent, scale gracefully, and be a joy to maintain.
- Types are truth — never add
?./||/??on non-optional types. UseensurePresent()to fail fast. typeoverinterface— interface only for class contracts- No
asassertions — design APIs with precise types and discriminated unions - Derive types from const arrays — single source of truth
- Pattern matching over control flow — Record lookups,
match(), config arrays. Never switch/case. - Pure functions for data transformation — no side effects in mappers
attempt()over try-catch — only for user-facing errors, executing alternative logic, or fallback values. Let errors bubble up naturally.- Object parameters for functions with >1 arg (use
{FunctionName}Inputtype) - Readable code over comments — self-documenting names, extract complex logic into functions
- DRY — extract repeated values into constants
- Feature-based file organization — never generic
components/folders - Design system adherence — always use theme tokens, never hardcode hex values
typefor all object type definitions,interfaceonly for class contracts- No type assertions (
as) — use type narrowing, guards, predicate functions, discriminated unions - Derive union types from const arrays:
const items = ['a', 'b'] as const; type Item = (typeof items)[number] - Trust types — no
?.or?? ''on guaranteed-present values ensurePresent()for required runtime values- No phantom API props — never cast API types. Create derived values in mappers instead.
core.tsfor runtime values + derived types;types.tsfor pure type definitions (zero runtime code)
- React Compiler handles memoization — never use
useMemooruseCallback - Component autonomy — domain components access state directly via hooks, not props. Only pass props to truly reusable/generic components.
- Colocate state with consumers — each component owns its data dependencies
- One component per file (small helpers < 10 lines and inline styled components are exceptions)
- MatchQuery for loading/error/success — never manual
if (isLoading)checks - Pattern matching over switch/case in JSX — use
<Match>component, Record lookups, config arrays
Never use switch/case or nested ternaries:
- Simple value mapping →
Record<Key, Value> - Functional branching →
match(value, { ... }) - JSX conditional rendering →
<Match value={status} loading={() => ...} /> - Discriminated unions →
matchDiscriminatedUnion(action, 'kind', 'payload', { ... }) - Record unions →
matchRecordUnion(shape, { ... }) - Repetitive conditionals → config arrays with
.every()/.filter()
- Object parameters for >1 arg with
{FunctionName}Inputtype - Never try-catch — use
attempt()only for: showing errors to users, executing alternative logic, or providing fallback values - Let errors bubble up naturally — don't wrap functions just to log
- Conventional commits:
type(scope): description - Types:
feat,fix,chore,refactor,docs,test,style,perf - Do NOT include
Co-Authored-Bytrailer — user is sole author - PRs: conventional title, Summary + Test plan, always draft
- Colocate state with consumers to minimize re-render blast radius
- Config-driven patterns (Record lookups, config arrays) over runtime branching
- Fail fast with assertions rather than propagating invalid state
- Prefer derived/computed values over redundant state
- Run the project's check/lint command after modifying code files
- ESLint autofix for auto-fixable issues only (import sorting, etc.) — never sort imports manually
- Check shared utilities before creating local helpers
- Comments only for: complex business logic, non-obvious algorithmic decisions, temporary workarounds/TODOs
When compacting context, preserve: current branch name and task, file paths already modified, error messages being debugged, the specific ticket being implemented.