mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-05-29 11:18:46 -04:00
Update agent docs and add Claude docs
This commit is contained in:
@@ -9,7 +9,7 @@ alwaysApply: true
|
||||
- express node api server `/backend/api`
|
||||
- one off scripts, like migrations `/backend/scripts`
|
||||
- supabase postgres. schema in `/backend/supabase`
|
||||
- supabase-generated types in `/backend/supabase/schema.ts`
|
||||
- supabase-generated types in `/common/src/supabase/schema.ts`
|
||||
- files shared between backend directories `/backend/shared`
|
||||
- anything in `/backend` can import from `shared`, but not vice versa
|
||||
- files shared between the frontend and backend in `/common`
|
||||
@@ -313,7 +313,7 @@ export const placeBet: APIHandler<'bet'> = async (props, auth) => {
|
||||
}
|
||||
```
|
||||
|
||||
And finally, you need to register the handler in `backend/api/src/routes.ts`
|
||||
And finally, you need to register the handler in the `handlers` map in `backend/api/src/app.ts`
|
||||
|
||||
```ts
|
||||
import { placeBet } from './place-bet'
|
||||
|
||||
2
.github/copilot-instructions.md
vendored
2
.github/copilot-instructions.md
vendored
@@ -14,7 +14,7 @@ Compass (compassmeet.com) is a transparent dating platform for forming deep, aut
|
||||
- **Shared backend utilities** `/backend/shared`
|
||||
- **Email functions** `/backend/email`
|
||||
- **Database schema** `/backend/supabase`
|
||||
- Supabase-generated types in `/backend/supabase/schema.ts`
|
||||
- Supabase-generated types in `/common/src/supabase/schema.ts`
|
||||
- **Files shared between frontend and backend** `/common`
|
||||
- Types (User, Profile, etc.) and utilities
|
||||
- Try not to add package dependencies to common
|
||||
|
||||
116
CLAUDE.md
Normal file
116
CLAUDE.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# 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`).
|
||||
|
||||
```bash
|
||||
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):
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
./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 in `src/`.
|
||||
- `/backend/shared` — backend-only utilities (DB init, monitoring, push). Anything in `/backend` may import from
|
||||
`shared`, **not vice versa**.
|
||||
- `/backend/email` — React-email templates and send routines.
|
||||
- `/common` — types (User, Profile, etc.) and pure utilities shared by frontend and backend. `/web` and `/backend`
|
||||
import from `/common`, **never the reverse**. Avoid adding package dependencies here.
|
||||
- `/supabase` — active Postgres migrations (`migrations/`), `config.toml`, `seed.sql` for local/isolated dev.
|
||||
- `/backend/supabase` — per-table SQL definitions and the `make regen-types` targets.
|
||||
- `/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)
|
||||
|
||||
1. Define the endpoint schema (method, `authed`, `props` as a Zod object, `returns`) in `common/src/api/schema.ts`.
|
||||
2. Implement the handler `export const x: APIHandler<'endpoint-name'>` in its own file under `backend/api/src/`.
|
||||
3. Register it in the `handlers` map in `backend/api/src/app.ts` (around line 583).
|
||||
|
||||
### Database access — two clients
|
||||
|
||||
- **Backend** (`createSupabaseDirectClient()` from `shared/supabase/init`): raw SQL via pg-promise (`pg.oneOrNone`,
|
||||
`pg.manyOrNone`). Web cannot do this.
|
||||
- **Frontend** (`db` from `web/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 with `renderSql`/`select`/`from`/`where` from `shared/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)` inside `getStaticProps`/`getServerSideProps`.
|
||||
- Prefer `usePersistentInMemoryState` / `usePersistentLocalState` over `useState` when navigating back to a page should
|
||||
feel instant.
|
||||
- Live updates use WebSockets via `useApiSubscription` (topics broadcast from
|
||||
`backend/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 in `create-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` — use `debug()` from `common/logger`.
|
||||
- Scripts in `/backend/scripts` run inside `runScript(async ({pg}) => ...)` which loads secrets into `process.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/`.
|
||||
@@ -15,57 +15,5 @@ See those other useful documents as well:
|
||||
- [PERFORMANCE_OPTIMIZATION.md](PERFORMANCE_OPTIMIZATION.md) for performance best practices
|
||||
- [DATABASE_CONNECTION_POOLING.md](DATABASE_CONNECTION_POOLING.md) for database connection management
|
||||
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) for resolving common development issues
|
||||
|
||||
### Adding a new profile field
|
||||
|
||||
A profile field is any variable associated with a user profile, such as age, politics, diet, etc. You may want to add a
|
||||
new profile field if it helps people find better matches.
|
||||
|
||||
To do so, you can add code in a similar way as
|
||||
in [this commit](https://github.com/CompassConnections/Compass/commit/940c1f5692f63bf72ddccd4ec3b00b1443801682) for the
|
||||
`religion` field. If you also want people to filter by that profile field, you'll also need to add it to the search
|
||||
filters, as done
|
||||
in [this commit](https://github.com/CompassConnections/Compass/commit/a4bb184e95553184a4c8773d7896e4b570508fe5) (for the
|
||||
`religion` field as well).
|
||||
|
||||
Note that you will also need to add a column to the `profiles` table in the dev database before running the code; you
|
||||
can do so via this SQL command (change the type if not `TEXT`):
|
||||
|
||||
```sql
|
||||
ALTER TABLE profiles
|
||||
ADD COLUMN profile_field TEXT;
|
||||
```
|
||||
|
||||
Store it in `add_profile_field.sql` in the [migrations](../backend/supabase/migrations) folder and
|
||||
run [migrate.sh](../scripts/migrate.sh) from the root folder:
|
||||
|
||||
```bash
|
||||
./scripts/migrate.sh backend/supabase/migrations/add_profile_field.sql
|
||||
```
|
||||
|
||||
Then sync the database types from supabase to the local files (which assist Typescript in typing):
|
||||
|
||||
```bash
|
||||
yarn regen-types dev
|
||||
```
|
||||
|
||||
That's it!
|
||||
|
||||
### Adding a new language
|
||||
|
||||
Adding a new language is very easy, especially with translating tools like large language models (ChatGPT, etc.) which
|
||||
you can use as first draft.
|
||||
|
||||
- Add the language to the LOCALES dictionary in [constants.ts](../common/src/constants.ts) (the key is the locale code,
|
||||
the value is the original language name (not in English)).
|
||||
- Duplicate [fr.json](../web/messages/fr.json) and rename it to the locale code (e.g., `de.json` for German). Translate
|
||||
all the strings in the new file (keep the keys identical). LLMs like ChatGPT may not be able to translate the whole
|
||||
file in one go; try to copy-paste by batch of 300 lines and ask the LLM to
|
||||
`translate the values of the json above to <new language> (keep the keys unchanged)`. In order to fit the bottom
|
||||
navigation bar on mobile, make sure the values for those keys are less than 10 characters: "nav.home", "
|
||||
nav.messages", "nav.more", "nav.notifs", "nav.people".
|
||||
- Duplicate the [fr](../web/public/md/fr) folder and rename it to the locale code (e.g., `de` for German). Translate all
|
||||
the markdown files in the new folder. To do so, you can copy-paste each file into an LLM and ask it to
|
||||
`translate the markdown above to <new language>`.
|
||||
|
||||
That's all, no code needed!
|
||||
- [profile_fields.md](profile_fields.md) for adding new profile fields
|
||||
- [internationalization.md](internationalization.md) for adding new languages
|
||||
|
||||
20
docs/internationalization.md
Normal file
20
docs/internationalization.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Adding a new language
|
||||
|
||||
Adding a new language is very easy, especially with translating tools like large language models (ChatGPT, Claude, etc.)
|
||||
which
|
||||
you can use as first draft.
|
||||
|
||||
- Add the language to the LOCALES dictionary in [constants.ts](../common/src/constants.ts) (the key is the locale code,
|
||||
the value is the original language name (not in English)).
|
||||
- Duplicate [fr.json](../common/messages/fr.json) and rename it to the locale code (e.g., `de.json` for German).
|
||||
Translate
|
||||
all the strings in the new file (keep the keys identical). LLMs like ChatGPT may not be able to translate the whole
|
||||
file in one go; try to copy-paste by batch of 300 lines and ask the LLM to
|
||||
`translate the values of the json above to <new language> (keep the keys unchanged)`. In order to fit the bottom
|
||||
navigation bar on mobile, make sure the values for those keys are less than 10 characters: "nav.home", "
|
||||
nav.messages", "nav.more", "nav.notifs", "nav.people".
|
||||
- Duplicate the [fr](../web/public/md/fr) folder and rename it to the locale code (e.g., `de` for German). Translate all
|
||||
the markdown files in the new folder. To do so, you can copy-paste each file into an LLM and ask it to
|
||||
`translate the markdown above to <new language>`.
|
||||
|
||||
That's all, no code needed!
|
||||
@@ -15,7 +15,7 @@
|
||||
- express node api server `/backend/api`
|
||||
- one off scripts, like migrations `/backend/scripts`
|
||||
- supabase postgres. schema in `/backend/supabase`
|
||||
- supabase-generated types in `/backend/supabase/schema.ts`
|
||||
- supabase-generated types in `/common/src/supabase/schema.ts`
|
||||
- files shared between backend directories `/backend/shared`
|
||||
- anything in `/backend` can import from `shared`, but not vice versa
|
||||
- files shared between the frontend and backend in `/common`
|
||||
@@ -333,7 +333,7 @@ export const placeBet: APIHandler<'bet'> = async (props, auth) => {
|
||||
}
|
||||
```
|
||||
|
||||
And finally, you need to register the handler in `backend/api/src/routes.ts`
|
||||
And finally, you need to register the handler in the `handlers` map in `backend/api/src/app.ts`
|
||||
|
||||
```ts
|
||||
import {placeBet} from './place-bet'
|
||||
@@ -454,7 +454,7 @@ const t = useT()
|
||||
t('common.key', 'English translations')
|
||||
```
|
||||
|
||||
Translations should go to the JSON files in `web/messages` (`de.json` and `fr.json`, as of now).
|
||||
Translations should go to the JSON files in `common/messages` (`de.json` and `fr.json`, as of now; English is the inline fallback passed to `t()`).
|
||||
|
||||
### Misc coding tips
|
||||
|
||||
|
||||
49
docs/profile_fields.md
Normal file
49
docs/profile_fields.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Adding a new profile field
|
||||
|
||||
A profile field is any variable associated with a user profile, such as age, politics, diet, etc. You may want to add a
|
||||
new profile field if it helps people find better matches.
|
||||
|
||||
To do so, you add code here:
|
||||
|
||||
- common/src/supabase/schema.ts
|
||||
- web/components/filters/choices.tsx (if multi choices)
|
||||
- web/components/optional-profile-form.tsx
|
||||
- web/components/profile-about.tsx
|
||||
- backend/api/src/get-profiles.ts
|
||||
- common/src/api/schema.ts ('get-profiles' props)
|
||||
- common/src/api/zod-types.ts (optionalProfilesSchema)
|
||||
- web/components/filters/filters.tsx
|
||||
- common/src/filters.ts
|
||||
- web/components/filters/use-filters.ts (yourFilters and isYourFilters)
|
||||
|
||||
Note that you will also need to add a column to the `profiles` table; you
|
||||
can do so via this SQL command (change the type and index if not `TEXT`):
|
||||
|
||||
```sql
|
||||
ALTER TABLE profiles
|
||||
ADD COLUMN profile_field TEXT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_profiles_profile_field ON profiles USING btree (mbti);
|
||||
```
|
||||
|
||||
Store it in `add_<profile_field>.sql` in the [migrations](../backend/supabase/migrations) folder and
|
||||
run [migrate.sh](../scripts/migrate.sh) from the root folder:
|
||||
|
||||
```bash
|
||||
./scripts/migrate.sh backend/supabase/migrations/add_<profile_field>.sql
|
||||
```
|
||||
|
||||
Optionally, if you use the remote dev DB, run the SQL above in the dev DB and sync the database types from supabase to
|
||||
the local files (which assist Typescript in typing):
|
||||
|
||||
```bash
|
||||
yarn --cwd=backend/api regen-types-dev
|
||||
```
|
||||
|
||||
If you use your local DB, load the new schema with:
|
||||
|
||||
```bash
|
||||
yarn test:db:reset
|
||||
```
|
||||
|
||||
That's it!
|
||||
Reference in New Issue
Block a user