# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Important: Design Guidelines **Always read `apps/mobile/DESIGN.md` before making UI/UX changes to the mobile app.** This file contains the design system, visual language, and styling patterns that must be followed for consistency. Agents and subagents should read this file when: - Creating new screens or components - Modifying existing UI elements - Adding animations or visual effects - Working with colors, typography, or spacing ## Project Overview Sauci is a couples relationship app that helps partners communicate better and grow closer together. Users swipe on thoughtful questions (yes/no/maybe), and when both partners answer positively, a "match" is created that unlocks a chat thread for that topic. ## Commands ```bash # Install dependencies (from root) npm install # Start all apps in development npm run dev # Run specific apps cd apps/mobile && npm run dev # Expo mobile app cd apps/admin && npm run dev # React Admin dashboard # Type checking npm run typecheck # Linting npm run lint # Build all npm run build ``` ### Mobile-specific commands ```bash cd apps/mobile npm run ios # Run on iOS simulator npm run android # Run on Android emulator npm run web # Run in browser ``` ### Supabase commands ```bash supabase start # Start local Supabase supabase db reset # Reset DB and run all migrations supabase functions serve # Run edge functions locally supabase migration new # Create new migration ``` ### Supabase Environments There are two Supabase projects configured via MCP servers: | Environment | Project Ref | URL | MCP Server | |-------------|-------------|-----|------------| | **Production** | `ckjcrkjpmhqhiucifukx` | https://ckjcrkjpmhqhiucifukx.supabase.co | `sauci-prod` | | **Non-Production** | `itbzhrvlgvdmzbnhzhyx` | https://itbzhrvlgvdmzbnhzhyx.supabase.co | `sauci-non-prod` | When using Claude Code with MCP tools: - Use `mcp__sauci-prod__*` tools for production database operations - Use `mcp__sauci-non-prod__*` tools for development/staging database operations - **Always verify which environment you're targeting before running destructive operations** ### Database Migrations - CRITICAL > **STOP: Read this entire section before making ANY database schema change.** #### The Golden Rule **ALL schema changes MUST go through local migration files.** No exceptions. #### Forbidden Actions - **NEVER call `apply_migration` via MCP** (either `mcp__sauci-prod__apply_migration` or `mcp__sauci-non-prod__apply_migration`). This creates migration entries in the remote database with NO local file, which breaks `supabase db push` in CI and causes deployment failures that are painful to fix. - **NEVER run DDL statements (`CREATE`, `ALTER`, `DROP`) via `execute_sql`**. This changes the schema without any migration tracking at all. - **NEVER manually INSERT into `supabase_migrations.schema_migrations`** unless repairing a broken migration history (and only with explicit user approval). #### Why This Matters The CI/CD pipeline runs `supabase db push --include-all` which compares local migration files against the remote `schema_migrations` table. If the remote has migrations that don't exist locally, or local files exist without remote entries, the deployment **will fail** and block all subsequent deployments. **A single `apply_migration` call can break deployments for the entire project.** #### Correct Workflow for Schema Changes ```bash # 1. Create migration file locally supabase migration new # 2. Edit the migration file in apps/supabase/migrations/ # 3. Make migrations idempotent where possible # Use: CREATE OR REPLACE, IF NOT EXISTS, DROP IF EXISTS # This prevents failures if re-run # 4. Test locally supabase db reset # 5. Commit the migration file to git # 6. Deploy happens automatically via CI on push to master # OR deploy manually: supabase link --project-ref supabase db push ``` #### MCP Tools - What IS Allowed - **Reading data:** `execute_sql` with SELECT queries only - **Exploring schema:** `list_tables`, `list_migrations`, `list_extensions` - **Deploying edge functions:** `deploy_edge_function` - **Generating types:** `generate_typescript_types` - **Checking advisors:** `get_advisors` #### If You Accidentally Used `apply_migration` If a migration was applied via MCP, you must immediately: 1. Retrieve the migration SQL from `schema_migrations.statements` 2. Create a matching local file: `apps/supabase/migrations/_.sql` 3. Commit and push the file so CI can reconcile ## Architecture ### Monorepo Structure (Turborepo) - `apps/mobile` - Expo React Native app (expo-router for navigation) - `apps/admin` - React Admin dashboard for content management - `apps/supabase` - Database migrations and edge functions - `packages/shared` - Shared TypeScript types (`@sauci/shared`) ### Mobile App Architecture - **Routing**: Expo Router with file-based routing (`app/` directory) - `(auth)/` - Authentication screens - `(app)/` - Main app screens (protected) - **State Management**: Zustand stores in `src/store/index.ts` - `useAuthStore` - User, couple, partner state - `useMatchStore` - Matches and notifications - `usePacksStore` - Question packs and enabled packs per couple - `useMessageStore` - Chat messages and unread counts - `useSubscriptionStore` - RevenueCat subscription state - **Backend**: Supabase client in `src/lib/supabase.ts` - **UI Components**: Glass-morphism themed components in `src/components/ui/` ### Data Model (Core Entities) - **Profile** - User profile extending Supabase auth (has `couple_id`) - **Couple** - Two users linked via invite code - **QuestionPack** - Collection of questions (can be premium) - **Question** - Individual question with intensity level (1-5) - **Response** - User's answer (yes/no/maybe) to a question - **Match** - Created when both partners answer positively (yes_yes, yes_maybe, maybe_maybe) ### Edge Functions Edge functions in `apps/supabase/functions/` handle complex operations: - `submit-response` - Saves response, checks for matches, triggers notifications - `manage-couple` - Join/leave couple operations - `delete-relationship` - Deletes couple data (matches, messages, media) while keeping accounts - `delete-account` - Permanently deletes user account and all associated data (Apple App Store requirement) - `send-notification` - Push notifications for matches - `send-nudge-notification` - Partner nudge notifications (rate limited to once per 12 hours) - `revenuecat-webhook` - Subscription webhook handler - `sync-subscription` - Syncs RevenueCat subscription status - `redeem-code` - Redeems promotional codes for premium access (website only) #### Edge Function Deployment Guidelines When deploying edge functions via MCP tools (`mcp__sauci-*__deploy_edge_function`): **IMPORTANT: Always use `verify_jwt: false`** for all edge functions. Supabase's built-in JWT verification causes 401 errors even with valid tokens. Instead, handle authentication manually in the function code using: ```typescript const { data: { user }, error: authError } = await supabase.auth.getUser( authHeader.replace("Bearer ", "") ); ``` This approach: - Gives better error messages - Allows custom auth handling - Avoids mysterious 401 failures **Current function settings (all should be verify_jwt: false):** | Function | verify_jwt | Auth Method | |----------|------------|-------------| | `manage-couple` | false | Manual via getUser() | | `submit-response` | false | Manual via getUser() | | `sync-subscription` | false | Manual via getUser() | | `delete-relationship` | false | Manual via getUser() | | `delete-account` | false | Manual via getUser() | | `redeem-code` | false | Manual via getUser() | | `revenuecat-webhook` | false | Webhook signature validation | | `send-notification` | false | Internal trigger (no auth) | | `send-message-notification` | false | Internal trigger (no auth) | | `send-nudge-notification` | false | Manual via getUser() | ### Database Best Practices - **Always use `.maybeSingle()` instead of `.single()`** when the row might not exist. `.single()` throws an error if 0 or >1 rows are returned. - **Profile creation trigger**: The `on_auth_user_created` trigger automatically creates a profile when a user signs up. Never assume a profile exists without checking. - **Check for null profiles** in edge functions before performing operations. ### Key Patterns - Responses use UPSERT with `onConflict: "user_id,question_id"` - Match detection happens in `submit-response` edge function after each response - Real-time subscriptions used for chat messages - RevenueCat handles in-app purchases and subscription management - RLS policies enforce couple-level data isolation ### Redemption Code System Promotional codes that grant users premium access. **Codes can only be redeemed on the website, NOT in the mobile app.** **Database Tables:** - `redemption_codes` - Stores codes with max_uses, current_uses, expiration, active status - `code_redemptions` - Tracks which users redeemed which codes **Components:** - **Admin UI** (`apps/admin/src/pages/RedemptionCodesPage.tsx`) - Super admins can generate, manage, and view redemption codes - **Edge Function** (`apps/supabase/functions/redeem-code/`) - Validates and processes code redemption (unauthenticated, accepts email + code) - **Database Function** (`redeem_code_by_email(p_email, p_code)`) - Atomic redemption with validation (active, not expired, has uses remaining, user hasn't redeemed) - **Web UI** (`apps/web/app/redeem/page.tsx`) - Public redemption page **Flow:** 1. Admin generates code in admin dashboard 2. User visits `/redeem` on website and enters email + code 3. `redeem-code` edge function calls `redeem_code_by_email()` database function 4. Database function looks up user by email, validates code, and sets `profiles.is_premium = true` **Important:** Do NOT add redemption code UI to the mobile app. Apple requires in-app purchases for premium features purchased within iOS apps. ## Mobile UI - DO NOT CHANGE ### Tab Bar Background **NEVER set `backgroundColor: 'transparent'` on the tab bar.** Always use a semi-transparent solid color: ```typescript backgroundColor: 'rgba(18, 18, 18, 0.85)', ``` The `BlurView` in `tabBarBackground` is unreliable and can fail during re-renders/transitions, causing a fully transparent tab bar. The solid rgba background acts as a fallback while still allowing the blur effect on top. **Location:** `apps/mobile/app/(app)/_layout.tsx` - `tabBarStyle` ## Infrastructure as Code (Terraform) Supabase project configuration is managed via Terraform in the `terraform/` directory. This includes auth settings, OAuth providers, email templates, SMTP config, rate limits, and more. **Directory structure:** ``` terraform/ ├── modules/ │ └── supabase/ # Shared configuration module │ ├── main.tf │ ├── variables.tf │ ├── outputs.tf │ └── templates/ # Email templates └── environments/ ├── prod/ # Production (ckjcrkjpmhqhiucifukx) └── non-prod/ # Non-production (itbzhrvlgvdmzbnhzhyx) ``` **When to update Terraform:** - Changing auth settings (OAuth providers, JWT expiry, MFA settings) - Modifying email templates or subjects - Updating rate limits or security settings - Changing SMTP configuration - Modifying storage or API settings **Workflow (always test in non-prod first):** ```bash # 1. Apply to non-prod first cd terraform/environments/non-prod export TF_VAR_supabase_access_token=sbp_xxxxx terraform plan terraform apply # 2. Verify changes work in non-prod # 3. Apply to prod cd terraform/environments/prod terraform plan terraform apply ``` **Important:** After making Terraform changes, remind the user to: 1. Commit the updated `terraform/` files 2. The GitHub Action will auto-apply on merge to master (non-prod first, then prod) **Do NOT use Terraform for:** - Database schema changes (use migrations) - Edge function deployment (use CLI or MCP) - Storage bucket creation (use migrations) ## Edge Function Secrets Edge functions use secrets stored in Supabase. These are synced from GitHub Secrets during CI/CD deployment. **Current secrets:** | Secret | Used By | Description | |--------|---------|-------------| | `REVENUECAT_WEBHOOK_SECRET` | revenuecat-webhook | Webhook signature validation | | `REVENUECAT_API_KEY` | sync-subscription | RevenueCat API access | | `REVENUECAT_ENTITLEMENT_ID` | sync-subscription | Entitlement identifier | | `OPENROUTER_API_KEY` | classify-message | AI classification API | | `ADMIN_PRIVATE_KEY_JWK` | admin-decrypt-* | E2EE admin decryption | | `ADMIN_KEYS_JSON` | admin-decrypt-*, migrate-e2ee | Multiple admin keys | | `DISCORD_WEBHOOK_URL` | send-discord-notification | Discord notifications | **Auto-provided by Supabase (don't set):** - `SUPABASE_URL` - `SUPABASE_ANON_KEY` - `SUPABASE_SERVICE_ROLE_KEY` **When adding a new edge function that needs secrets:** 1. Add the secret to the edge function code: `Deno.env.get("SECRET_NAME")` 2. Update `.github/workflows/deploy-supabase.yml` to include the new secret in the sync step 3. Update `apps/supabase/functions/.secrets.example` with the new secret 4. **Remind the user** to add the secret to GitHub Secrets (Settings → Secrets → Actions) 5. Update this table in CLAUDE.md **Local development:** ```bash cp apps/supabase/functions/.secrets.example apps/supabase/functions/.secrets # Fill in values supabase functions serve --env-file apps/supabase/functions/.secrets ```