typescript
Patterns 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
Install
npx skills add https://github.com/gaia-react/gaia/tree/main/.claude/skills/typescript
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install gaia-react-gaia@llmmart
git clone https://github.com/gaia-react/gaia.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole gaia-react/gaia collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
TypeScript
Patterns and conventions for all TypeScript code.
Types
import type {}for type-only imports:import type {FC} from 'react'
Naming, camelCase
All identifiers use camelCase: Zod fields, form name/id/htmlFor, props, state, params.
Exceptions (snake_case OK):
types/database.ts, mirrors DB column names- Dynamic template literal names where variable part is already lowercase
- Environment variable names (
SUPABASE_URL)
Map snake_case ↔ camelCase at API call boundaries, not in schemas or UI code.
Naming, Descriptive and Self-Documenting
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.
- Functions and methods: imperative verb phrases:
calculateProgressPercentageFromCompletedSets,processUserOnboardingProfile - Parameters: role, not type:
totalSecondsnotn,emailAddressnots - Variables: what they hold:
restDurationInSeconds,submitButton,weightInputValue - No abbreviations: spell out unless universally known (
url,id,api):animationDurationInMillisecondsnotanimDur - No redundant words:
availableExercisesnotexerciseArray, but don't sacrifice clarity for brevity
Exception, React event handlers follow
handle{Action}{Element}from the react-code skill (e.g.handleClickSave,handleChangeInput), the{Element}is required, since a barehandleClickorhandleChangetripsreact-doctor/no-generic-handler-names. The descriptive naming guidelines above apply to utilities, hooks, callbacks, and non-event-handler functions.
Read references/naming-conventions.md for extended BAD/GOOD examples of each naming pattern.
Exported Functions, Explicit Return Types
All exported functions must have explicit return types.
Exceptions:
- Route loaders/actions (complex generics)
- React components typed with
FC<Props>(return type provided by generic)
// BAD
export const formatDate = (date: Date) => format(date, 'yyyy-MM-dd');
// GOOD
export const formatDate = (date: Date): string => format(date, 'yyyy-MM-dd');
General Rules
- Use
typenotinterface, interfaces support declaration merging, which creates unpredictable behavior;typeis consistent and predictable - Arrays:
string[]notArray<string> - Boolean naming:
^((can|has|hide|is|show)[A-Z]|checked|disabled|required)
Code Patterns
- No
switchstatements, use if/else chains or object maps; switch requiresbreak, is prone to fallthrough bugs, and is harder to type-check exhaustively - No TypeScript enums, use
as constobjects with derived types; enums compile to runtime objects with surprising behavior and don't tree-shake well - JSX boolean props: always explicit
={true}, makes props grep-able and avoids confusion when a prop is later refactored to a non-boolean type - 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
- Inline boolean coercion uses
!!x, neverBoolean(x); reserveBooleanfor 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 - Prefer
undefinedovernullfor GAIA-controlled absence (state, optional fields, internal sentinels); reservenullfor external contracts that require it: DOMuseRef(null), ref-callback params, React Routerdata(null), Zod.nullable(), and platform/library APIs that returnnull
Zod
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.
z.literal([...])notz.enum()for string unions, sort values alphanumerically
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.
Files (gaia)
-
references
-
naming-conventions.md 2.5 KB
# Naming Conventions, Extended Examples ## Functions and Methods Read like imperative verb phrases that describe what they do and what they act on. ```ts // BAD, vague, what does "handle" mean? what is "data"? const handle = (data: unknown) => { ... } const proc = (u: User) => { ... } const calc = (a: number, b: number) => { ... } // GOOD, clear intent at every call site const handleWorkoutSessionTimeout = (session: WorkoutSession) => { ... } const processUserOnboardingProfile = (user: User) => { ... } const calculateProgressPercentageFromCompletedSets = (completedSets: number, totalSets: number) => { ... } ``` > React event handlers are the exception (`handle{Action}{Element}`); see the event-handler note in `typescript/SKILL.md`. The descriptive guidelines above apply to utilities, hooks, callbacks, and non-event-handler functions. ## Parameters and Arguments Named for their role, not their type. ```ts // BAD, type as name, single-letter params const formatDuration = (n: number): string => { ... } const findUser = (s: string): User | null => { ... } // GOOD, role is immediately clear const formatDurationInSeconds = (totalSeconds: number): string => { ... } const findUserByEmailAddress = (emailAddress: string): User | null => { ... } ``` ## Variables and Constants Describe what they hold, not how they're used. ```ts // BAD, abbreviations, vague names const btn = document.querySelector('button'); const val = form.get('weight'); const temp = calculateRestDuration(); const MAX = 3; // GOOD, unambiguous at a glance const submitButton = document.querySelector('button'); const weightInputValue = form.get('weight'); const restDurationInSeconds = calculateRestDuration(); const maximumRetryAttemptCount = 3; ``` ## Avoiding Abbreviations Spell out words in full unless the abbreviation is universally known (e.g., `url`, `id`, `api`). ```ts // BAD const calcBMI = (ht: number, wt: number) => { ... } const usrPref = getUserPref(); const animDur = 300; // GOOD const calculateBodyMassIndex = (heightInCentimeters: number, weightInKilograms: number) => { ... } const userDisplayPreferences = getUserDisplayPreferences(); const animationDurationInMilliseconds = 300; ``` ## Omitting Redundant Words Don't pad names with type noise, but don't sacrifice readability for brevity. ```ts // BAD, redundant type noise const exerciseArray = getExercises(); const userObject = fetchUser(); // BAD, too terse const ex = getExercises(); const u = fetchUser(); // GOOD, just right const availableExercises = getExercises(); const currentUser = fetchUser(); ``` -
zod.md 4.5 KB
# Zod 4, Zod 3 → Zod 4 Migration Map ## Authoritative source, consult before judging a schema Zod 4 reworked the API heavily from v3, and most v3 forms still type-check, so training memory is an unreliable guide here: it is easy to "correct" valid v4 code back into deprecated v3 code, or to reject a valid form as invalid. The array union `z.literal(['a', 'b'])` in the migration map below is real Zod 4, not a mistake. So before writing a schema you are unsure of, and especially before flagging or rewriting an existing Zod form as wrong, verify it against the installed Zod's official docs and treat them as authoritative over memory. The docs are hosted, and the package advertises their URLs through the `package.json` auto-discovery convention. Read the URL from the installed package rather than hardcoding it: ```bash node -p "require('./node_modules/zod/package.json').llmsFull" # https://zod.dev/llms-full.txt, full concatenated docs node -p "require('./node_modules/zod/package.json').llms" # https://zod.dev/llms.txt, curated index ``` WebFetch that URL with a specific question instead of reading it into context. The full doc is ~65k tokens, but WebFetch distills it through a side model and returns only the answer, so context pays for the answer, not the doc. Ask for the exact signature and request a verbatim quote (e.g. "quote the exact `z.literal` signature for multiple values"). When the topic is unclear, WebFetch the smaller index first to find the right page. If the fields are absent, fall back to `https://zod.dev/llms-full.txt`. Version caveat: these hosted docs track the latest published Zod, not the installed one, so confirm the installed major matches before trusting them with `node -p "require('./node_modules/zod/package.json').version"`. If the project is ever pinned behind what zod.dev serves, the docs run ahead of the installed API and the memory-versus-reality problem returns. ## Migration map This project uses Zod 4; default to the Zod 4 form in every schema. | Zod 3, avoid | Zod 4, use | | ---------------------------------------- | ------------------------------------- | | `z.object({…}).strict()` | `z.strictObject({…})` | | `z.object({…}).passthrough()` | `z.looseObject({…})` | | `z.record(z.string())` | `z.record(z.string(), z.string())` | | `z.string().email()` | `z.email()` | | `z.string().url()` | `z.url()` | | `z.string().uuid()` | `z.uuid()` | | `z.string().datetime()` | `z.iso.datetime()` | | `z.enum(['metric', 'imperial'])` | `z.literal(['imperial', 'metric'])`, values sorted alphanumerically (`z.enum()` is valid Zod 4, but the project standardizes on the literal-array form) | | `z.function().args(a).returns(b)` | `z.function({input: [a], output: b})` | | `z.string({required_error: 'Required'})` | `z.string({error: 'Required'})` | ## Strict objects `.strict()` / `.passthrough()` / `.strip()` are deprecated methods. Use the top-level factories. ```ts // BAD, Zod 3 chained method const Payload = z.object({id: z.string(), tags: z.array(z.string())}).strict(); // GOOD, Zod 4 factory const Payload = z.strictObject({id: z.string(), tags: z.array(z.string())}); ``` ## String formats Format validators moved from `ZodString` methods to top-level functions (`z.iso.*` for ISO date/time). ```ts // BAD z.object({email: z.string().email(), createdAt: z.string().datetime()}); // GOOD z.object({email: z.email(), createdAt: z.iso.datetime()}); ``` ## Records Both key and value schemas are required, single-arg `z.record()` is Zod 3. ```ts // BAD z.record(z.number()); // GOOD z.record(z.string(), z.number()); ``` ## Functions `.args()` / `.returns()` chaining is replaced by an `{input, output}` object; `input` is the array of argument schemas. ```ts // BAD const fn = z.function().args(z.string(), z.number()).returns(z.boolean()); // GOOD const fn = z.function({input: [z.string(), z.number()], output: z.boolean()}); ``` ## Error customization The unified `error` key replaces `message`, `required_error`, `invalid_type_error`, and `errorMap`. ```ts // BAD z.string({required_error: 'Required', invalid_type_error: 'Must be text'}); z.string().min(5, {message: 'Too short'}); // GOOD z.string({error: 'Required'}); z.string().min(5, {error: 'Too short'}); ```
-
-
SKILL.md 4.7 KB
--- name: typescript description: Patterns 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 statements and enums. model: haiku --- # TypeScript Patterns and conventions for all TypeScript code. ## Types - `import type {}` for type-only imports: `import type {FC} from 'react'` ## Naming, camelCase All identifiers use camelCase: Zod fields, form `name`/`id`/`htmlFor`, props, state, params. **Exceptions (snake_case OK):** - `types/database.ts`, mirrors DB column names - Dynamic template literal names where variable part is already lowercase - Environment variable names (`SUPABASE_URL`) Map snake_case ↔ camelCase at API call boundaries, not in schemas or UI code. ## Naming, Descriptive and Self-Documenting 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. - **Functions and methods**: imperative verb phrases: `calculateProgressPercentageFromCompletedSets`, `processUserOnboardingProfile` - **Parameters**: role, not type: `totalSeconds` not `n`, `emailAddress` not `s` - **Variables**: what they hold: `restDurationInSeconds`, `submitButton`, `weightInputValue` - **No abbreviations**: spell out unless universally known (`url`, `id`, `api`): `animationDurationInMilliseconds` not `animDur` - **No redundant words**: `availableExercises` not `exerciseArray`, but don't sacrifice clarity for brevity > **Exception, React event handlers** follow `handle{Action}{Element}` from the react-code skill > (e.g. `handleClickSave`, `handleChangeInput`), the `{Element}` is required, since a bare > `handleClick` or `handleChange` trips `react-doctor/no-generic-handler-names`. The descriptive > naming guidelines above apply to utilities, hooks, callbacks, and non-event-handler functions. Read `references/naming-conventions.md` for extended BAD/GOOD examples of each naming pattern. ## Exported Functions, Explicit Return Types All exported functions must have explicit return types. **Exceptions:** - Route loaders/actions (complex generics) - React components typed with `FC<Props>` (return type provided by generic) ```tsx // BAD export const formatDate = (date: Date) => format(date, 'yyyy-MM-dd'); // GOOD export const formatDate = (date: Date): string => format(date, 'yyyy-MM-dd'); ``` ## General Rules - Use `type` not `interface`, interfaces support declaration merging, which creates unpredictable behavior; `type` is consistent and predictable - Arrays: `string[]` not `Array<string>` - Boolean naming: `^((can|has|hide|is|show)[A-Z]|checked|disabled|required)` ## Code Patterns - 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 - No TypeScript enums, use `as const` objects with derived types; enums compile to runtime objects with surprising behavior and don't tree-shake well - JSX boolean props: always explicit `={true}`, makes props grep-able and avoids confusion when a prop is later refactored to a non-boolean type - 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 - 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` - 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` ## Zod **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. - **`z.literal([...])` not `z.enum()`** for string unions, sort values alphanumerically 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.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.