12. Stack decisions
Each entry states the decision and the main reason. Full research, sources and alternatives live in Ben's decision log. Dates: MVP1 Oct 26, MVP2 Nov 9, Demo Day Dec 11.
Platform and frontend
- S1. Next.js web app for MVP1, then a native iOS app on the same API. No wrapper (Capacitor). Why: a truly native iOS experience and a clean App Review path. Cost: screens get built twice once iOS starts.
- S4. iOS in Swift (SwiftUI); Mabelle owns architecture and review. Why: most native feel and Mabelle knows Swift. Starting before MVP1 is encouraged, since TestFlight is due two weeks after MVP1. Expo was the alternative.
- S16. Tailwind plus shadcn/ui, with design tokens in one tokens.json shared by web and iOS. Why: flexible, and the components live in our repo. (Figma's variables API needs an Enterprise plan, so tokens are exported by plugin.)
Backend and API
- S5. All business logic runs on the server behind one API both apps call. Why: one engine, one rank-blind test, one place to fix a rule. Database row level security is the lock that holds even if server code is skipped, because signed-in users can reach Supabase directly.
- S9. REST with an OpenAPI spec that generates the web and Swift clients. Why: simplest and callable from both apps. GraphQL, gRPC, tRPC and Server Actions were rejected (tRPC and Server Actions can't be called from Swift).
- S40. The spec is generated from the code (Zod). Why: a hand-written spec drifts from the code, and iOS finds out first.
- S41. One standard error format (RFC 9457) with stable error codes. Why: apps react to codes, so wording can change without breaking them.
- S42. Every iOS build checks a minimum supported version on launch. Why: installed apps can't be forced to update unless this is in the first build.
- S43. House rules: money in whole cents, times in UTC, snake_case names, saves safe to repeat.
- S44. The web app uses the same API as iOS. Why: the API gets real use from MVP1, so iOS inherits a tested contract.
- S20. Vercel Cron every 15 minutes sends due reminders; each reminder is claimed once and late ones are skipped. Why: Vercel runs can be missed, duplicated or overlap.
Data
- S2, S26. Card data lives as reviewed files in the repo, each card with a permanent ID. Why: a wrong eligibility rule is our biggest trust risk, so every change is reviewed; moving cards into the database later stays easy.
- S8. Supabase (Postgres plus Auth). Why: relational data, auth and jobs from one SOC 2 vendor, Swift and JS SDKs, and Yunho has used it. Firebase, Neon, Render, raw AWS/GCP and Clerk were weighed.
- S11. Local, Preview and Production environments; production on Supabase Pro with daily backups; schema changes only through migrations.
- S15. Server code queries the database as the signed-in student, no ORM. Why: keeps row level security on every query.