Fault Line — Architecture Map

Complete system architecture, external services, accounts, API keys, and maintenance costs. Gitignored — not committed.

Plain Language
Technical

How the App is Built

Fault Line has three user-facing surfaces — a mobile app, a Next.js web app, and a marketing site — all backed by a single Supabase project. Users submit reports from the phone or the web, the database stores them, edge functions run AI analysis and send emails, and a daily cron escalates to authorities. Here's every piece:

User's Phone / Browser
Mobile App (Expo)
Web App (Next.js/Vercel)
Supabase (DB + Storage + Auth)
Edge Functions + pg_cron
Claude AI · Resend · Modal Kokoro TTS

The App Itself

React Native + Expo
Free

The app is built with React Native (makes one codebase work on both Android and iPhone) using Expo (a toolkit that handles building, testing, and publishing). Written in TypeScript.

Think of it like: React Native is the engine, Expo is the car body, TypeScript is the language the blueprints are written in.

Account needed: Expo account (free) at expo.dev/signup

Build command: npx eas build --platform android

Cloud Database & Backend

Supabase
Free Tier

Supabase is where ALL the data lives — every report, user profile, authority, cluster, vote, escalation log, AI cache, and feedback submission. It also handles user login (magic link emails), file storage (photos/videos), and real-time notifications.

Think of it as: the filing cabinet, the security guard, and the postal service all in one.

Account: Already created. Project URL: https://dzewklljiksyivsfpunt.supabase.co

SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=sb_publishable_xxxxxxxxxxxxxxxxxxxx (safe for app)
SUPABASE_SERVICE_ROLE_KEY=sb_secret_xxxxxxxxxxxxxxxxxxxx (ONLY in edge functions)

What's stored in Supabase:

Free tier limits:

Server-Side Code (Edge Functions)

Supabase Edge Functions (Deno)
Included in Supabase

These are small programs that run on Supabase's servers (not on the user's phone). They handle things that need secret keys — like sending emails and calling the AI.

Think of it as: a private office in the back that handles sensitive paperwork the public can't access.

Functions:

Secrets stored in Supabase Edge Function settings:

Set (verified via `supabase secrets list`):
ANTHROPIC_API_KEY=sk-ant-xxxx (Claude Haiku + Sonnet)
RESEND_API_KEY=re_xxxx (transactional email)
FROM_EMAIL=onboarding@resend.dev (Resend test sender until domain is bought)
REPLY_TO=moonlit-social-labs@proton.me (replies route to founder inbox)
CRON_SECRET=xxxx (rotated 2026-04-20, used by pg_cron header)

Auto-provided by Supabase:
SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, SUPABASE_DB_URL

Next.js Web App

fault-line-web (Next.js 15 on Vercel)
Free (Vercel Hobby)

A full-featured reporting interface in the browser. Shares the same Supabase backend as the mobile app — identical authentication, identical schema, identical RLS. Users can submit reports, browse the map, search, view dashboards, and manage their profile from any browser.

Think of it as: the second window into the same filing cabinet. Useful for desktop users, journalists, city officials, and anyone who doesn't want to install an app.

Stack

Env vars (set in Vercel dashboard)

NEXT_PUBLIC_APP_ORIGIN=https://fault-line-web.vercel.app
NEXT_PUBLIC_SUPABASE_URL=https://dzewklljiksyivsfpunt.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=sb_publishable_xxxx
SUPABASE_SERVICE_ROLE_KEY=sb_secret_xxxx
CRON_SECRET=xxxx (matches Supabase cron secret)
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
KOKORO_TTS_URL=https://moons7onr--kokoro-tts-server-kokorotts-tts.modal.run

External Services

Anthropic (Claude AI)
Pay per use

Powers all AI features: photo analysis, auto-descriptions, legal letter enhancement, escalation emails, notification copy, report summaries, photo comparison, and feedback triage.

Uses two models: Haiku 4.5 (fast/cheap) for descriptions and notifications, Sonnet 4.6 (accurate/quality) for legal letters and escalation emails.

ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxx

Cost: Haiku ~$0.25/1M input tokens. Sonnet ~$3/1M input. Photo analysis ~$0.01/photo. Estimated $5-20/month at moderate usage.

