# AGENTS.md — AI collaboration (Echo OCR)

Guidance for agents and humans working in this repo. Prefer existing patterns over new abstractions.

## 1. Project overview

Echo OCR is a Next.js + Prisma app for Echo Life Church. It ingests paper "connect cards" via OCR (scanned PDFs / images / public survey submissions), extracts structured data, stores it in PostgreSQL behind a multi-tenant org model, and serves a filterable review UI with integrations to Planning Center, Monday.com, Airtable, and webhooks.

- **Framework:** Next.js 16 (App Router) + React 19, TypeScript strict
- **Data:** Prisma 7 (custom client output at `src/generated/prisma/`) + PostgreSQL via `pg` + `@prisma/adapter-pg`
- **Auth:** NextAuth 5 beta — session in `src/auth.ts`; server-side `auth()` wrapped by `requireApiAuthWithOrg` in `src/lib/api-auth.ts`
- **UI:** Tailwind v4 + shadcn/ui primitives in `src/components/ui/`; `lucide-react` icons; `sonner` toasts
- **OCR / AI:** Ollama vision models (local) and the `ai` SDK with OpenAI-compatible gateways
- **Storage:** S3-compatible (MinIO local, anything S3 in prod) via `@aws-sdk/client-s3`
- **Hosting:** Vercel (`vercel.json`) primary; Dockerfile + `docker-compose.yml` for Coolify / local

## 2. Architecture quick reference

| Area | Path | Notes |
| --- | --- | --- |
| App pages | `src/app/(dashboard)/`, `src/app/(auth)/`, `src/app/(marketing)/` | Route groups; dashboard is the protected app shell |
| API routes | `src/app/api/**/route.ts` | All start with `requireApiAuthWithOrg()`; errors via `handleApiError` |
| Prisma client | `src/lib/db.ts` | Lazy proxy singleton; **import `prisma` from `@/lib/db`** (never instantiate `new PrismaClient()`) |
| Auth helpers | `src/lib/api-auth.ts`, `src/auth.ts` | `requireApiAuthWithOrg(action?)` returns an `OrgSession` with `user.id` + `user.orgId` |
| Permissions | `src/lib/permissions.ts` | `can(role, action)` and `Action` union; roles: `owner > admin > editor > reviewer > viewer` |
| UI primitives | `src/components/ui/` | shadcn-style; do not duplicate — extend or compose |
| Feature components | `src/components/cards/`, `src/components/forms/`, etc. | Co-located by feature |
| OCR pipeline | `src/lib/ocr.ts`, `src/lib/ai-ocr.ts`, `src/lib/ollama.ts` | Background job model in `ProcessingJob` |
| Integrations | `src/lib/integrations/providers/` | Each provider exports the same shape; fired via `fireIntegrationEvent()` |
| Schema | `prisma/schema.prisma` | 21 models, no enums; multi-tenant via `organizationId` |
| Schema map | `docs/SCHEMA_MAP.md` | Regenerate with `npm run schema:map` |

Generated Prisma client lives at `src/generated/prisma/` — do not edit; regenerate with `npx prisma generate`.

## 3. Key conventions

- **Auth (server):** `const session = await requireApiAuthWithOrg(); /* session.user.orgId */`. Pass an optional `Action` to enforce a permission inline; `handleApiError` returns the right 401/403/500.
- **Multi-tenancy:** every `ResponseCard`, `FormTemplate`, `Person`, etc. is scoped by `organizationId`. **Always** include `organizationId: session.user.orgId` in `where:` filters and create payloads — there is no row-level security in dev.
- **API errors:** wrap handlers in `try { ... } catch (error) { return handleApiError(error); }`. Don't hand-roll `NextResponse.json({ error }, { status: 500 })`.
- **Validation:** body parsing is hand-rolled with defensive defaults (see `src/app/api/cards/[id]/route.ts` PUT). Zod is in `package.json` but not widely adopted yet — match the surrounding file's style.
- **Form templates:** field keys that match a top-level `ResponseCard` column must be `isCore: true` (see `src/lib/form-templates.ts`). Non-core values live in `ResponseCard.fieldData` JSON; the PUT route auto-promotes matching keys to columns.
- **Toasts:** `import { toast } from "sonner"` — `toast.success`, `toast.error`, `toast.info`.
- **Imports:** `@/*` → `src/*` (`tsconfig.json` `paths`). Generated Prisma at `@/generated/prisma/...`.
- **File names:** kebab-case for routes (`reprocess-batch/`), kebab-case for components (`upload-modal.tsx`), camelCase for hooks (`useUserProfile`).
- **Client vs server:** every interactive page starts with `"use client"`; API routes never include it.

## 4. Common gotchas

- `ResponseCard.name` is a **denormalized** display string. The PUT route at `src/app/api/cards/[id]/route.ts` recomputes it from `firstName + lastName` on edit; the OCR pipeline and `survey/submit` populate it on create. If you write a new path that mutates first/last, recompute `name` too.
- Prisma client lives at `src/generated/prisma/` (custom output, not the default `@prisma/client`). Import enums and types from there.
- `src/lib/db.ts` exports a **lazy `Proxy`** so importing `prisma` doesn't open a DB pool at module load — required for Next.js build-time data collection without `DATABASE_URL`. Don't call `prisma` from top-level module code.
- MinIO/S3 images are served through `GET /api/images/[...path]` which presigns and proxies. Don't hand back raw S3 URLs to the client.
- The Cloudflare/Vercel route `src/middleware.ts` enforces auth before requests hit `src/app/...`. New unauthenticated routes must be allowlisted there.
- `prisma/schema.prisma` does **not** declare a `url`. The connection string comes from `DATABASE_URL` at runtime — local dev needs `.env`.

## 5. Running locally

- **Runtime:** Node 18+ (Node 20 LTS recommended).
- **Setup:** see [`README.md`](README.md) (`docker compose up -d postgres minio`, `npm run db:push`, `npm run dev`). Requires Ollama with a vision model (`ollama pull llava:7b`) and GraphicsMagick for PDF rasterization.
- **Dev server:** `npm run dev`. App at `http://localhost:3000`, MinIO console at `:9001`.

## 6. Testing

- **Runner:** none configured yet. There are no unit, integration, or E2E tests. Adding tests is welcome — start with `vitest` and `@playwright/test`; gate them in CI before requiring green.

## 7. Deployment

- Primary target is Vercel (`vercel.json`). `Dockerfile` + `docker-compose.yml` are for Coolify / self-host. Build step is `npx prisma generate && next build` (see `package.json`). Notes on Coolify / Postgres / MinIO / Traefik wiring are in [`server_deploy.md`](../server_deploy.md) at the workspace root.
