# AGENTS.md ## Cursor Cloud specific instructions ### Overview Reactive Resume is a pnpm monorepo (Turborepo) with a single full-stack web app at `apps/web` (TanStack Start / React 19 / Vite) and ~15 internal packages under `packages/`. It runs as a single Node.js process on port 3000. Internal packages are source-consumed through `package.json` export maps that point at `src` files. Do not assume package-local `dist` output exists unless a package explicitly adds it. ### Prerequisites - **Node.js 24** (matches Dockerfile `ARG NODE_VERSION=24`). Use `nvm install 24 && nvm use 24` if needed. - **Docker** is required to run PostgreSQL. Start it with `sudo dockerd &` if the daemon isn't running. - **pnpm 11.0.9** is managed via corepack (`corepack enable`). ### Codebase map - `apps/web` is the only app. It owns TanStack Start routes, Vite/Nitro config, PWA setup, oRPC client wiring, route-level server handlers, and the resume builder UI. - `packages/api` contains oRPC routers, services, DTOs, storage, resume access policy, statistics, AI services, and rate limiting. The web route `apps/web/src/routes/api/rpc.$.ts` exposes these routers at `/api/rpc`. - `packages/auth` contains Better Auth config, auth helper functions, and exported auth types. The web route `apps/web/src/routes/api/auth.$.ts` delegates to `auth.handler`. - `packages/db` contains the Drizzle client and schema. Migration files live at the repo root in `migrations/`. - `packages/env` defines server environment validation and auto-loads the root `.env` for app/server code. - `packages/schema` contains Zod schemas and typed resume/page/template models. - `packages/pdf` contains the React PDF document, font registration, shared template primitives, and template implementations. - `packages/ui` contains shared Base UI/shadcn-style components and hooks. - `packages/fonts`, `packages/email`, `packages/import`, `packages/ai`, `packages/utils`, `packages/scripts`, `packages/config`, and `packages/runtime-externals` provide focused support surfaces. Prefer their existing exports over adding cross-package shortcuts. ### Web app conventions - Routes are file-based under `apps/web/src/routes`. Do not hand-edit `apps/web/src/routeTree.gen.ts`; it is generated by TanStack Router tooling. - Server-only route handlers use route `server.handlers` blocks, for example `api/rpc.$.ts`, `api/auth.$.ts`, `api/health.ts`, uploads, schema, OpenAPI, MCP, and `.well-known` routes. - `apps/web/src/router.tsx` initializes router context with `queryClient`, `orpc`, `theme`, `locale`, `session`, and `flags`. Reuse route context where possible instead of refetching these concerns ad hoc. - The builder shell lives under `apps/web/src/routes/builder/$resumeId`. The nested preview route is client-only (`ssr: false`), while the public resume route `apps/web/src/routes/$username/$slug.tsx` uses `ssr: "data-only"`. - Browser-only resume preview code is split across `apps/web/src/components/resume/preview.tsx`, `preview.browser.tsx`, `pdf-canvas.tsx`, and shared helpers. Keep PDF.js/canvas/browser APIs out of SSR paths. - The isomorphic oRPC client is in `apps/web/src/libs/orpc/client.ts`; server calls use an in-process router client and browser calls use `/api/rpc` with credentials included. ### Package and feature boundaries - Add new API procedures in `packages/api/src/routers/*` and keep business logic in `packages/api/src/services/*` or helpers. Prefer `protectedProcedure` from `packages/api/src/context.ts` for authenticated procedures. - Add database columns/tables in `packages/db/src/schema/*`, then generate root-level migrations with `pnpm db:generate`. - Add or change resume data shape in `packages/schema/src/resume/*` first, then update API DTOs, importers, PDF rendering, and web forms that consume that shape. - Add or rename templates in all relevant places: `packages/schema/src/templates.ts`, `packages/pdf/src/templates/index.ts`, template source under `packages/pdf/src/templates//`, and static previews under `apps/web/public/templates/{jpg,pdf}`. - Shared PDF section filtering lives in `packages/pdf/src/templates/shared/filtering.ts`. Keep template-specific visual exceptions in the owning template directory unless multiple templates need the same behavior. - `packages/pdf/src/hooks/use-register-fonts.ts` owns React PDF font registration, standard PDF font handling, CJK fallback stacks, and global hyphenation behavior. - `packages/utils` has narrowly exported helpers. If another package needs a utility, add an explicit export path instead of importing private files. ### Database PostgreSQL runs via Docker Compose: ``` sudo docker compose -f compose.dev.yml up -d postgres ``` The dev default connection string is `postgresql://postgres:postgres@localhost:5432/postgres`. **Important**: `drizzle-kit` (used by `pnpm db:migrate`) reads `DATABASE_URL` from `process.env` directly — it does **not** auto-load the `.env` file. You must `export DATABASE_URL=...` before running migration commands, or set it in your shell profile. The web app also runs migrations during Nitro startup via `apps/web/plugins/1.migrate.ts`. Manual `pnpm db:migrate` is mainly for first setup, migration debugging, or applying migrations without starting the app. ### Environment Copy `.env.example` to `.env`. The three required variables are: - `APP_URL` (default `http://localhost:3000`) - `DATABASE_URL` (default `postgresql://postgres:postgres@localhost:5432/postgres`) - `AUTH_SECRET` (any non-empty string) S3/SeaweedFS is optional. If `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, and `S3_BUCKET` are all set, the app uses S3-compatible storage. The checked-in `.env.example` sets SeaweedFS defaults, so either start the `seaweedfs` compose service too or comment out those S3 vars to use local filesystem storage under `/data`. `LOCAL_STORAGE_PATH` must be absolute when set. ### Common commands | Task | Command | |------|---------| | Install deps | `pnpm install` | | Start Postgres only | `sudo docker compose -f compose.dev.yml up -d postgres` | | Start Postgres + SeaweedFS | `sudo docker compose -f compose.dev.yml up -d postgres seaweedfs seaweedfs_create_bucket` | | Generate migrations | `DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" pnpm db:generate` | | Run migrations | `DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" pnpm db:migrate` | | Dev server | `pnpm dev` (starts on port 3000) | | Web dev server only | `pnpm dev:web` | | Lint/format | `pnpm check` (Biome) | | Tests | `pnpm test` (Vitest) | | Build | `pnpm build` | | Typecheck | `pnpm typecheck` | For focused validation, prefer package filters before repo-wide commands, for example: ``` pnpm --filter web typecheck pnpm --filter @reactive-resume/pdf test pnpm --filter @reactive-resume/api test ``` Vitest test paths are package-relative when running through `pnpm --filter test -- `. ### Gotchas - The dev server (`pnpm dev`) auto-runs migrations on startup via Nitro, so `pnpm db:migrate` is only strictly needed for first-time setup or after pulling new migration files. - Email sending requires SMTP config; without it, emails are logged to console. This is fine for dev — the app still functions, but email verification links appear in server logs. - The `lefthook.yml` pre-commit hook runs `biome check` on staged files. Run `pnpm check` before committing to avoid hook failures. - `pnpm check` is write-capable (`biome check --write --unsafe .`). Call that out when using it, and use narrower Biome commands if you need a non-mutating inspection. - Biome uses tabs, double quotes, line width 120, organized import groups, and sorted Tailwind classes for `clsx`, `cva`, and `cn`. - Most packages use `tsgo --noEmit` for typechecking and `vitest run --passWithNoTests` for tests. - There may be unrelated local edits in the worktree. Inspect `git status --short` first and avoid reverting files you did not touch.