Resend (Email)
3,000 emails/month free

Sends all emails: escalation reports to government authorities, individual report submissions, and demand letter deliveries.

RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

Currently sending from: onboarding@resend.dev (Resend test sender). Replies route to: moonlit-social-labs@proton.me (via REPLY_TO header). Switch FROM_EMAIL to reports@yourdomain once you buy the domain and verify it in Resend.

Kokoro TTS (Modal-hosted GPU)
~$0.001/req

Text-to-speech server powering the audio-guided reporting mode in the mobile app and any AI-generated voice content on the web. Modal runs a private deployment of the Kokoro TTS model on a T4 GPU that scales to zero when idle.

Shared across the user's projects — same server handles TTS for every Moonlit Social Labs app, not just Fault Line. The mobile app posts text to the endpoint, receives audio/wav back, caches it to disk, and plays it via expo-av. Next.js proxies through /api/tts to avoid browser CORS.

KOKORO_TTS_URL=https://moons7onr--kokoro-tts-server-kokorotts-tts.modal.run
(POST { text, voice, speed } → audio/wav · default voice: bf_emma)

Cost: ~$0.001 per request. Scales to zero. First request after idle has a 10–15s cold start; subsequent requests are <1s.

Formspree (Feedback Forms)
50 submissions/month free

Handles feedback/feature-request/bug-report form submissions from the marketing site, Next.js web app, and mobile app. All three surfaces POST to the same endpoint; submissions are also mirrored to Supabase so the public submission board keeps working.

FORMSPREE_ENDPOINT=https://formspree.io/f/mlgojojg
Google AdMob (Ads)
Not yet configured

Banner ads that generate revenue to keep the app free. Currently placeholder — app works without it. When ready, install react-native-google-mobile-ads and plug in IDs.

ADMOB_APP_ID_ANDROID=ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy
ADMOB_BANNER_ID_ANDROID=ca-app-pub-xxxxxxxxxxxxxxxx/zzzzzzzzzz
Sentry (Crash Reporting)
Not yet configured

Catches app crashes and sends reports so you can fix bugs. Free tier: 5,000 errors/month. App works without it — you just won't see crashes.

SENTRY_DSN=https://xxxxxxxxxxxx@o000000.ingest.sentry.io/000000
GitHub Pages (Marketing Site)
Free

Hosts the public website: landing, features (plain + technical), blog, about, privacy, terms, feedback. 10 static HTML pages sharing a single theme.css. Currently at fault-line.dev.

Repo layout: the public repo contains only the website/ files (served at root). The private repo contains the full project source: mobile app, Next.js web app pointer, Supabase migrations, docs. Two remotes — origin (private) is the default push target; github-public is pushed manually for website updates.

Vercel (Next.js Web App)
Free (Hobby)

Hosts the Next.js fault-line-web project. Auto-deploys on every push to main. Edge middleware, SSR, image optimization, zero cold-start on the production URL.

Limits: Hobby tier gives 100 GB bandwidth/month, 1000 deployments/month. Plenty for pre-launch. Paid (Pro) is $20/month if traffic outpaces the free tier.

Upstash Redis (Rate Limiting)
10K req/day free

Serverless Redis used by the Next.js web app's rate limiter. Protects against abuse of the report-submission endpoint and other public mutation paths. Fails open in dev, fails closed in prod.

Status: account not yet created. Code references UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN env vars; without them the web app's rate limiter short-circuits to reject every request (by design).

Ko-fi (Donations)
Free

Accepts community donations to support development. Button on all website pages.

Free APIs (No Account Needed)

App Store Accounts

Monthly & Annual Costs

