# AGENTS.typescript.md — TypeScript/Next.js Agent Rules

Applies to: `pitch-deck/`, `admin-v2/` (Next.js apps in this repo)

---

## NON-NEGOTIABLE: Pre-Push Checklist

**BEFORE every `git push`, run ALL of the following from the Next.js app directory. A single failure blocks the push.**

```bash
# 1. Type check (catches the class of bug that broke ChatFAB.tsx — const inside object)
npx tsc --noEmit

# 2. Lint (ESLint with TypeScript-aware rules)
npm run lint

# 3. Production build (THE most important check — passes lint/types but still fails build)
npm run build
```

**One-liner pre-push gate:**
```bash
npx tsc --noEmit && npm run lint && npm run build
```

> **Why `npm run build` is mandatory:** Next.js performs additional checks during build (server component boundaries, missing env vars referenced in code, RSC/client component violations) that `tsc` and ESLint alone do not catch. The ChatFAB syntax error (`const` inside object literal) is exactly the kind of error caught only by build.

### Why each check matters

| Check | Catches | Time |
|-------|---------|------|
| `tsc --noEmit` | Type errors, wrong prop types, missing members | 5-20s |
| `eslint` | React hooks rules, import order, unused vars | 5-15s |
| `next build` | Server/client boundary violations, missing deps, syntax errors in JSX, env var issues | 30-120s |

---

## TypeScript Configuration

`tsconfig.json` must have strict mode enabled:

```json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  }
}
```

**Never use `// @ts-ignore` or `// @ts-expect-error` without a comment explaining why it's unavoidable.**

---

## ESLint Configuration

```json
{
  "extends": [
    "next/core-web-vitals",
    "plugin:@typescript-eslint/recommended-type-checked"
  ],
  "rules": {
    "@typescript-eslint/no-explicit-any": "error",
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/no-floating-promises": "error",
    "@typescript-eslint/await-thenable": "error",
    "react-hooks/exhaustive-deps": "error",
    "no-console": "warn"
  }
}
```

**`@typescript-eslint/no-floating-promises`** — catches `await`-less async calls that silently swallow errors.
**`react-hooks/exhaustive-deps`** — catches missing deps in `useEffect`/`useCallback` (source of stale closure bugs).

---

## Next.js 15 Rules (App Router)

### Server vs Client boundary

```typescript
// Server Component (default) — no 'use client' needed
// Can: fetch data, access DB, read env vars, import server-only packages
async function Page() {
  const data = await fetchData()  // direct async/await
  return <ClientComponent data={data} />
}

// Client Component — must have 'use client' at top
'use client'
// Can: use hooks, handle events, access browser APIs
// Cannot: import server-only packages (nodemailer, fs, db pool)
```

**Common violation:** Importing `lib/email.ts` (which imports nodemailer) from a client component → use `lib/email-templates.ts` instead.

### Route Handler typing

```typescript
// Always type request and use NextResponse
export async function GET(request: Request): Promise<NextResponse> {
  const { searchParams } = new URL(request.url)
  return NextResponse.json({ data })
}
```

### Environment variables

```typescript
// Server-only env vars: access directly
const secret = process.env.PITCH_ADMIN_SECRET  // fine in server components

// Client env vars: must be prefixed NEXT_PUBLIC_
const url = process.env.NEXT_PUBLIC_API_URL  // accessible in browser

// Never access server-only env vars in 'use client' components
```

---

## Component Architecture

```
app/
├── (route-group)/
│   ├── page.tsx           # Server Component — data fetching
│   └── _components/       # Colocated components for this route
│       ├── ClientThing.tsx  # 'use client' when needed
│       └── ServerThing.tsx  # Server by default
components/
│   └── ui/               # Shared presentational components
lib/
│   ├── server-only-module.ts  # import 'server-only' at top
│   └── shared-module.ts       # safe for both server and client
```

**Rules:**
- Push `'use client'` boundary as deep as possible (toward leaves)
- Never import server-only modules from client components
- Colocate `_components/` and `_hooks/` per route when they're route-specific

---

## Testing Requirements

```bash
# Type check (fastest, run first)
npx tsc --noEmit

# Unit tests (Vitest)
npx vitest run

# E2E tests (Playwright — CI only, requires running server)
npx playwright test
```

**Test every:**
- Custom hook (`usePresenterMode`, `useSlideNavigation`)
- Utility function (`lib/auth.ts` helpers, `lib/email-templates.ts`)
- API route handler (mock DB, assert response shape)

---

## Common Pitfalls That Break CI

| Pitfall | Prevention |
|---------|------------|
| `const x = ...` inside object literal | `tsc --noEmit` + `npm run build` |
| Server-only import in client component | `import 'server-only'` guard + ESLint |
| Missing `await` on async function call | `@typescript-eslint/no-floating-promises` |
| `useEffect` with missing dependency | `react-hooks/exhaustive-deps` error |
| `any` type hiding type errors | `@typescript-eslint/no-explicit-any` error |
| Unused variable left after refactor | `noUnusedLocals` in tsconfig |
| `process.env.SECRET` in client component | Next.js build error |
| Forgetting `export default` on page component | Next.js build error |
| Calling server action from server component | must use route handler instead |
| `jose` full import in Edge Runtime | Use specific subpath: `jose/jwt/verify` |
