---
name: agentic-jumpstart-code-quality
description: Code quality standards for TypeScript and React including naming conventions, file organization, error handling, and best practices. Use when reviewing code, establishing patterns, or when the user mentions code quality, standards, conventions, formatting, or linting.
---
# Code Quality Standards
## TypeScript Best Practices
### Type Safety
```typescript
// GOOD: Explicit types for function parameters and returns
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
// GOOD: Use inferred types from Drizzle
import type { User, UserCreate } from "~/db/schema";
// GOOD: Define interfaces for complex objects
interface CourseWithProgress {
course: Course;
progress: number;
completedModules: number[];
}
// AVOID: Using `any`
// function processData(data: any) { ... }
// BETTER: Use unknown and narrow
function processData(data: unknown) {
if (isValidData(data)) {
// data is now typed
}
}
```
### Type Exports
```typescript
// Export types from schema
export type User = typeof users.$inferSelect;
export type UserCreate = typeof users.$inferInsert;
// Re-export for convenience
export type { User, UserCreate } from "~/db/schema";
```
## Naming Conventions
### Functions
| Layer | Convention | Example |
|-------|------------|---------|
| Data Access | `verbNoun` | `createUser`, `getSegmentById` |
| Use Cases | `verbNounUseCase` | `createUserUseCase`, `enrollInCourseUseCase` |
| Server Functions | `verbNounFn` | `createUserFn`, `updateProfileFn` |
### Components
```typescript
// PascalCase for components
function CourseCard({ course }: CourseCardProps) {}
function UserProfilePage() {}
// camelCase for props interfaces
interface CourseCardProps {
course: Course;
onEdit?: (id: number) => void;
}
```
### Files
```
src/
├── components/
│ ├── CourseCard.tsx # PascalCase for components
│ └── ui/
│ └── button.tsx # lowercase for shadcn/ui
├── hooks/
│ └── useAuth.ts # camelCase with use prefix
├── fn/
│ └── users.ts # lowercase, plural for collections
├── use-cases/
│ └── users.ts
└── data-access/
└── users.ts
```
### Variables and Constants
```typescript
// camelCase for variables
const currentUser = await getUserById(userId);
// SCREAMING_SNAKE_CASE for constants
const MAX_FILE_SIZE = 500 * 1024 * 1024;
const API_TIMEOUT_MS = 30000;
// Constants at top of file or in /src/config/
// src/config/index.ts
export const MAX_TITLE_LENGTH = 100;
export const PAGINATION_DEFAULT_LIMIT = 20;
```
## File Organization
### Maximum File Length
**Never let a file exceed 1,000 lines.** Split into smaller modules:
```typescript
// BEFORE: One large file
// src/routes/admin/courses/route.tsx (1500 lines)
// AFTER: Split into smaller files
// src/routes/admin/courses/route.tsx
// src/routes/admin/courses/-components/CourseList.tsx
// src/routes/admin/courses/-components/CourseForm.tsx
// src/routes/admin/courses/-components/CourseFilters.tsx
```
### Import Order
```typescript
// 1. External packages
import { useState, useEffect } from "react";
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
// 2. Internal modules (path alias)
import { authenticatedMiddleware } from "~/lib/auth";
import { updateUserUseCase } from "~/use-cases/users";
import { Button } from "~/components/ui/button";
// 3. Types (with type keyword)
import type { User, UserCreate } from "~/db/schema";
// 4. Relative imports (if any)
import { CourseCard } from "./-components/CourseCard";
```
### Path Aliases
Always use `~/` for imports from `/src/`:
```typescript
// GOOD
import { Button } from "~/components/ui/button";
import { database } from "~/db";
// AVOID
import { Button } from "../../../components/ui/button";
```
## Error Handling
### Use PublicError for User-Facing Errors
```typescript
// src/use-cases/errors.ts
export class PublicError extends Error {
constructor(message: string) {
super(message);
this.name = "PublicError";
}
}
// Usage
export async function enrollInCourseUseCase(userId: number, courseId: number) {
const course = await getCourseById(courseId);
if (!course) {
throw new PublicError("Course not found");
}
const existing = await getEnrollment(userId, courseId);
if (existing) {
throw new PublicError("Already enrolled in this course");
}
return createEnrollment({ userId, courseId });
}
```
### Handle Errors Gracefully
```typescript
// In server functions - errors bubble up to error boundary
export const enrollFn = createServerFn()
.middleware([authenticatedMiddleware])
.inputValidator(z.object({ courseId: z.number() }))
.handler(async ({ data, context }) => {
return enrollInCourseUseCase(context.userId, data.courseId);
});
// In components - use mutation error handling
const { mutate, error, isError } = useMutation({
mutationFn: enrollFn,
onError: (error) => {
toast.error(error.message);
},
});
```
## Magic Numbers
### Never Hard Code
```typescript
// BAD: Magic numbers in code
if (password.length < 8) { ... }
const result = items.slice(0, 20);
// GOOD: Constants at top of file
const MIN_PASSWORD_LENGTH = 8;
const DEFAULT_PAGE_SIZE = 20;
if (password.length < MIN_PASSWORD_LENGTH) { ... }
const result = items.slice(0, DEFAULT_PAGE_SIZE);
// BETTER: Shared constants in config
// src/config/index.ts
export const MIN_PASSWORD_LENGTH = 8;
export const DEFAULT_PAGE_SIZE = 20;
```
## Component Patterns
### Props Interface
```typescript
interface CourseCardProps {
course: Course;
onEdit?: (id: number) => void;
onDelete?: (id: number) => void;
className?: string;
}
export function CourseCard({
course,
onEdit,
onDelete,
className,
}: CourseCardProps) {
return (
{/* content */}
);
}
```
### Avoid Prop Drilling
```typescript
// BAD: Passing props through many levels
// GOOD: Use context or query
const { data: user } = useQuery(userQueryOptions());
// or
const user = useUserContext();
```
## Hooks Rules
### Custom Hook Pattern
```typescript
// Prefix with "use"
function useDebounce(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
```
### Effect Cleanup
```typescript
useEffect(() => {
const controller = new AbortController();
fetchData({ signal: controller.signal })
.then(setData)
.catch((err) => {
if (err.name !== "AbortError") setError(err);
});
// Always cleanup
return () => controller.abort();
}, [dependency]);
```
## Comments
### When to Comment
```typescript
// GOOD: Explain WHY, not WHAT
// We need to sort by module order first, then by segment order within each module
const sortedSegments = segments.sort((a, b) => {
if (a.moduleOrder !== b.moduleOrder) {
return a.moduleOrder - b.moduleOrder;
}
return a.order - b.order;
});
// BAD: Obvious comments
// Loop through users
for (const user of users) { ... }
// BAD: Commented out code (just delete it)
// const oldFunction = () => { ... };
```
### JSDoc for Public APIs
```typescript
/**
* Creates a new user account and sends welcome email.
* @param data - User registration data
* @throws {PublicError} If email is already registered
* @returns The created user without sensitive fields
*/
export async function createUserUseCase(data: UserCreate) {
// implementation
}
```
## Code Quality Checklist
- [ ] No `any` types - use `unknown` with type guards
- [ ] Naming follows conventions (verbNoun, verbNounUseCase, verbNounFn)
- [ ] Files under 1,000 lines
- [ ] No magic numbers - use constants
- [ ] Path alias `~/` used for imports
- [ ] Import order: external, internal, types, relative
- [ ] Errors use PublicError pattern
- [ ] Effects have cleanup functions
- [ ] No commented-out code
- [ ] Comments explain WHY, not WHAT