ServiceMonthlyAnnualNotes
Supabase$0$0Free tier (500MB DB, 1GB storage, 50K users, 500K edge invocations)
Vercel (Hobby)$0$0Next.js web app. Free tier — 100GB bandwidth/mo
Upstash Redis$0$0Free tier (10K req/day) — rate limiting
Resend$0$0Free tier (3,000 emails/month)
Anthropic (Claude AI)~$5–20~$60–240Pay per use. Scales with report volume
Modal (Kokoro TTS)~$0.50–3~$6–36Pay per request (~$0.001/req). Shared across all MSL projects
GitHub Pages$0$0Free for public repos (marketing site)
Formspree$0$0Free tier (50 submissions/month)
Expo (EAS)$0$0Free tier (30 builds/month)
Google Play$0$0$25 one-time (already paid)
Apple Developer~$8.25$99Annual subscription (not yet enrolled)
Domain (fault-line.dev)~$1~$12When purchased (not yet)
AdMob / AdSense$0$0Free — generates revenue (not yet configured)
Sentry$0$0Free tier (5K errors/month, not yet configured)
Ko-fi$0$0Free — receives donations
TOTAL~$15–32/mo~$177–387/yrMostly AI + Apple Developer fee. Everything else is free tier.

Break-even: With AdMob banner ads averaging $1-3 eCPM and even moderate daily active users (~500), ad revenue would cover all costs. Ko-fi donations are bonus.

Scaling Costs (When You Outgrow Free Tiers)

TriggerWhat HappensCost
500MB databaseUpgrade Supabase to Pro$25/mo
1GB file storageIncluded in Supabase Pro
3,000 emails/monthUpgrade Resend$20/mo (50K emails)
5K crash reportsUpgrade Sentry$26/mo
30 builds/monthUpgrade EAS$15/mo

Stack Overview

┌─────────────────────────────────────────────────────────┐
│                    CLIENT (Mobile App)                    │
│  React Native 0.76 + Expo SDK 55 + TypeScript            │
│  73 source files | 11 screens | 10 components             │
│  43 service modules | 5 hooks | 3 stores                  │
├─────────────────────────────────────────────────────────┤
│                         ↕ HTTPS                          │
├─────────────────────────────────────────────────────────┤
│               BACKEND (Supabase Cloud)                    │
│  PostgreSQL 15 + PostGIS 3.4 | Row Level Security         │
│  11 tables | 12 RPC functions | 6 triggers                │
│  Auth (magic link) | Storage (report-media bucket)        │
│  Realtime (WebSocket subscriptions)                       │
├─────────────────────────────────────────────────────────┤
│            EDGE FUNCTIONS (Deno Runtime)                   │
│  5 functions | Authenticated | Rate-limited               │
│  escalate-clusters | analyze-photo | ai-generate          │
│  ai-compare-photos | send-report-email                    │
├─────────────────────────────────────────────────────────┤
│              EXTERNAL APIs                                │
│  Anthropic Claude (Haiku 4.5 + Sonnet 4.6)               │
│  Resend (transactional email)                             │
│  Census Bureau Geocoder (election districts)              │
│  OpenStreetMap (offline tile cache)                       │
│  Open311 / SeeClickFix (bidirectional 311 sync)          │
└─────────────────────────────────────────────────────────┘
  

Client Architecture

Build System

Navigation

State Management

Key Dependencies

Database Schema

reports (PostGIS)
├── id UUID PK
├── user_id UUID FK→profiles (nullable for anonymous)
├── category TEXT (24 enum values)
├── latitude/longitude DOUBLE PRECISION
├── location_point GEOGRAPHY(POINT,4326) — auto-set by trigger
├── address, city, state, zip TEXT
├── description TEXT
├── size_rating, hazard_level, urgency, condition_level TEXT
├── media JSONB[] — [{id, uri, uploadedUrl, thumbnailUrl, type}]
├── resolved_media JSONB[] — before/after photos
├── vehicle_damage JSONB
├── status TEXT (draft|submitted|acknowledged|in_progress|resolved|closed|rejected)
├── authority_id UUID FK→authorities
├── cluster_id UUID FK→report_clusters
├── upvote_count, confirm_count INTEGER
├── is_anonymous, sensor_detected, offline_queued, is_quick_report BOOLEAN
├── _hp TEXT — honeypot (bot detection)
└── created_at, updated_at, resolved_at TIMESTAMPTZ

profiles
├── id UUID PK FK→auth.users
├── display_name, avatar_url, push_token TEXT
├── total_reports, total_upvotes, total_confirms, points INTEGER
├── badges JSONB[]
└── created_at, updated_at TIMESTAMPTZ

