accessibility
Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…
TypeScript patterns for strict, type-safe, maintainable code in modern TypeScript and JavaScript applications. Covers strict compiler configuration, type design, narrowing, discriminated unions, generics, async and error handling, module boundaries, dependency hygiene, and
Claude Code
$ npx -y skills add affaan-m/ECC --skill typescript-patterns --agent claude-code
Codex
$ npx skills add affaan-m/ECC --skill typescript-patterns -a codex -y
Cursor
$ npx skills add affaan-m/ECC --skill typescript-patterns -a cursor -y
Gemini CLI
$ npx skills add affaan-m/ECC --skill typescript-patterns -a gemini-cli -y
GitHub Copilot
$ npx skills add affaan-m/ECC --skill typescript-patterns -a github-copilot -y
OpenCode
$ npx skills add affaan-m/ECC --skill typescript-patterns -a opencode -y
Windsurf
$ npx skills add affaan-m/ECC --skill typescript-patterns -a windsurf -y
name: typescript-patterns description: TypeScript patterns for strict, type-safe, maintainable code in modern TypeScript and JavaScript applications. Covers strict compiler configuration, type design, narrowing, discriminated unions, generics, async and error handling, module boundaries, dependency hygiene, and type-level testing. Use when writing or reviewing TypeScript or JavaScript code. metadata: origin: ECC
TypeScript patterns and best practices for building robust, type-safe, and maintainable applications. The compiler is most useful when it can prove your program's shape, so the guiding principle here is: push the type system to do real work instead of fighting it. This skill is the "how." For the "what" and "when" (the decisions about which pattern to reach for), see the TypeScript rules — `rules/typescript/coding-style.md` and `rules/typescript/patterns.md` in this repository, installed as `rules/ecc/typescript/coding-style.md` and `rules/ecc/typescript/patterns.md`.
`strict: true` (which enables `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, and the rest) is not optional in a healthy codebase. If a codebase cannot compile under strict, that is a migration task, not an excuse to leave the flag off.
// tsconfig.json — a strict, discriminating baseline
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
"verbatimModuleSyntax": true
}
}`noUncheckedIndexedAccess` makes array and record access possibly-undefined, which surfaces a whole class of runtime crashes at compile time. If it produces too much noise, this is the pattern to add, not the flag to remove:
// If you have proven the index exists, lift the element into a local first.
const [first] = items
if (first === undefined) {
throw new Error("expected at least one item")
}`any` disables the type system exactly where a bug is most likely to hide: at the boundary with untrusted or dynamic data. Prefer `unknown` and narrow it. When you truly need a fallback, prefer an explicit loosening with a comment over a silent `any`.
// Bad: any disables checking on the public API
export function parseConfig(raw: any): Config {
return raw.config ?? {}
}
interface Config {
timeout: number
retries: number
}
const DEFAULT_CONFIG: Config = { timeout: 30_000, retries: 3 }
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}
function toPositiveInt(value: unknown, field: string): number {
if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
throw new TypeError(`Expected ${field} to be a positive integer`)
}
return value
}
// Good: unknown forces safe narrowing at the edge. Every consumed field is
// validated with an explicit range, and defaults are declared up front.
export function parseConfig(raw: unknown): Config {
if (!isRecord(raw)) return { ...DEFAULT_CONFIG }
return {
timeout: raw['timeout'] === undefined ? DEFAULT_CONFIG.timeout : toPositiveInt(raw['timeout'], 'timeout'),
retries: raw['retries'] === undefined ? DEFAULT_CONFIG.retries : toPositiveInt(raw['retries'], 'retries'),
}
}TypeScript infers local variables well. Exporting functions whose parameter and return types are implicit leaks inference decisions into every consumer and makes intent unreadable.
// Bad: exported API without explicit types
export function formatUser(user) {
return `${user.firstName} ${user.lastName}`
}
// Good: explicit public contract, inferred internals
interface User {
firstName: string
lastName: string
}
export function formatUser(user: User): string {
return `${user.firstName} ${user.lastName}`
}`interface` for object shapes that may be extended or implemented; `type` for unions, intersections, tuples, mapped types, and utility types. Prefer string-literal unions over `enum` unless you need runtime value enumeration or interop with code that requires it.
interface User {
id: string
email: string
}
type UserRole = 'admin' | 'member'
type AdminUser = User & { role: 'admin' }When a value can be in multiple shapes, model it as a discriminated union with a literal `type`/`kind` field. Exhaustive checking then becomes a free compiler feature: never a runtime fallback, always a compile error on a missing branch.
type Transaction =
| { kind: 'draft'; id: string }
| { kind: 'submitted'; id: string; submittedAt: Date }
| { kind: 'paid'; id: string; paidAt: Date; amountCents: number }
function describe(t: Transaction): string {
switch (t.kind) {
case 'draft':
return 'Saved but not sent'
case 'submitted':
return `Submitted on ${t.submittedAt.toISOString()}`
case 'paid':
return `Paid ${t.amountCents} cents on ${t.paidAt.toISOString()}`
default:
// exhaustive: any new member forces this to fail to compile
return assYour agent can write code, but ECC gives it a coordinated engineering system and toolbox: it plans before it builds, verifies changes with tests, reviews its own work from a fresh context, remembers what matters, and turns repeated wins into reusable skills
Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…
Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution,…
Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and…
Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when…
Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports.…
Security hardening guidance for AI agent frameworks that process untrusted content, invoke tools, write workspace files, manage…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.
Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…
Act on user feedback attached to the current session. Use when the user submits feedback on the session's changes via the Submit…