# Contributing ## Prerequisites - **Node.js** 24 LTS (`node --version` should print `v24.x.x`) - **pnpm** 10 (`pnpm --version` should print `10.x.x`) - **Docker** with the Compose plugin (used for the local Postgres + MinIO containers) ## Setup ```bash git clone https://github.com/ginnoir/famapp.git cd famapp pnpm install cp .env.example .env # Edit .env — minimum required for local dev: # NEXT_PUBLIC_APP_URL=http://127.0.0.1:3000 # DATABASE_URL=postgres://famapp:famapp@localhost:5432/famapp # ENABLE_DEV_LOGIN=true # AUTH_SECRET= ``` Start everything (database + migrations + seed + dev server): ```bash pnpm dev:local ``` Then open `http://localhost:3000/login` and click **Dev login**. See `docs/dev-login.md` for the full local setup including HTTPS/PWA testing. ## Available scripts | Command | Description | | --------------------- | ----------------------------------------------------------------------------------------- | | `pnpm dev` | Next.js dev server (localhost only) | | `pnpm dev:network` | Dev server bound to `0.0.0.0` (LAN access for phone testing) | | `pnpm dev:local` | Full local stack — starts DB container, runs migrations, seeds, launches dev server | | `pnpm dev:reset` | Tear down local DB and start fresh (destructive — deletes all local data) | | `pnpm build` | Production Next.js build | | `pnpm start` | Start the production build locally | | `pnpm lint` | ESLint check | | `pnpm lint:fix` | ESLint with auto-fix | | `pnpm format` | Prettier — format all files | | `pnpm format:check` | Prettier — check only (used in CI) | | `pnpm typecheck` | TypeScript type check (`tsc --noEmit`) | | `pnpm test:e2e` | Playwright end-to-end tests | | `pnpm db:generate` | Generate a new Drizzle migration from schema changes | | `pnpm db:migrate` | Apply pending Drizzle migrations | | `pnpm db:seed` | Seed the database with dev fixtures | | `pnpm db:studio` | Open Drizzle Studio (local DB browser) | | `pnpm gen:icons` | Regenerate PWA icon set from source | | `pnpm vapid:generate` | Generate VAPID key pair for Web Push | | `pnpm release` | Interactive release (prompts for semver bump, tags, publishes changelog + GitHub Release) | | `pnpm release:patch` | Non-interactive patch release | | `pnpm release:minor` | Non-interactive minor release | | `pnpm release:major` | Non-interactive major release | | `pnpm release:dry` | Dry-run release — preview without writing | ## Running tests ### Type check + lint (CI equivalent) ```bash pnpm typecheck pnpm lint pnpm format:check ``` ### End-to-end tests The app must be running first (`pnpm dev:local`). Generate a Playwright auth state file, then run tests: ```powershell # Generate auth state (run once after starting the app) New-Item -ItemType Directory -Force tests\.auth | Out-Null @' const { chromium } = require('@playwright/test'); (async () => { const browser = await chromium.launch(); const page = await browser.newPage(); await page.goto('http://127.0.0.1:3000/login'); await page.getByRole('button', { name: 'Dev login' }).click(); await page.waitForURL('http://127.0.0.1:3000/'); await page.context().storageState({ path: 'tests/.auth/dev-user.json' }); await browser.close(); })(); '@ | node - # Run tests $env:PLAYWRIGHT_STORAGE_STATE='tests/.auth/dev-user.json' pnpm test:e2e ``` ### Writing tests - Unit tests: Vitest under `tests/unit/` — only where it pays off (utilities, pure logic). - E2E tests: Playwright under `tests/e2e/` — one happy-path test per module. Do not write brittle selector-heavy tests for trivial CRUD. ## Code style - **TypeScript strict** — no `any` without a written reason. - **No comments** unless the _why_ is non-obvious. Names carry intent. - **Immutable** — always return new objects; never mutate in place. - **Module isolation** — a module imports from `_core` and `lib/` only; never from a sibling module. - **File size** — 200–400 lines typical, 800 hard cap. Formatting and lint run automatically on staged files via `lint-staged` at commit time. You can also run them manually with `pnpm lint:fix` and `pnpm format`. ## Commit format Commits must follow [Conventional Commits](https://www.conventionalcommits.org/). `commitlint` enforces this at the `commit-msg` hook. ``` : [optional body] ``` Allowed types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`, `revert` Examples: ``` feat: add plant species search to garden module fix: calendar event end time off by one day chore: bump next to 15.6 ``` ## Adding a module Every feature lives under `src/modules//`. A new module needs: - `schema.ts` — Drizzle tables - `server/` — server actions and queries - `components/` — React components - `manifest.ts` — registers the module with the core registry (nav, entity types, dashboard widget, quick-add actions) See `CLAUDE.md` for the full architectural brief. The module loader in `src/modules/_core/` discovers manifests automatically — no changes to core code required for a new module. ## Schema changes 1. Edit the relevant `schema.ts`. 2. Run `pnpm db:generate` to create a new migration file under `drizzle/`. 3. Commit the migration alongside the schema change. 4. Never edit a shipped migration — always add a new one. ## PR checklist - [ ] `pnpm typecheck` passes - [ ] `pnpm lint` passes - [ ] `pnpm build` succeeds - [ ] New Drizzle migration committed if schema changed - [ ] E2E test added or updated if a user-visible flow changed - [ ] `docs/tasks/09-pre-deploy-checklist.md` reviewed if touching auth or env