authorities (42 seeded for MA/RI/NH)
├── id UUID PK
├── name, level (federal|state|county|city|town), state, city, county TEXT
├── submission_methods JSONB[] — [{method, endpoint, priority, notes}]
├── boundary_geojson JSONB
├── response_time_avg_days, fix_rate_percent REAL
└── is_active BOOLEAN

report_clusters
├── id UUID PK
├── category TEXT, centroid_lat/lng, centroid_point GEOGRAPHY
├── report_count, unique_reporters INTEGER
├── max_hazard_level, status TEXT
├── authority_id UUID FK→authorities
├── city, state, address TEXT
└── first_reported_at, last_reported_at, submitted_at, escalated_at TIMESTAMPTZ

report_votes (UNIQUE: report_id + user_id + vote_type)
escalation_log (cluster_id, method, recipient, subject, body, status)
feedback (type, name, email, subject, message, status, votes)
ai_cache (report_id, task_type, result JSONB, model)
submission_log (user_id, ip_address, user_agent)
cluster_reports (cluster_id, report_id — link table)
  

Key Triggers

Key RPC Functions

RLS Policies

AI Model Routing

TaskModelReason~Cost/call
Photo analysisHaiku 4.5 (vision)Fast, cheap, strong vision$0.01
Photo comparisonHaiku 4.5 (vision)Pattern matching, 2 images$0.02
DescriptionsHaiku 4.5Short output, frequent$0.001
NotificationsHaiku 4.5Short, latency-sensitive$0.001
SummariesHaiku 4.5Short, straightforward$0.001
Legal lettersSonnet 4.6Legal accuracy critical$0.02
Escalation emailsSonnet 4.6Quality affects outcomes$0.015
Feedback triageSonnet 4.6Nuanced deduplication$0.02

System prompt provides full app context: 24 categories, 3 state statutes with notice periods, clustering thresholds, anti-hallucination rules.

Security Architecture

SQL Migrations (run in order)

  1. supabase/schema.sql — core tables, PostGIS, triggers, RLS, RPC functions
  2. supabase/seed_authorities.sql — 42 MA/RI/NH authorities with real contact info
  3. supabase/clustering.sql — cluster tables, auto-assign trigger, escalation functions
  4. supabase/migration_001_add_missing_columns.sql — urgency, condition, quick report, cluster_id, resolved_media, push_token
  5. supabase/migration_002_missing_rpcs.sql — award_points, get_nearby_clusters
  6. supabase/migration_003_security_fixes.sql — RLS: enforce user_id on insert, add DELETE policies
  7. supabase/migration_004_server_rate_limit.sql — rate limit trigger, honeypot, submission_log
  8. supabase/migration_005_feedback.sql — feedback table with RLS
  9. supabase/migration_006_ai_cache.sql — AI result cache table
  10. supabase/migration_007_rpc_auth_guards.sql — increment_upvote / increment_confirm now require auth; prevent self-voting
  11. supabase/migration_008_reports_insert_rls.sql — tightened INSERT policy so an attacker cannot forge reports under another user's ID
  12. supabase/migration_009_schedule_escalation_cron.sql — enable pg_cron + pg_net, schedule escalate-clusters-daily to run at 14:00 UTC every day

Cost Analysis

ServiceFree TierMonthly CostAnnual CostScale Trigger
Supabase500MB DB, 1GB storage, 50K MAU$0$0$25/mo at 500MB+
AnthropicNone (pay per use)$5–20$60–240Scales linearly with volume
Resend3,000 emails/mo$0$0$20/mo at 3K+
Apple DeveloperNone$8.25$99Fixed
DomainNone$1$12Fixed
Expo EAS30 builds/mo$0$0$15/mo at 30+
GitHub PagesUnlimited$0$0Never
Sentry5K errors/mo$0$0$26/mo at 5K+
AdMobN/ARevenueRevenueGenerates income
TOTAL$14–29$171–351

One-time costs: Google Play $25. That's it.

Revenue potential: Banner ads at $1-3 eCPM. 500 DAU × 3 impressions = 1,500/day = 45K/month = $45-135/month in ad revenue. Covers all costs at ~300 DAU.