# 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.