# AGENTS.md ## Architecture - `apps/next`: Next.js 16 frontend plus embedded Payload CMS. - `apps/expo`: Expo scaffold; only work here when explicitly requested. - `packages/backend/convex`: self-hosted Convex functions, schema, and auth. - `packages/ui`: shared shadcn-based UI components. - `tools`: shared ESLint, Prettier, Tailwind, TypeScript, and Vitest config. - Local development uses host-run apps, local Convex on ports 3240/3241, its own data volume, and Payload Postgres on port 5436. Convex does not use Postgres unless a cloned project explicitly enables the commented option. ## Protected and generated files - Never edit `packages/backend/convex/_generated/**` or `packages/backend/convex/http.ts`. - Do not rename `apps/next/next.config.js` or `apps/next/src/proxy.ts`. - Preserve `withPayload(...)` and `typescript.ignoreBuildErrors` in Next config. - Do not edit Payload-generated routes, layouts, import maps, or `apps/next/payload-types.ts` manually. - Do not modify Sentry config or `tools/tailwind/theme.css` unless requested. - Generated `.cache`, `.turbo`, `.local`, and environment files are ignored. ## Environment rules - Local `dev` and `staging` come only from Infisical via `scripts/with-env`; it never falls back to `.env*`. - Run `infisical login` and `infisical init` before local development. - Machine-generated values belong in `.local/.generated.env`; never put the generated Convex admin key in shared Infisical. - CI reads `staging` through `scripts/ci-export-env.mjs`, logged in as the read-only `convexmonorepo-ci` machine identity (Gitea secrets `INFISICAL_CLIENT_ID` and `INFISICAL_CLIENT_SECRET`). `scripts/export-env` refuses to run in CI. - `staging` holds the production values that CI builds and deploys with. - If you are logged in to more than one Infisical account, put `INFISICAL_EMAIL=...` in `scripts/flags/infisical.env` (gitignored) and the scripts switch the CLI to that account before exporting. - Server code imports validated variables from `@/env`. Client components and `instrumentation-client.ts` import from `@/publicEnv`, which keeps zod out of the browser bundle. Never read `process.env` elsewhere. - Add cache-relevant variables to `turbo.json` `globalEnv`. ## Code and dependency rules - Use `const` arrow functions instead of function declarations. - Add `.js` extensions to Convex generated-file imports. - Put custom frontend routes in `apps/next/src/app/(frontend)`. - Extend the Convex `users` table for application user data; do not add a profile table by default. Authenticate protected functions. - Manage shared versions in root catalogs. Never run `bun update` in a package. - Run root `bun install` and verify with `bun lint:ws`. - Update this guide when architecture or workflows change. ## Local stack ```sh bun db:up # start Payload Postgres, Convex, and dashboard bun dev:next # host Next + deploy/watch local Convex functions bun db:down # stop and preserve both data volumes bun db:down:wipe # remove both volumes, generated admin key, and seed marker bun db:sync:payload # refresh and apply the local Payload seed snapshot bun db:sync-env # re-push Infisical dev values into the local Convex env bun dev:kill # stop dev servers that survived a Ctrl-C ``` Local URLs: app `http://localhost:3000`, Convex `http://localhost:3240`, dashboard `http://localhost:6794`, Payload Postgres `localhost:5436`. Use `INFISICAL_ENV=staging bun dev:next` for staging services. `db:sync:payload` is an explicit, destructive local operation. It validates a remote staging source and localhost destination, stores the sensitive snapshot only under `.local/`, and never changes Convex or production data. Normal `db:up` never connects to staging; it only applies `.local/payload-staging.dump` when `.local/payload-seed-state.env` is absent or stale. Do not store seed markers in Payload Postgres; schema push treats unmanaged tables as drift. ## Payload schema changes Payload schema changes ship as migrations in `apps/next/src/migrations/`. Locally, Payload pushes schema edits straight into the localhost database. When a change is ready, run `bun with-env bunx payload migrate:create ` from `apps/next` and commit the generated files. Production runs pending migrations on startup (`prodMigrations` in `payload.config.ts`). Push only runs against a database on `localhost`: a push leaves a `dev` row in `payload_migrations`, and Payload's startup migration stops on a confirmation prompt when it finds one. For the same reason, a local production build against the local dev database hangs; point it at a database without that row. ## Analytics and bundle size - Plausible (`NEXT_PUBLIC_PLAUSIBLE_SRC`, the per-site script URL) loads through a same-origin proxy under `/pa`, clear of Payload's `/api` routes. PostHog (`NEXT_PUBLIC_POSTHOG_KEY`, `NEXT_PUBLIC_POSTHOG_HOST`) loads after the page is idle through `/ingest`. Both stay off when their env is unset. - Session replay comes from PostHog. Sentry carries errors and traces only. - `packages/ui/src/index.tsx` holds re-exports only (`cn` and `ccn` live in `utils.ts`). With `optimizePackageImports` in `next.config.js`, each page loads only the UI modules it imports. Declaring anything in the index breaks that. ## Deployment A push to `main` runs `.gitea/workflows/build-next.yml`: the quality gate, then `convex deploy` to production, then the image build and push, then `ssh gib@192.168.2.2 convexmonorepo` (the forced-command deploy on the VPS). A push to `main` is a production deploy. ## Validation Use `bun typecheck`, never a production build for routine validation. The full gate is `SKIP_E2E=1 bun run ci:check`; local-stack smoke e2e is `bun test:e2e`. Pre-commit runs lint-staged serially and pre-push runs the bounded full gate.