# AGENTS.md

This is a subscription SaaS built with Next.js (App Router), Supabase, and Stripe.

## Project

Stack: Next.js 15 (App Router), TypeScript (strict), Tailwind, Supabase (auth + Postgres), Stripe, deployed on Vercel.

Layout:

- `src/app/(marketing)/` public pages, static where possible
- `src/app/(app)/` authenticated product, every route under an auth guard
- `src/app/api/` route handlers, including `src/app/api/stripe/webhook/route.ts`
- `src/components/ui/` primitives, `src/components/` composed features
- `src/lib/supabase/server.ts` server client, `src/lib/supabase/client.ts` browser client
- `src/lib/stripe.ts` Stripe SDK instance and price ID map
- `src/db/schema.ts` Drizzle schema, `src/db/migrations/` generated SQL
- `middleware.ts` session refresh and route protection

Entry points: `src/app/layout.tsx` (root shell), `middleware.ts` (auth), `src/app/api/stripe/webhook/route.ts` (billing state).

## Commands

Use pnpm. Do not use npm or yarn, the lockfile is pnpm-lock.yaml.

```bash
pnpm install              # install dependencies
pnpm dev                  # local dev server on :3000
pnpm build                # production build, must pass before any PR
pnpm start                # serve the production build
pnpm lint                 # eslint
pnpm typecheck            # tsc --noEmit
pnpm test                 # vitest run
pnpm test src/lib/billing # run one file or directory
pnpm db:generate          # generate a migration from schema.ts
pnpm db:migrate           # apply migrations to the current DATABASE_URL
pnpm db:studio            # inspect data locally
```

Stripe webhooks locally: `stripe listen --forward-to localhost:3000/api/stripe/webhook`.

Before returning work, run `pnpm typecheck` and `pnpm lint`.

## Code style

- Server components are the default. Add `"use client"` only for state, effects, or browser APIs, and push it as far down the tree as possible.
- Never query the database or read secrets in a client component.
- Data fetching happens in server components or route handlers, never in useEffect.
- Validate every request body and form input with Zod before it reaches the database.
- Use the typed Supabase client from `src/lib/supabase/server.ts`. Do not construct clients inline.
- Absolute imports only, via the `@/` alias. No `../../` chains.
- No `any`. If a type is genuinely unknown use `unknown` and narrow it.
- Tailwind utilities in the markup. No CSS modules, no styled-components, no inline style objects for layout.
- Money is stored and passed around in integer cents, never floats.
- User facing strings live in the component, not in helper files.

## Boundaries

Do not touch without explicit instruction:

- `src/db/migrations/` Migrations are generated, never hand edited, and never deleted once applied.
- `pnpm-lock.yaml` Do not regenerate. Add dependencies with `pnpm add` so the diff stays minimal.
- `.env`, `.env.local`, or any secret value. Add new keys to `.env.example` with an empty value instead.
- `.github/workflows/` and `vercel.json`.
- Stripe price IDs and product configuration.

Needs human review before merge: anything under `src/app/api/stripe/`, changes to `middleware.ts`, row level security policies, and any change to how a subscription tier maps to product access.

If a task appears to require a schema change, propose the schema diff first and wait.

## Testing

- `pnpm test` runs the suite with Vitest. `pnpm test <path>` runs one file.
- Required tests: billing logic (plan resolution, proration, webhook handlers), auth guards, and any pure function in `src/lib/`.
- Webhook handlers are tested against recorded Stripe event fixtures in `src/test/fixtures/stripe/`. Add a fixture rather than mocking the SDK.
- UI tests are not required for presentational components.
- A bug fix ships with a test that fails without the fix.

## Git workflow

- Branch from `main`: `feat/short-description`, `fix/short-description`, `chore/short-description`.
- Conventional commits: `feat(billing): handle subscription downgrade`. Imperative mood, lowercase subject, no trailing period.
- One logical change per commit. Do not mix a refactor with a behavior change.
- Never commit directly to `main` and never force push a shared branch.
- PR description states what changed, why, and how it was verified. Link the issue. Note any migration or env var the reviewer must run.
