6.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project
Compass (compassmeet.com) is a transparent platform for forming deep, authentic 1-on-1 connections. It is a Yarn-workspaces monorepo: Next.js + React web frontend, Express API backend, Capacitor Android app, and shared TypeScript packages. Backed by Supabase (PostgreSQL), Firebase (auth + media storage), hosted on Vercel (web) and Google Cloud (API).
Commands
Run from the repo root unless noted. Package manager is yarn (yarn install --frozen-lockfile).
yarn dev # Run web + API against the shared remote dev DB (visit http://localhost:3000)
yarn dev:isolated # Run with local Supabase + Firebase emulators (needs Docker, Supabase CLI, Java 21+, Firebase CLI)
yarn lint # ESLint across web, common, backend/{api,shared,email}
yarn lint-fix # ESLint --fix across the same
yarn typecheck # tsc --noEmit across all packages
yarn prettier # Format the repo
yarn test # Jest unit + integration across all workspaces
yarn test:coverage # Jest with coverage
yarn test:e2e # Playwright E2E (spins up services)
yarn test:e2e:dev # Playwright against an already-running dev server
Per-package and single tests (the workspace test script already passes --config, so append Jest args):
yarn --cwd=common test # one workspace
yarn --cwd=backend/api test path/to/file.unit.test.ts # one file
yarn --cwd=web test -t "renders profile card" # by test name
Database migrations and types:
./scripts/migrate.sh supabase/migrations/<file>.sql # apply a migration
yarn --cwd=backend/api regen-types-dev # regenerate Supabase types (dev) into common/src/supabase/schema.ts
Architecture
Workspaces and import boundaries (enforced by convention, important)
/web— Next.js/React/Tailwind frontend (pages/,components/,hooks/,lib/)./backend/api— Express REST + WebSocket server. Handlers are one file per endpoint insrc/./backend/shared— backend-only utilities (DB init, monitoring, push). Anything in/backendmay import fromshared, not vice versa./backend/email— React-email templates and send routines./common— types (User, Profile, etc.) and pure utilities shared by frontend and backend./weband/backendimport from/common, never the reverse. Avoid adding package dependencies here./supabase— active Postgres migrations (migrations/),config.toml,seed.sqlfor local/isolated dev./backend/supabase— per-table SQL definitions and themake regen-typestargets./android— Capacitor wrapper around the web build.
Request flow: React component → useAPIGetter/api() → HTTP → Express handler (auth middleware → handler) → Postgres →
response → React state.
Adding an API endpoint (3 steps, spans packages)
- Define the endpoint schema (method,
authed,propsas a Zod object,returns) incommon/src/api/schema.ts. - Implement the handler
export const x: APIHandler<'endpoint-name'>in its own file underbackend/api/src/. - Register it in the
handlersmap inbackend/api/src/app.ts(around line 583).
Database access — two clients
- Backend (
createSupabaseDirectClient()fromshared/supabase/init): raw SQL via pg-promise (pg.oneOrNone,pg.manyOrNone). Web cannot do this. - Frontend (
dbfromweb/lib/supabase/db): the Supabase JS client (db.from('table').select(...)), a PostgREST wrapper. - Never string-concatenate SQL. Use the helpers in
shared/supabase/utils(insert,update,updateData,bulkUpsert, ...) or compose withrenderSql/select/from/wherefromshared/supabase/sql-builder.ts. SQL is written lowercase by convention.
Frontend conventions
- Many small composable components over large ones. Export the main component at the top of the file; name it after the
file (
profile-card.tsx→ProfileCard). - Client data fetching:
useAPIGetter('endpoint', props)(returns{data, refresh}, cached in memory). Server-side:api('endpoint', props)insidegetStaticProps/getServerSideProps. - Prefer
usePersistentInMemoryState/usePersistentLocalStateoveruseStatewhen navigating back to a page should feel instant. - Live updates use WebSockets via
useApiSubscription(topics broadcast frombackend/shared/src/websockets/helpers.ts). - Prefer lodash (
keyBy,uniq,uniqBy) over hand-rolled loops/Sets.
Internationalization
const t = useT() (from web/lib/locale), then t('key', 'English fallback'). Translation JSON lives in
common/messages/ (de.json, fr.json; English is the inline fallback). To add a language see docs/development.md
and the LOCALES dict in common/src/constants.ts.
Timestamps
Use Date everywhere in TypeScript; TIMESTAMPTZ in Postgres (pg-promise converts automatically). The Zod endpoint
schema handles Date↔string serialization across the wire. When persisting to localStorage, convert string back to Date
on load.
Conventions to follow
- Don't add
sleep()delays for "eventual consistency" — rely on transactional integrity (e.g. user + profile + options are created in one transaction increate-user-and-profile.ts). - Don't split into multiple API calls when data can be batched in one transaction; fetch profile options.
- Don't use
console.log— usedebug()fromcommon/logger. - Scripts in
/backend/scriptsrun insiderunScript(async ({pg}) => ...)which loads secrets intoprocess.env. Anything that mutates backend state or schema should generally be run by the user, not Claude.
Detailed docs
docs/knowledge.md (architecture + code patterns), docs/internationalization.md (adding languages),
docs/profile_fields.md (adding profile fields), docs/TESTING.md (test layout and practices),
docs/DATABASE_SCHEMA.md, docs/PERFORMANCE_OPTIMIZATION.md, docs/DATABASE_CONNECTION_POOLING.md,
docs/TROUBLESHOOTING.md, docs/Next.js.md. Per-area READMEs in web/, backend/api/, backend/email/.