.planning/codebase/STRUCTURE.md
Analysis Date: 2026-05-11
reactive-resume/
├── apps/
│ └── web/ # Sole deployable application (TanStack Start / Vite / Nitro)
│ ├── plugins/ # Nitro startup plugins (run on `pnpm dev` / `pnpm start`)
│ ├── public/ # Static assets (PWA, opengraph, templates, screenshots)
│ ├── locales/ # Lingui `.po` translation catalogs (~40 locales)
│ ├── src/
│ │ ├── components/ # Reusable web components grouped by feature
│ │ ├── dialogs/ # Global dialog system + dialog implementations
│ │ ├── hooks/ # App-level React hooks
│ │ ├── libs/ # Browser/server glue (auth, orpc, query, resume, theme, locale, pwa)
│ │ ├── routes/ # File-based TanStack Router routes (UI + `server.handlers`)
│ │ ├── router.tsx # Router factory (builds query client, context, SSR query integration)
│ │ ├── routeTree.gen.ts # GENERATED — do not hand-edit
│ │ ├── server.ts # Nitro fetch entry (wraps `react-start/server-entry`)
│ │ └── index.css # Tailwind v4 entry
│ ├── components.json # shadcn config
│ ├── lingui.config.ts # i18n extract config
│ ├── vite.config.ts # Vite + TanStack Start + Nitro + PWA + Lingui
│ └── vitest.config.ts
├── packages/
│ ├── ai/ # AI prompts, Zustand store, patch-resume tool
│ ├── api/ # oRPC routers, services, helpers, DTOs, rate-limit middleware
│ ├── auth/ # Better Auth config and helpers
│ ├── config/ # Shared tsconfig + vitest base configs
│ ├── db/ # Drizzle client + schema (Postgres)
│ ├── email/ # Nodemailer transport + react-email templates
│ ├── env/ # `@t3-oss/env-core` server env (auto-loads root `.env`)
│ ├── fonts/ # Google Fonts metadata
│ ├── import/ # JSON Resume / Reactive Resume v3/v4 importers
│ ├── pdf/ # React PDF document, fonts, 14 templates, shared primitives
│ ├── runtime-externals/ # Declares bcrypt, sharp, @aws-sdk/client-s3 as runtime-only
│ ├── schema/ # Zod schemas (resume data, templates, page, analysis, icons)
│ ├── scripts/ # Standalone tsx scripts (db reset, font generation)
│ ├── ui/ # Shared Base UI + shadcn-style components
│ └── utils/ # Small focused helpers (color, date, html, sanitize, etc.)
├── migrations/ # Drizzle-generated SQL + snapshots (one folder per migration)
├── docs/ # Project documentation
├── skills/ # Internal agent skill definitions
├── data/ # Local-FS storage root (when S3 is unset) — gitignored runtime data
├── .vite-hooks/ # Lefthook-managed git hooks (commit-msg, pre-commit)
├── .github/ # GitHub Actions / issue templates
├── .claude/ # Project-local Claude/Codex agent assets
├── .codex/
├── .planning/ # GSD planning + codebase maps (this directory)
├── .vscode/
├── compose.dev.yml # Postgres + SeaweedFS for local dev
├── compose.yml # Production-shaped compose
├── Dockerfile # Node 24 multi-stage build
├── AGENTS.md # Canonical agent guide (symlinked from CLAUDE.md)
├── README.md
├── SECURITY.md
├── LICENSE
├── package.json # Root scripts (turbo run …) + workspace devDeps
├── pnpm-workspace.yaml # `apps/*` + `packages/*`
├── pnpm-lock.yaml
├── turbo.json # Pipeline + globalEnv allowlist
├── biome.json # Biome (lint + format) config
├── knip.json # Dead-code/dep analysis config
├── lefthook.yml # Git hooks
├── commitlint.config.cjs
├── crowdin.yml # Translation sync config
├── tsconfig.json # Root TS solution config
├── vitest.shared.ts / vitest.setup.ts # Shared vitest config and global setup
├── .ncurc.cjs # npm-check-updates ignore list
├── .env.example # Documented env vars
└── .gitignore / .dockerignore
apps/web/:
apps/web/vite.config.ts, apps/web/src/router.tsx, apps/web/src/server.ts, apps/web/src/routes/__root.tsx, apps/web/plugins/1.migrate.ts, apps/web/plugins/2.storage.ts.apps/web/src/routes/:
server.handlers), or both.__root.tsx — global HTML shell, providers, head/meta, PWA scripts._home/ — public marketing layout (route.tsx, index.tsx, -sections/{hero,features,faq,testimonials,...}.tsx).auth/ — login/register/2FA/password flows and OAuth callback (oauth.ts).dashboard/ — authenticated dashboard (route.tsx, index.tsx, resumes/, settings/{profile,preferences,api-keys,authentication,integrations,job-search,danger-zone}.tsx, -components/{header,sidebar,functions}.{ts,tsx}).builder/$resumeId/ — resume builder (route.tsx shell, index.tsx browser-only preview, -components/, -sidebar/{left,right}/, -store/{section,sidebar}.ts).$username/$slug.tsx — public/shared resume route (ssr: "data-only").api/ — server-only endpoints (rpc.$.ts, auth.$.ts, health.ts, openapi.$.ts, uploads/$userId.$.ts, -helpers/resume-pdf.ts).mcp/ — Model Context Protocol server (index.ts, -helpers/{tools,resources,prompts,mcp-server-card,mcp-tool-names,tool-annotations}.ts).[.]well-known/ — OAuth/OIDC/MCP discovery documents.uploads/$userId.$.tsx — etag/security-validated file serving.templates/$.tsx — template preview/download.schema[.]json.ts — /schema.json endpoint.apps/web/src/components/:
animation/ — Motion-driven UI (comet-card, count-up, spotlight, text-mask).command-palette/ — Cmd-K UI (index.tsx, store.ts, pages/).input/ — Custom inputs (chip-input, color-picker, github-stars-button, icon-picker, rich-input, url-input).layout/ — Error/loading/not-found screens, breakpoint indicator.level/, locale/, theme/, typography/ — combobox/toggle utilities for those concerns.resume/ — Builder preview surface (preview.tsx, preview.browser.tsx, preview.shared.tsx, pdf-canvas.tsx, builder-resume-draft.ts).ui/ — App-level UI extensions (combobox.tsx, copyright.tsx).user/ — User dropdown menu.apps/web/src/libs/:
auth/{client.ts,session.ts} — Better Auth browser client + SSR session getter.orpc/client.ts — Isomorphic oRPC client (in-process server + RPCLink browser).query/client.ts — TanStack Query client factory.resume/ — Section, PDF, and ordering helpers (make-section-item.ts, move-item.ts, section-actions.ts, section-title.ts, section-title-locale.ts, pdf-document.tsx, pdf-document.server.tsx, section.tsx).theme.ts, locale.ts, pwa.ts, error-message.ts, tanstack-form.tsx — direct app utilities.apps/web/src/dialogs/:
manager.tsx (renders open dialogs), store.ts (Zustand store), then domain dialogs under auth/, resume/{sections,template,index.tsx,import.tsx}, api-key/.apps/web/src/hooks/:
packages/ui/src/hooks/.)use-confirm.tsx, use-controlled-state.tsx, use-form-blocker.tsx, use-mobile.tsx, use-prompt.tsx, use-sync-form-values.ts.apps/web/plugins/:
1.migrate.ts (runs Drizzle migrations on boot), 2.storage.ts (validates the local data dir when S3 isn't configured).apps/web/public/:
templates/{jpg,pdf}/ (per-template previews), screenshots/ (PWA + marketing), opengraph/, icon/, logo/, fonts/, photos/, sounds/, videos/. Top-level files include favicon.{ico,svg}, apple-touch-icon-180x180.png, manifest.webmanifest, pwa-{64,192,512}x.png, maskable-icon-512x512.png, robots.txt, sitemap.xml, funding.json.apps/web/locales/:
.po translation catalogs (~40 languages, en-US is source).crowdin.yml and pnpm lingui:extract.packages/ai/:
packages/ai/src/prompts/{chat,analyze-resume,docx-parser,pdf-parser}-*.md, packages/ai/src/store.ts, packages/ai/src/tools/{patch-resume,patch-proposal}.ts, packages/ai/src/resume/{extraction-template,sanitize}.ts.packages/api/:
packages/api/src/context.ts (auth context + publicProcedure/protectedProcedure), packages/api/src/routers/{ai,auth,flags,resume,statistics,storage,index}.ts, packages/api/src/services/{resume,resume-events,storage,ai,auth,flags,statistics}.ts, packages/api/src/dto/resume.ts, packages/api/src/helpers/{resume-access,resume-access-policy}.ts, packages/api/src/middleware/rate-limit/index.ts.packages/auth/:
packages/auth/src/config.ts (Drizzle adapter, OAuth/passkey/2FA/api-key/JWT/MCP OAuth provider), packages/auth/src/functions.ts, packages/auth/src/types.ts.packages/config/:
packages/config/tsconfig.base.json, packages/config/vitest.config.ts.packages/db/:
migrations/ (packages/db/drizzle.config.ts → out: "../../migrations").packages/db/src/client.ts (singleton pg.Pool + drizzle() on globalThis), packages/db/src/schema/{auth,resume,index}.ts, packages/db/src/relations.ts.packages/email/:
packages/email/src/transport.ts, packages/email/src/templates/{auth,reset-password,verify-email,verify-email-change}.tsx.packages/env/:
.env for app/server callers (drizzle-kit must export DATABASE_URL manually).packages/env/src/server.ts.packages/fonts/:
packages/fonts/src/index.ts, packages/fonts/src/webfontlist.json (regenerated by packages/scripts/fonts/generate.ts).packages/import/:
ResumeData schema.packages/import/src/{json-resume,reactive-resume-json,reactive-resume-v4-json}.tsx.packages/pdf/:
packages/pdf/src/document.tsx, packages/pdf/src/context.tsx, packages/pdf/src/section-title.ts, packages/pdf/src/hooks/use-register-fonts.ts, packages/pdf/src/templates/index.ts, packages/pdf/src/templates/<name>/<Name>Page.tsx (14 templates: azurill, bronzor, chikorita, ditgar, ditto, gengar, glalie, kakuna, lapras, leafish, meowth, onyx, pikachu, rhyhorn, scizor), shared primitives under packages/pdf/src/templates/shared/ (filtering.ts, rich-text.tsx, sections.tsx, primitives.tsx, picture.ts, page-size.ts, columns.ts, metrics.ts, meta-line.tsx, contact.ts, contact-item.tsx, level-display.tsx, section-links.ts, rich-text-html.ts, rich-text-spacing.ts, styles.ts, types.ts, context.tsx).packages/runtime-externals/:
bcrypt, sharp, @aws-sdk/client-s3 so they stay runtime-only (Vite externalizes them in apps/web/vite.config.ts:55).src/ — package.json is the entire surface.packages/schema/:
packages/schema/src/resume/{data,default,sample,analysis}.ts, packages/schema/src/templates.ts, packages/schema/src/page.ts, packages/schema/src/icons.ts.packages/scripts/:
packages/scripts/database/reset.ts (pnpm --filter @reactive-resume/scripts db:reset), packages/scripts/fonts/generate.ts (pnpm --filter @reactive-resume/scripts fonts:generate).packages/ui/:
packages/ui/src/components/{dialog,dropdown-menu,command,resizable,form,tooltip,sonner,…}.tsx, packages/ui/src/hooks/{use-confirm,use-controlled-state,use-mobile,use-prompt}.tsx, packages/ui/src/styles/globals.css.packages/utils/:
packages/utils/src/{color,date,field,file,html,level,locale,network-icons,rate-limit,sanitize,string,style,url}.ts, Node-only packages/utils/src/{monorepo.node,url-security.node}.ts, and packages/utils/src/resume/{docx/index.ts,patch.ts}.migrations/:
YYYYMMDDhhmmss_<adjective>_<noun>/ contains migration.sql (the SQL Drizzle will apply) and snapshot.json (Drizzle's internal state).apps/web/plugins/1.migrate.ts on every server boot, and manually by pnpm db:migrate. Generated by pnpm db:generate (which writes here because of out: "../../migrations" in packages/db/drizzle.config.ts). Do not hand-edit migration.sql/snapshot.json.data/ (and apps/web/data/):
<workspace>/data is validated/created at boot by apps/web/plugins/2.storage.ts. Override with LOCAL_STORAGE_PATH (must be absolute).data/statistics/ and apps/web/data/statistics/ exist as runtime byproducts; treat as gitignored runtime state..vite-hooks/:
pre-commit runs biome check, commit-msg runs commitlint). Configured by lefthook.yml and commitlint.config.cjs..planning/:
.planning/codebase/ (this directory — analysis docs).Entry Points:
apps/web/src/server.ts — Nitro fetch entry.apps/web/src/router.tsx — Router factory.apps/web/src/routes/__root.tsx — Root route + global providers.apps/web/plugins/1.migrate.ts — Migration on boot.apps/web/plugins/2.storage.ts — Local storage validation on boot.Configuration:
apps/web/vite.config.ts — Vite + TanStack Start + Nitro + PWA + Lingui.turbo.json — Pipeline + globalEnv allowlist.pnpm-workspace.yaml — Workspaces (apps/* + packages/*).biome.json — Lint + format rules.knip.json — Dead-code analysis config.lefthook.yml — Git hooks.packages/db/drizzle.config.ts — Drizzle migration config (writes to ../../migrations).packages/env/src/server.ts — Server env schema + .env loader.apps/web/lingui.config.ts — i18n extract config.compose.dev.yml / compose.yml — Postgres + SeaweedFS (dev) and prod-shaped compose.Core API:
packages/api/src/routers/index.ts — Router root.packages/api/src/context.ts — Auth resolver + procedure factories.packages/api/src/services/resume.ts — Resume CRUD/patch/lock/password/duplication.packages/api/src/services/storage.ts — S3 + local FS storage abstraction.packages/api/src/helpers/resume-access-policy.ts — Visibility/redaction policy.Core data:
packages/db/src/schema/resume.ts — Resume, statistics, analysis tables.packages/db/src/schema/auth.ts — Better Auth tables.packages/db/src/client.ts — Singleton pg.Pool + drizzle() client.packages/schema/src/resume/data.ts — Canonical Zod schema for ResumeData.packages/schema/src/templates.ts — Enum of template names.Core PDF:
packages/pdf/src/document.tsx — ResumeDocument root component.packages/pdf/src/templates/index.ts — Template registry.packages/pdf/src/hooks/use-register-fonts.ts — Font registration + CJK fallbacks.packages/pdf/src/templates/shared/filtering.ts — Shared section filtering.Auth:
packages/auth/src/config.ts — Better Auth instance.apps/web/src/routes/api/auth.$.ts — /api/auth/* handler with OAuth sanitization.apps/web/src/libs/auth/{client.ts,session.ts} — Browser client + SSR session helper.Testing:
vitest.shared.ts, vitest.setup.ts — Repo-wide setup (Testing Library, jest-dom, happy-dom env).packages/config/vitest.config.ts — Reusable Vitest base.apps/web/vitest.config.ts — Web app Vitest config.Files:
kebab-case.ts / kebab-case.tsx (e.g. resume-access-policy.ts, command-palette.tsx).Button exported from packages/ui/src/components/button.tsx).packages/pdf/src/templates/azurill/AzurillPage.tsx (kept that way because the directory name doubles as the template enum value).foo.ts ↔ foo.test.ts, foo.tsx ↔ foo.test.tsx..browser.tsx suffix (e.g. apps/web/src/components/resume/preview.browser.tsx)..server.tsx suffix when they live next to browser counterparts (e.g. apps/web/src/libs/resume/pdf-document.server.tsx)..gen.ts (e.g. apps/web/src/routeTree.gen.ts)..node.ts suffix (e.g. packages/utils/src/monorepo.node.ts, packages/utils/src/url-security.node.ts).Routes (TanStack Router file conventions):
$param.tsx — dynamic path segment (e.g. apps/web/src/routes/$username/$slug.tsx).$.tsx — splat (matches the rest of the path; used for api/rpc/$, api/auth/$, [.]well-known/$)._layout/ (underscore prefix) — pathless layout group (e.g. apps/web/src/routes/_home/).-folder/ (dash prefix) — colocated, non-route helpers (-components/, -sidebar/, -store/, -sections/, -helpers/). The router ignores these.[.]well-known — escaped folder name for paths starting with a dot.name[.]json.ts — escaped dot in a route filename (used for /schema.json).route.tsx — layout/wrapper route at a directory level; index.tsx — index route inside it.Directories:
apps/<app> and packages/<name> — kebab-case workspace members.packages/<name>/src/<subdomain>/<file>.ts — every public path in package.json exports points into src/.Package names: All workspace packages are scoped under @reactive-resume/* (api, auth, db, ui, etc.). The web app is just web.
New oRPC procedure:
packages/api/src/routers/<domain>.ts, register it on the exported router map, and add corresponding business logic to packages/api/src/services/<domain>.ts.protectedProcedure from packages/api/src/context.ts.resumeMutationRateLimit from packages/api/src/middleware/rate-limit/index.ts.packages/api/src/dto/<domain>.ts when reused.New database table or column:
packages/db/src/schema/<file>.ts, update packages/db/src/relations.ts if needed, re-export from packages/db/src/schema/index.ts.DATABASE_URL=... pnpm db:generate to write a migration directory under migrations/.DATABASE_URL=... pnpm db:migrate (or just start the app — apps/web/plugins/1.migrate.ts runs them on boot).New resume field or section:
packages/schema/src/resume/data.ts (and default.ts / sample.ts if applicable).packages/api/src/dto/resume.ts), importers (packages/import/src/*.tsx), PDF templates and shared primitives (packages/pdf/src/templates/...), and the builder forms under apps/web/src/routes/builder/$resumeId/-sidebar/.New resume template:
packages/schema/src/templates.ts.packages/pdf/src/templates/<name>/<Name>Page.tsx and register it in packages/pdf/src/templates/index.ts.apps/web/public/templates/jpg/<name>.jpg and apps/web/public/templates/pdf/<name>.pdf.New web route:
apps/web/src/routes/.... Use $param.tsx for dynamic segments, _layout/ for pathless groups, and -folder/ for colocated helpers.apps/web/src/routeTree.gen.ts — do not hand-edit.ssr: false (see apps/web/src/routes/builder/$resumeId/index.tsx) or ssr: "data-only" (see apps/web/src/routes/$username/$slug.tsx).New server-only HTTP endpoint:
apps/web/src/routes/api/) and export Route = createFileRoute(...)({ server: { handlers: { GET/POST/ANY: handler } } }).@reactive-resume/db/client, @reactive-resume/api/services/*) only inside the handler module so they stay out of the client bundle.New shared component:
apps/web/src/components/<group>/<name>.tsx (with colocated test if behaviour is non-trivial).packages/ui/src/components/<name>.tsx (export via deep path @reactive-resume/ui/components/<name>).New shared utility:
packages/utils/src/<topic>.ts and an explicit "./<topic>": "./src/<topic>.ts" entry in packages/utils/package.json exports. Never import private files across packages.New dialog:
apps/web/src/dialogs/<group>/<name>.tsx.apps/web/src/dialogs/manager.tsx renders it.New AI prompt or tool:
packages/ai/src/prompts/<name>.md (re-exported via packages/ai/src/prompts.ts).packages/ai/src/tools/<name>.ts and are exposed through the AI router in packages/api/src/routers/ai.ts.New MCP tool/resource/prompt:
apps/web/src/routes/mcp/-helpers/{tools,resources,prompts}.ts; reuse existing oRPC services for actual logic.apps/web/src/routes/:
apps/web/src/routeTree.gen.ts.migrations/:
pnpm db:generate).apps/web/.output/:
pnpm build).apps/web/locales/:
.po catalogs.en-US.po is the source; other locales are synced via Crowdin (crowdin.yml).data/ and apps/web/data/:
<workspace>/data).apps/web/plugins/2.storage.ts or service calls)..turbo/, .pnpm-store/, node_modules/:
.vite-hooks/:
commit-msg, pre-commit).lefthook.yml.packages/runtime-externals/:
bcrypt, sharp, @aws-sdk/client-s3 as runtime dependencies that Vite externalizes.apps/web/src/routeTree.gen.ts — regenerated by TanStack Router tooling.migrations/<timestamp>_<name>/migration.sql and snapshot.json — generated by drizzle-kit. Add a new migration via pnpm db:generate instead of editing past ones.pnpm-lock.yaml — managed by pnpm. Update via pnpm install.apps/web/.output/, apps/web/coverage/, packages/*/coverage/, packages/*/reports/ — build/test artifacts.apps/web/locales/*.po (except en-US.po) — synced from Crowdin per crowdin.yml.apps/web/public/screenshots/, apps/web/public/opengraph/, apps/web/public/templates/{jpg,pdf}/ — regenerated assets; replace files wholesale rather than diff-editing.Structure analysis: 2026-05-11