12 KiB
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 intimacy app featuring swipeable question packs with partner matching. Users swipe on questions (yes/no/maybe), and when both partners answer positively, a "match" is created that unlocks a chat thread for that question.
Commands
# 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
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
supabase start # Start local Supabase
supabase db reset # Reset DB and run all migrations
supabase functions serve # Run edge functions locally
supabase migration new <name> # 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 - IMPORTANT
NEVER use MCP tools (apply_migration) for database migrations. Always use the Supabase CLI instead.
Why: MCP migrations are tracked in the database but do NOT create local migration files. This means:
- Migration history is not committed to git
- Other developers won't get the schema changes
supabase db resetwon't include MCP-applied migrations- Changes cannot be reproduced from scratch
Correct workflow for schema changes:
# 1. Create migration file locally
supabase migration new <descriptive_name>
# 2. Edit the migration file in apps/supabase/migrations/
# 3. Test locally
supabase db reset
# 4. Deploy to remote (link to correct project first)
supabase link --project-ref <project_ref>
supabase db push
MCP tools ARE appropriate for:
- Reading data (
execute_sqlfor SELECT queries) - Exploring schema (
list_tables,list_migrations) - Deploying edge functions (
deploy_edge_function) - Generating types (
generate_typescript_types)
Architecture
Monorepo Structure (Turborepo)
apps/mobile- Expo React Native app (expo-router for navigation)apps/admin- React Admin dashboard for content managementapps/supabase- Database migrations and edge functionspackages/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.tsuseAuthStore- User, couple, partner stateuseMatchStore- Matches and notificationsusePacksStore- Question packs and enabled packs per coupleuseMessageStore- Chat messages and unread countsuseSubscriptionStore- 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 notificationsmanage-couple- Join/leave couple operationsdelete-relationship- Deletes couple data (matches, messages, media) while keeping accountsdelete-account- Permanently deletes user account and all associated data (Apple App Store requirement)send-notification- Push notifications for matchesrevenuecat-webhook- Subscription webhook handlersync-subscription- Syncs RevenueCat subscription statusredeem-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:
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) |
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_createdtrigger 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-responseedge 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 statuscode_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:
- Admin generates code in admin dashboard
- User visits
/redeemon website and enters email + code redeem-codeedge function callsredeem_code_by_email()database function- 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:
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):
# 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:
- Commit the updated
terraform/files - 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_URLSUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEY
When adding a new edge function that needs secrets:
- Add the secret to the edge function code:
Deno.env.get("SECRET_NAME") - Update
.github/workflows/deploy-supabase.ymlto include the new secret in the sync step - Update
apps/supabase/functions/.secrets.examplewith the new secret - Remind the user to add the secret to GitHub Secrets (Settings → Secrets → Actions)
- Update this table in CLAUDE.md
Local development:
cp apps/supabase/functions/.secrets.example apps/supabase/functions/.secrets
# Fill in values
supabase functions serve --env-file apps/supabase/functions/.secrets