A private weekly newsletter you write together with family and friends. Members write short updates (with optional photos) throughout the week, and on each group's publish day everything gets compiled into a single "edition" — delivered in‑app, by push, and by email.
The MVP is aimed at older adults and small friend groups who want something warmer and more deliberate than a group chat. Design priorities: large tap targets, readable serif type, minimal navigation, "newspaper on the kitchen counter" feel.
The app is feature‑complete through MVP Phase 7 of todo.md and into Phase 8 polish. What's working today:
- Email + password auth, password reset, 3‑screen onboarding (account → name/avatar → create/join group)
- Group creation, invite codes, join‑by‑code, member list, leave/remove, moderator‑only settings
- Post composer with single‑photo upload, current‑draft surfacing, edit/delete before publish
- Weekly compilation via Supabase Edge Function on a 15‑minute cron, scoped by each group's
publish_day/publish_time/timezone - Inbox + newspaper‑styled edition reading view with pull‑to‑refresh
- Transactional email via Resend with per‑group unsubscribe tokens
- Push notifications on edition publish, deep‑linked to the edition view
- Account deletion (with moderator handoff) via RPC + edge function
Still open: store icons / splash polish, App Store and Play Store metadata, an end‑to‑end performance pass. See todo.md for the full checklist and bugs.md for known issues.
- Mobile: React Native 0.81 + Expo SDK 54 (managed workflow), Expo Router 6 with typed routes
- Language: TypeScript (strict), path alias
@/*→ repo root - Backend: Supabase — Postgres + RLS, Auth, Storage, Edge Functions (Deno)
- Email: Resend
- Push: Expo Notifications (token registered server‑side, pushes sent from the edge function)
- Fonts: Superclarendon + Futura on iOS (system); Roboto Slab + Jost on Android/web via
@expo-google-fonts
app/ Expo Router file-based routes
(auth)/ login, signup, onboarding, reset-password
(tabs)/ home, inbox, post (compose), groups, profile
group/ create, join, [id] detail
edition/[id]/ front page (index) + story reader ([postId])
components/ Reusable UI (themed-text, edition-post, tab bar, etc.)
constants/ colors, typography, layout, motion, strings, icons, loading
hooks/ use-auth, use-post-image-url, use-image-orientation,
use-reduce-motion
lib/ supabase client + domain modules (auth, groups, posts,
editions, notifications, image, edition-layout,
edition-seen, haptics)
types/ Shared DB + domain types
supabase/
migrations/ 24 SQL migrations (schema → security hardening)
functions/
compile-editions/ Cron-driven compile + email + push
publish-edition-now/ Moderator-only immediate publish
delete-account/ Auth-aware account deletion
unsubscribe/ Token-based per-group unsubscribe endpoint
_shared/ Shared HTML email rendering
design/ BRAND.md, implementation plan, logo assets
assets/ Brand logo, app icons, splash, fonts
- Node 18+ and npm
- Expo CLI (
npx expois fine — no global install needed) - A Supabase project (cloud or local via
supabaseCLI) - A Resend account + API key (for email delivery)
- Xcode (iOS sim) and/or Android Studio (Android emulator) for native runs
npm installCopy .env.example → .env.local and fill in your Supabase project values:
EXPO_PUBLIC_SUPABASE_URL=https://<project-ref>.supabase.co
EXPO_PUBLIC_SUPABASE_ANON_KEY=<anon-key>The Supabase client throws at load if either var is missing — fail loud beats silent 401s.
npx supabase link --project-ref <project-ref>
npx supabase db pushThis creates all tables, RLS policies, storage buckets (avatars, post-images, group-covers), and the cron job that invokes the compile function every 15 minutes.
npx supabase functions deploy compile-editions
npx supabase functions deploy publish-edition-now
npx supabase functions deploy delete-account
npx supabase functions deploy unsubscribeSet the function secrets:
npx supabase secrets set \
CRON_SECRET=<shared-secret-for-manual-invocations> \
RESEND_API_KEY=<resend-api-key> \
FUNCTIONS_PUBLIC_URL=https://<project-ref>.supabase.co/functions/v1 \
EMAIL_FROM='Catch Up Column <hello@your-verified-domain>'SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are injected automatically. EMAIL_FROM defaults to the Resend sandbox sender, which is fine for dev but tanks deliverability in production — point it at a verified domain before shipping.
npx expo start # dev server
npx expo run:ios # iOS simulator
npx expo run:android # Android emulatorsupabase/functions/compile-editions is the heart of the publishing pipeline. The cron job hits it every 15 minutes; on each run it:
- Calls the
compile_due_editionsRPC, which finds every group whose publish window falls inside the current tolerance, claims all uncompiled posts for those groups, and inserts a neweditionsrow linking them. - Fetches the email payload for each newly compiled edition (plus any earlier editions whose previous send failed and still have retry budget).
- Renders the newspaper‑styled HTML email and sends to each subscribed recipient via Resend.
- Fans out Expo push notifications to all registered devices for the group, with a deep link to
catchupcolumn://edition/<id>.
Idempotency, per‑group timezone math, push retries with exponential backoff, and unsubscribe handling all live in dedicated migrations — see supabase/migrations/20260426000007_*, 20260430010000_*, 20260505000030_*, and 20260505000040_*.
You can fire the function manually for testing:
curl -X POST \
-H "Authorization: Bearer $CRON_SECRET" \
https://<project-ref>.supabase.co/functions/v1/compile-editionsThe visual language is documented in design/BRAND.md. Quick reference:
- Palette: warm paper background (
#FAF7F2), orange primary (#FF7237), peach surfaces (#FFD3C2), yellow accent (#F4E33A), high‑contrast ink text. - Type: Superclarendon (display serif) + Futura (body sans) on iOS; Roboto Slab + Jost as the cross‑platform fallback. Pick families from
Typography.familiesin constants/typography.ts — don't hardcode font names. - Tokens: semantic only. Use
Colors.ink/Colors.paperWarm, never raw hex; useLayout.padding.*,Layout.borderRadius.*, andLayout.touchTargetMin(48 px floor) at component sites.
- Functional components with hooks; arrow‑function
constexports. - Files in kebab‑case (
edition-post.tsx), components in PascalCase. StyleSheet.create()colocated at the bottom of each component file.- Keep components under ~150 lines; break out helpers into
components/orlib/. - TypeScript strict mode — no inline
any. DB row/insert/update types live in types/database.ts.
- CLAUDE.md — full project spec, schema, terminology, MVP scope
- todo.md — build phases and what's left
- bugs.md — known issues
- design/BRAND.md — colors, typography, and component intent
- design/BRAND_IMPLEMENTATION_PLAN.md — branding migration tracker