LTL is a cross-platform mobile app backed by a managed Postgres platform, designed offline-first so every core read path works without a network.
Stack
Client
- React Native with Expo (SDK 52+), TypeScript in strict mode.
- Expo Router for file-based navigation.
- Zustand for UI and session state, TanStack Query for server state.
- expo-sqlite with SQLCipher for the encrypted local database.
- NativeWind plus a custom theme layer for styling.
- expo-image, expo-av, expo-camera, and expo-file-system for media; expo-notifications for push; expo-secure-store for tokens.
Backend
- Supabase as the primary platform: Postgres, Auth, Storage, Realtime, and Edge Functions.
- Edge Functions (Deno) handle the operations that cannot be client-only: death-transition state changes, signed-URL generation, AI prompt generation (proxying to a server-side API key), notification fan-out, and data-export bundling.
- RevenueCat for cross-platform billing, Sentry for error tracking, and a self-hosted privacy-first analytics layer.
Why This Stack
- Expo over fully native: one codebase across both platforms with acceptable performance for this use case, which matters for solo-dev bandwidth.
- Supabase over Firebase: Postgres plus SQL fits the relational data model (family graphs, permissions) far better, and row-level security provides fine-grained protection without writing a separate backend.
- Room to migrate: building a bespoke backend would add months and is not justified for the early phases; the architecture leaves room to move off Supabase later if scale or features demand it.
Data Model
The conceptual schema is relational and stores relationships rather than a single family tree, so it can represent chosen, blended, and estranged families. Core entities include:
usersandprofiles(the structured card; either user-backed or memorial-only for subjects who never had an account).relationships,family_spaces, andfamily_membershipsfor the connection graph.entries(polymorphic across all four tracks via atrackpluscontext_idshape) andprompts/prompt_deliveries.streaksandparticipationfor the per-track engagement mechanics.memorial_candles,media_assets,legacy_transitions, and an immutableaudit_log.subscriptionsandmemorial_endowmentsfor billing and perpetual preservation.
Entries are a single polymorphic table; reveal-on-both-answered state for Pairings is derived at read time rather than stored, and memorial prompt deliveries are written one row per candle member so each person’s response state is trackable.
Offline-First Sync
A subset of the schema lives on the device via SQLCipher-encrypted SQLite: the user’s recent entries, drafts, a cache of upcoming prompts, the pending-write sync queue, and media thumbnails. Reads come from SQLite first, with a background pull from Postgres on app foreground. Writes are last-write-wins per field, with conflict detection on entry body text (the UI asks on the rare conflict, since entries are user-authored and not concurrently edited). Realtime subscriptions are used sparingly for active Pairings, Family Spaces, and memorial activity; this is deliberately not a chat app.
CI/CD and Ops
GitHub Actions runs lint, type-check, and tests on every pull request; EAS handles native builds and over-the-air JS updates with rollback. The death-transition system has its own internal review dashboard, treated as critical infrastructure rather than an afterthought.