diff --git a/AGENTS.md b/AGENTS.md index 240dfcef6..6dd6dc3df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,11 +6,44 @@ 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** is managed via corepack (`corepack enable`). +- **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 @@ -24,6 +57,8 @@ The dev default connection string is `postgresql://postgres:postgres@localhost:5 **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: @@ -32,22 +67,40 @@ Copy `.env.example` to `.env`. The three required variables are: - `DATABASE_URL` (default `postgresql://postgres:postgres@localhost:5432/postgres`) - `AUTH_SECRET` (any non-empty string) -S3/SeaweedFS is optional — if S3 vars are omitted or commented out, the app uses local filesystem storage under `/data`. +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. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file