Skip to content

Code Style

Consistent style improves readability and lowers the cost of code review. This page describes the conventions used throughout the OpenScouter codebase.

TypeScript

The project uses TypeScript in strict mode. The tsconfig.json sets "strict": true, which enables all strict type-checking flags.

Do not use any. If a type is genuinely unknown at compile time, use unknown and narrow it explicitly. Do not suppress TypeScript errors with // @ts-ignore without a documented reason.

Prefer explicit return types on exported functions. Internal helpers may omit them when the type is obvious from context.

React Components

Use function components. Class components are not used in this codebase.

// Correct
export function UserCard({ name, email }: UserCardProps) {
return <div>{name}</div>;
}
// Incorrect
export class UserCard extends React.Component { ... }

Define prop types using a TypeScript interface or type alias declared immediately above the component. Do not use React.FC or React.FunctionComponent wrappers.

Extract subcomponents to their own files if they exceed roughly 50 lines or are reused in more than one place.

File Naming

Use kebab-case for all file and directory names:

  • user-card.tsx
  • api-client.ts
  • use-auth.ts
  • token-validation.test.ts

Use PascalCase for React component names and their exported identifiers:

user-card.tsx
export function UserCard() { ... }

Test files live alongside the code they test and share the same base name with a .test.ts or .test.tsx suffix.

Import Ordering

Imports are sorted in this order, with a blank line between each group:

  1. Node built-ins (e.g. path, fs)
  2. External packages (e.g. react, next, zod)
  3. Internal aliases (e.g. @/components, @/lib)
  4. Relative imports
import path from 'path';
import { z } from 'zod';
import type { NextApiRequest } from 'next';
import { db } from '@/lib/db';
import { TokenSchema } from '@/schemas/token';
import { formatDate } from './utils';

ESLint and Prettier enforce this automatically. Run npm run lint:fix to fix ordering violations.

Error Handling

Every async function that can fail must handle errors explicitly. Do not let errors propagate silently.

In API routes, return structured error responses:

try {
const result = await doSomething();
return res.status(200).json({ success: true, data: result });
} catch (error) {
logger.error({ error }, 'doSomething failed');
return res.status(500).json({ success: false, error: 'An unexpected error occurred.' });
}

Log full error context on the server. Return user-friendly messages in API responses. Never expose stack traces or internal details to clients.

In UI code, use error boundaries for component-level failures and display clear, actionable messages to users.

Constants and Configuration

Do not hardcode strings or numbers that represent configuration. Extract them as named constants or read them from environment variables.

// Correct
const MAX_NOTES_PER_TEST = 50;
// Incorrect
if (notes.length > 50) { ... }

All environment variable access should go through a validated config module, not direct process.env reads scattered across the codebase.

Function Size

Keep functions under 50 lines. If a function grows beyond that, look for a natural extraction point.

Prefer many small, well-named functions over a few large ones. A function that does one thing and is named clearly is easier to test and easier to reason about.