# Coding Standards

TypeScript strict, ESLint + Prettier, accessibility baseline, and naming conventions for the dashboard template.

## TypeScript

`tsconfig.json` baseline:

```jsonc
// tsconfig.json:1
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "ES2022"],
    "module": "esnext",
    "moduleResolution": "bundler",
    "jsx": "preserve",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "noFallthroughCasesInSwitch": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "incremental": true,
    "plugins": [{ "name": "next" }],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "src/**/*", ".next/types/**/*.ts"],
  "exclude": ["node_modules", ".next", "dist", "coverage", "e2e"]
}
```

Rules:

- No `any`. Use `unknown` and narrow with Zod, type guards, or `instanceof`.
- No non-null assertions (`!`) outside tests. Validate at the boundary.
- Prefer `type` imports (`import type { Foo } from "..."`) for type-only symbols.
- Use `satisfies` for object literals that should match a contract without losing inference.

```ts
const config = {
  retries: 3,
  endpoint: "/api/users",
} satisfies Record<string, unknown>;
```

## ESLint

```js
// eslint.config.mjs:1
import next from "eslint-config-next";
import prettier from "eslint-config-prettier";

export default [
  ...next,
  prettier,
  {
    rules: {
      "@typescript-eslint/no-explicit-any": "error",
      "@typescript-eslint/consistent-type-imports": [
        "error",
        { prefer: "type-imports" },
      ],
      "react/jsx-no-leaked-render": "error",
      "no-console": ["warn", { allow: ["warn", "error"] }],
    },
  },
  {
    ignores: [".next/**", "node_modules/**", "coverage/**", "dist/**"],
  },
];
```

`bun run lint` runs ESLint. CI fails on any error.

## Prettier

```jsonc
// .prettierrc.json:1
{
  "semi": true,
  "singleQuote": false,
  "trailingComma": "all",
  "printWidth": 100,
  "tabWidth": 2,
  "arrowParens": "always"
}
```

```sh
bun run format          # write
bun run format:check    # check (CI)
```

Add a pre-commit hook (via `lefthook` or `husky`) running `format:check` and `lint` on staged files.

## Folder and File Naming

| Kind | Convention | Example |
|------|-----------|---------|
| Folder | kebab-case | `src/features/user-management/` |
| Component file | PascalCase.tsx | `UsersTable.tsx` |
| Hook file | camelCase.ts | `useUsers.ts` |
| Schema file | camelCase.schema.ts | `user.schema.ts` |
| API module | camelCase.api.ts | `users.api.ts` |
| Test file | `<name>.test.tsx` co-located | `UsersTable.test.tsx` |
| Server Action | camelCase.action.ts | `createUser.action.ts` |

## Component Conventions

- One component per file, exported as named export. Default exports only for route files (`page.tsx`, `layout.tsx`).
- Props type defined inline if used once; extract to `types.ts` if shared.
- Destructure props in the signature when more than three; otherwise pass `props` through.
- Never use `React.FC`. Type props directly:

```tsx
type ButtonProps = {
  label: string;
  onClick: () => void;
  loading?: boolean;
};

export function SubmitButton({ label, onClick, loading = false }: ButtonProps) {
  return <Button loading={loading} onClick={onClick}>{label}</Button>;
}
```

## Hook Conventions

- Always start with `use`.
- Return objects for hooks with multiple values, tuples for 1–2 related values.
- Never call hooks conditionally; never call hooks inside loops.
- Side effects go in `useEffect` with explicit dependencies. Lint will catch missing deps.

## API Conventions

- API modules return parsed, typed data — never raw axios responses.
- Schemas live in `schemas/`, parsed at the API boundary.
- Errors throw `ApiError`. Never swallow.
- No try/catch unless you're transforming the error or adding context.

## Logging

- Server: `console.error` for caught errors with full context. Use the team's logger if available.
- Client: `console.warn` and `console.error` only. No `console.log` in committed code.
- For user-facing errors, use `notifications.show(...)` from Mantine.

## Accessibility Checklist

Every interactive component must satisfy:

- [ ] Semantic HTML element (`<button>` not `<div onClick>`).
- [ ] Keyboard reachable (Tab, Enter, Space, Escape for modals).
- [ ] Visible focus indicator (Mantine handles this).
- [ ] `aria-label` on icon-only buttons.
- [ ] Color contrast ≥ WCAG AA.
- [ ] Form fields have `<label>` association (Mantine `label` prop wires this).
- [ ] Form errors announced via `aria-describedby` (Mantine wires this).
- [ ] Modals trap focus (Mantine handles this).
- [ ] Tables: `<caption>` for context, `scope="col"` on header cells.

## Imports

Order:

1. Node/React/Next built-ins (`react`, `next/*`)
2. Third-party (`@mantine/*`, `@tanstack/*`, `axios`, `zod`)
3. Internal alias (`@/components`, `@/lib`, `@/features`)
4. Relative (`./hooks/users.keys`)
5. Type imports (alphabetical, last)

Prettier + ESLint sort consistently with `eslint-plugin-import` rules. Empty line between groups.

```ts
import { useState } from "react";
import { Button } from "@mantine/core";

import { useQuery } from "@tanstack/react-query";

import { api } from "@/lib/axios";
import { usersKeys } from "../hooks/users.keys";

import type { User } from "../types";
```

## Git Conventions

- Branch names: `feat/<scope>`, `fix/<scope>`, `chore/<scope>`, `docs/<scope>`.
- Commit messages: Conventional Commits (`feat: add user table pagination`).
- One logical change per commit.
- Squash before merge.

## Forbidden Patterns

- `any` — use `unknown` and narrow.
- `// @ts-ignore` — fix the type instead; `@ts-expect-error` is acceptable with a comment.
- `useEffect` for derived state — compute inline or use `useMemo`.
- `dangerouslySetInnerHTML` — sanitize with DOMPurify if unavoidable.
- Direct `fetch` in features — go through the configured `api` instance.
- Hardcoded URLs — use `publicEnv.NEXT_PUBLIC_API_BASE_URL`.
- Magic numbers — name them.
- `var` — always `const` or `let`.