Development Guide
For ongoing code health reviews, use the Maintainability Checklist. For ownership rules across the current workspace layout, use Architecture Boundaries. For docs ownership, navigation, and issue hygiene, use Documentation Governance.
Prerequisites
- Node.js 24+ and npm (match the version required by the
package.jsonenginesfield) - Git
- VS Code (recommended)
Development Scripts
Testing
npm run test- Run all tests using Vitestnpm run test:server- Run utility function tests (Node.js environment)npm run test:storybook- Run Storybook component tests (browser environment)
Code Quality
npm run lintornpm run lint:check- Run ESLint for code lintingnpm run lint:fix- Fix ESLint issues automaticallynpm run format:check- Check code formatting with Prettiernpm run format:write- Fix code formatting with Prettiernpm run format:package- Sort and format package.jsonnpm run type-check- Run TypeScript type checkingnpm run fix- Run all fixes (lint, format, package.json)
Development
npm run dev- Start development servernpm run dev:docs- Start the Fumadocs documentation site onhttp://localhost:3003npm run dev:backoffice- Start the operator catalog-health UI onhttp://localhost:3000npm run build- Build production applicationnpm run build:docs- Build the Fumadocs documentation sitenpm run build:backoffice- Build the operator backoffice appnpm run start --workspace=apps/web- Start production servernpm run start --workspace=apps/docs- Start the production documentation server afternpm run build:docsnpm run copy:env- Copy the root.envintoapps/*,services/*, andpackages/sharednpm run migrate:db- Apply all idempotent SQL migrations fromdb/initnpm run storybook- Start Storybook development servernpm run build-storybook- Build Storybook for production
Workspace-local scripts used during app development:
cd apps/web && npm run start:workers- Start BullMQ workers for async recommendationscd apps/web && npm run bull-board- Open the BullMQ dashboard locally (OPERATOR_AUTH_USERNAMEandOPERATOR_AUTH_PASSWORDenable the same login used in production)npm run catalog:discovery:enqueue- Queue TMDB discovery pages for the rate-limited catalog workernpm run catalog:backfill:enqueue- Queue existing movies for the rate-limited catalog backfill worker
Database & Data Management
npm run setup:local-db- Start local PostgreSQL + Redis via Docker and write credentials to.envnpm run populate-db- Seed the database with movie embeddings via themovie-seedservicenpm run cleanup:local-db- Stop containers and remove the database volume (full reset)npm run analyze-movies- Analyze movie data chunks for embedding optimizationnpm run calibrate-similarity- Recalibrate recommendation similarity thresholds against the live DB
Database Setup Workflow
The project includes scripts to help you set up and manage your movie recommendation database. All commands run from the repo root:
- Start the DB - Run
npm run setup:local-dbto spin up PostgreSQL + Redis and initialize credentials - Sync env files - Run
npm run copy:envso the web app and services use the updated root.env - Seed the DB - Run
npm run populate-dbto import movies (viaservices/movie-seed) into PostgreSQL - Run the app - Start
npm run devfrom the repo root andcd apps/web && npm run start:workersin a second terminal - Reset - Run
npm run cleanup:local-dbto wipe everything, then repeat from step 1
The movie-seed service reads its movie list from services/movie-seed/movies.txt.
Schema and Metadata Notes
Features that depend on movie metadata need both schema and data to be present. For example, movie-memory cards use poster URLs, localized names, TMDB ids, and movie identity keys. When adding fields like these:
- add the column to the schema initialization path
- add an idempotent migration or
ALTER TABLE ... ADD COLUMN IF NOT EXISTSpath for existing local, preview, and production databases - update seed/backfill code so existing rows eventually receive the new data
- make the UI degrade gracefully while the backfill is incomplete
- document whether an existing Coolify preview volume needs to be recreated
If a preview deployment has a long-lived PostgreSQL volume, new code may run against an older schema until migrations run or the preview is recreated.
Local Runbook
For a full local environment that matches the async recommendation flow, use this sequence from the repo root:
cp .env.example .env
npm install
npm run setup:local-db
npm run copy:env
npm run populate-dbThen run the app in separate terminals:
# terminal 1
npm run dev
# terminal 2
cd apps/web
npm run start:workers
# optional terminal 3
cd apps/web
npm run bull-boardIf you change the root .env, re-run npm run copy:env before restarting the app or workers.
Mock Data Fallback
When DATABASE_URL is not set (or the database client is not configured), the /api/movies route automatically returns generated mock data instead of querying PostgreSQL. This means the Available Movies page works out of the box in local development without any database setup — you will see placeholder movies with realistic fields (name, age rating, duration, score, year).
To switch to real data, add DATABASE_URL to your .env file and seed the database as described above.
Code Style and Conventions
- TypeScript - All new code should be TypeScript
- ESLint - Follows Next.js and Prettier configurations
- Prettier - Code formatting is enforced
- Import organization - Auto-sorted with eslint-plugin-import
Testing Strategy
- Unit tests - Vitest for utility functions and business logic
- Component tests - Storybook with Vitest integration
- Browser tests - Playwright for end-to-end scenarios
End-to-End Database Harness
Run the deterministic e2e smoke suite from the repo root:
npm run test:e2eThe command prepares an isolated environment:
docker-compose.e2e.ymlstarts PostgreSQL with pgvector on127.0.0.1:55432and Redis on127.0.0.1:56379.scripts/e2e/setup-db.mjsresets the e2e compose project, applies everydb/init/*.sqlmigration throughapps/web/scripts/migrate-db.js, and seeds deterministic movie fixtures.apps/web/playwright.e2e.config.tsstarts the app onhttp://127.0.0.1:3100withDATABASE_URLandREDIS_URLpointed at the isolated services.E2E_DETERMINISTIC_RECOMMENDATIONS=1makes quiz submissions complete from seeded fixtures instead of calling live OpenAI/TMDB services or requiring a worker process.
Stop and remove the e2e services with:
npm run test:e2e:downThe smoke coverage proves /api/health, Available Movies filtering and empty states, auth/session flows, solo quiz submission, result rendering, recommendation feedback, and movie-memory persistence. AI-response evals remain separate so they can add fixture scoring and optional live-model runs without slowing normal e2e.
Recommendation Eval Harness
Run the deterministic recommendation eval suite from the repo root:
npm run eval:recommendationsThe default eval path uses fixture prompts, fixture user-memory constraints, and mocked recommendation outputs. It does not call OpenAI, TMDB, Redis, or PostgreSQL. The runner writes apps/web/test-results/recommendation-evals/report.json and prints a concise pass/fail summary. Current scoring checks:
- response shape against the
ApiResponseschema - main pick and alternates stay inside the fixture candidate set
- forbidden safety terms are absent
- watched, rejected, and explicitly forbidden titles are not repeated
- explanation text is specific enough for the fixture expectations
Run live-provider evals only when intentionally checking model/provider behavior:
npm run eval:recommendations -- --liveLive evals require configured provider and database environment variables such as OPENAI_API_KEY, DATABASE_URL, and usually TMDB_API_KEY. CI blocks on the deterministic eval job; live evals are manual so normal PR checks stay cheap and predictable.
Run real-data evals when checking catalog retrieval, seed/backfill, schema, or candidate-availability changes:
npm run test:e2e:setup
DATABASE_URL=postgresql://popchoice_e2e@127.0.0.1:55432/popchoice_e2e npm run eval:recommendations:real-data
npm run test:e2e:downThe real-data mode still uses controlled recommendation outputs and does not call live AI providers. It adds catalog connectivity, candidate availability, and catalog search retrieval checks to the normal eval report.
Use three eval levels when changing AI-related code:
npm run eval:recommendationsfor every PR that changes recommendation prompts, embeddings, candidate filters, ranking, feedback signals, response shape, or eval fixtures.- A real-data eval for changes that affect catalog retrieval, schema, seeding/backfill, or candidate availability. This runs against a seeded database through
npm run eval:recommendations:real-dataor the scheduled/manual GitHub workflow; it is not a default per-PR hard gate. npm run eval:recommendations -- --liveonly when intentionally checking live model/provider behavior before larger recommendation changes. Live runs can spend API credits and depend on provider availability, so agents should call out whether they ran it or why they did not.
Project Structure
apps/
├── web/
│ └── src/
│ ├── app/ # Next.js route and page boundaries
│ ├── clients/ # Low-level infrastructure clients
│ ├── components/ # Reusable React components
│ ├── features/ # Feature-owned orchestration and domain-facing modules
│ ├── hooks/ # React hooks
│ ├── i18n/ # Locale support
│ ├── integrations/ # App-local third-party API wrappers
│ ├── lib/ # App-local infra helpers and adapters
│ ├── mocks/ # Mock handlers and test support
│ ├── styles/ # Styling and theme assets
│ └── utils/ # Reusable utility helpers
├── bull-board/ # Queue monitoring app
├── backoffice/ # Operator app for catalog health and review
packages/
└── shared/ # Shared helpers reused by root services
services/
├── movie-discovery/ # Scheduled or one-shot TMDB discovery process
├── movie-seed/ # Database seeding process
└── movie-backfill/ # Metadata backfill process
db/ # SQL scripts and DB initialization assetsBoundary Notes
apps/web/src/appshould stay focused on route/page boundaries.apps/web/src/featuresshould own cross-route product behavior such as recommendation, auth, and catalog workflows.apps/web/src/clientsshould own low-level client setup.apps/web/src/integrationsshould own app-local wrappers around third-party APIs.apps/web/src/libshould hold app-local infrastructure helpers, not become a catch-all business layer.apps/web/src/utilsshould stay narrow and reusable, not absorb end-to-end orchestration.- Root
services/*are standalone background or offline runtimes.
See BOUNDARIES.md for the full ownership definitions.