---
description: TypeScript strict mode conventions
globs: ["**/*.{ts,tsx,mts,cts}"]
alwaysApply: false
---

# Strict TypeScript

- Keep `strict: true`, `noUncheckedIndexedAccess`, `noImplicitOverride`, and `exactOptionalPropertyTypes` (if enabled). Never loosen `tsconfig.json` to fix an error.
- No `any`. External data is `unknown` until validated with a schema (zod/valibot) at the boundary; derive types with `z.infer`.
- No non-null assertions (`!`) or `as` casts to silence errors. Narrow with checks, `in`, `instanceof`, or type guards (`function isUser(x: unknown): x is User`). `as const` and `satisfies` are fine.
- Model states as discriminated unions (`{ status: 'loading' } | { status: 'ok'; data: T } | { status: 'error'; error: string }`) and switch exhaustively with a `never` check.
- Prefer `type` aliases for unions and object shapes; `interface` for extendable public contracts. Be consistent with the file.
- Use `readonly` arrays/properties for data that shouldn't change; `Readonly<T>` for params.
- Return types explicit on exported functions.
- Errors: throw `Error` subclasses; in `catch (e)` treat `e` as `unknown`.
- Utility types (`Pick`, `Omit`, `Partial`, `Record`) over duplicating shapes; avoid deeply clever conditional types in app code.
- No `// @ts-ignore`; if unavoidable, `// @ts-expect-error <reason>`.
- `import type` for type-only imports (`verbatimModuleSyntax`).

## Done when
`npx tsc --noEmit` reports zero errors and lint passes.
