$npx -y skills add gaia-react/gaia --skill typescriptPatterns and conventions for all TypeScript code. Use this skill whenever writing or reviewing TypeScript, naming identifiers, typing exports, choosing between type and interface, using Zod schemas, structuring function parameters, or enforcing code patterns like avoiding switch
| 1 | # TypeScript |
| 2 | |
| 3 | Patterns and conventions for all TypeScript code. |
| 4 | |
| 5 | ## Types |
| 6 | |
| 7 | - `import type {}` for type-only imports: `import type {FC} from 'react'` |
| 8 | |
| 9 | ## Naming, camelCase |
| 10 | |
| 11 | All identifiers use camelCase: Zod fields, form `name`/`id`/`htmlFor`, props, state, params. |
| 12 | |
| 13 | **Exceptions (snake_case OK):** |
| 14 | |
| 15 | - `types/database.ts`, mirrors DB column names |
| 16 | - Dynamic template literal names where variable part is already lowercase |
| 17 | - Environment variable names (`SUPABASE_URL`) |
| 18 | |
| 19 | Map snake_case ↔ camelCase at API call boundaries, not in schemas or UI code. |
| 20 | |
| 21 | ## Naming, Descriptive and Self-Documenting |
| 22 | |
| 23 | Follow Apple's Swift API Design Guidelines: names should be clear at the point of use, reading like prose. Favor long, descriptive names over short or abbreviated ones. Code should be readable without consulting documentation. |
| 24 | |
| 25 | - **Functions and methods**: imperative verb phrases: `calculateProgressPercentageFromCompletedSets`, `processUserOnboardingProfile` |
| 26 | - **Parameters**: role, not type: `totalSeconds` not `n`, `emailAddress` not `s` |
| 27 | - **Variables**: what they hold: `restDurationInSeconds`, `submitButton`, `weightInputValue` |
| 28 | - **No abbreviations**: spell out unless universally known (`url`, `id`, `api`): `animationDurationInMilliseconds` not `animDur` |
| 29 | - **No redundant words**: `availableExercises` not `exerciseArray`, but don't sacrifice clarity for brevity |
| 30 | |
| 31 | > **Exception, React event handlers** follow `handle{Action}{Element}` from the react-code skill |
| 32 | > (e.g. `handleClickSave`, `handleChangeInput`), the `{Element}` is required, since a bare |
| 33 | > `handleClick` or `handleChange` trips `react-doctor/no-generic-handler-names`. The descriptive |
| 34 | > naming guidelines above apply to utilities, hooks, callbacks, and non-event-handler functions. |
| 35 | |
| 36 | Read `references/naming-conventions.md` for extended BAD/GOOD examples of each naming pattern. |
| 37 | |
| 38 | ## Exported Functions, Explicit Return Types |
| 39 | |
| 40 | All exported functions must have explicit return types. |
| 41 | |
| 42 | **Exceptions:** |
| 43 | |
| 44 | - Route loaders/actions (complex generics) |
| 45 | - React components typed with `FC<Props>` (return type provided by generic) |
| 46 | |
| 47 | ```tsx |
| 48 | // BAD |
| 49 | export const formatDate = (date: Date) => format(date, 'yyyy-MM-dd'); |
| 50 | |
| 51 | // GOOD |
| 52 | export const formatDate = (date: Date): string => format(date, 'yyyy-MM-dd'); |
| 53 | ``` |
| 54 | |
| 55 | ## General Rules |
| 56 | |
| 57 | - Use `type` not `interface`, interfaces support declaration merging, which creates unpredictable behavior; `type` is consistent and predictable |
| 58 | - Arrays: `string[]` not `Array<string>` |
| 59 | - Boolean naming: `^((can|has|hide|is|show)[A-Z]|checked|disabled|required)` |
| 60 | |
| 61 | ## Code Patterns |
| 62 | |
| 63 | - No `switch` statements, use if/else chains or object maps; switch requires `break`, is prone to fallthrough bugs, and is harder to type-check exhaustively |
| 64 | - No TypeScript enums, use `as const` objects with derived types; enums compile to runtime objects with surprising behavior and don't tree-shake well |
| 65 | - JSX boolean props: always explicit `={true}`, makes props grep-able and avoids confusion when a prop is later refactored to a non-boolean type |
| 66 | - Max 3 function parameters, use an options object beyond that; call sites with 4+ positional args are hard to read and argument order mistakes are common |
| 67 | - Inline boolean coercion uses `!!x`, never `Boolean(x)`; reserve `Boolean` for point-free use (e.g. `array.filter(Boolean)`), and coerce per operand in a nullable `||` chain (`!!a || !!b`), since `!!(a || b)` trips `@typescript-eslint/prefer-nullish-coalescing` |
| 68 | - Prefer `undefined` over `null` for GAIA-controlled absence (state, optional fields, internal sentinels); reserve `null` for external contracts that require it: DOM `useRef(null)`, ref-callback params, React Router `data(null)`, Zod `.nullable()`, and platform/library APIs that return `null` |
| 69 | |
| 70 | ## Zod |
| 71 | |
| 72 | **This project uses Zod 4**, in every schema. The deprecated Zod 3 chained forms (`.strict()`, `.email()`, single-arg `z.record()`, `.args().returns()`) still type-check and lint clean, so nothing flags them, reach for the Zod 4 form deliberately. |
| 73 | |
| 74 | - **`z.literal([...])` not `z.enum()`** for string unions, sort values alphanumerically |
| 75 | |
| 76 | Read `references/zod.md` for the full Zod 3 → Zod 4 migration map (`z.strictObject`, top-level string formats, `z.record` arity, function and error shapes). It opens with a standing directive to verify uncertain or suspect Zod forms against the official docs (WebFetched, auto-discovered from `node_modules/zod/package.json`) rather than trusting v3-era memory. |