- Add user message reporting via long press menu - Add delete-account edge function for App Store compliance - Update admin moderation page with AI/User tabs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
9.7 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 migrations or destructive operations
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
E2EE (End-to-End Encryption) Implementation
Chat messages (text, images, videos) are encrypted client-side using AES-256-GCM with triple-wrapped RSA keys.
Architecture
- Full documentation: See
docs/e2ee-implementation.mdfor complete details - Encryption library:
apps/mobile/src/lib/encryption/ - React hooks:
apps/mobile/src/hooks/useEncryptionKeys.ts,useDecryptedMessage.ts - Edge functions:
handle-key-rotation,reencrypt-pending-messages
Key Concepts
- Message versions: v1 = plaintext (legacy), v2 = E2EE
- Triple-wrapped keys: Each message's AES key is RSA-encrypted for sender, recipient, and admin
- Device recovery: When user gets new device, admin key re-wraps messages for new user key
Database Tables
messages.version- Encryption version (1 or 2)messages.encrypted_content- Base64 ciphertextmessages.encryption_iv- Base64 IV for AES-GCMmessages.keys_metadata- JSONB with wrapped keysprofiles.public_key_jwk- User's RSA public keymaster_keys- Admin RSA public keys
Setup Required
- Run
node scripts/generate-admin-keys.mjsto generate admin keypair - Insert public key into
master_keystable - Store private key in
ADMIN_PRIVATE_KEY_JWKenvironment variable for edge functions - Install crypto dependency:
npm install react-native-quick-crypto@^0.7.x - Run
npx expo prebuild --cleanafter installation
Edge Functions for E2EE
| Function | verify_jwt | Purpose |
|---|---|---|
handle-key-rotation |
false | Re-wraps message keys when user gets new device |
reencrypt-pending-messages |
false | Wraps keys for recipient after they generate keys |