Building DavisDine
April 2026DavisDine is an iOS app that shows UC Davis students what's in the dining commons today, with allergen filtering, AI cuisine sorting, and a meal planner that totals macros across stations. It's on the App Store.
The problem
UC Davis publishes menus on a slow web portal with no filters and no cross-location search. Students with allergies scan ingredient lists by hand every meal. The path from hungry to "what should I eat right now" was something like two minutes of clicking. I wanted that down to five seconds.
Stack
SwiftUI on the front; FastAPI on Python deployed to Google App Engine; Firestore for persistence. The backend scrapes UC Davis menus on a cron schedule, runs each batch through Claude Sonnet to classify items into 12 cuisines at 150 items per call, and serves the cached result through a REST API. The iOS app holds a UserDefaults mirror so it works offline. Everything heavy happens at scrape time; a per-request call is a Firestore read.
Anonymous reviews, SMS-gated
Reviews are anonymous but bot-resistant. Verification is one SMS code; the server stores a hash of the phone number and hands back a random handle. No name, bio, photo, or settable username. The phone number is the cost-of-spam signal and never appears in the UI. That gave me a review system with no moderation queue and no harassment surface.
The billing spike
A month in, my Google Cloud bill jumped. Three causes, in order of damage: an analytics dashboard polling on a tight loop during dev sessions, frequent redeploys triggering cold-start charges, and an F2 instance for a workload that fits in F1. Setting min_instances: 0 saved about $115/month. Cron dropped from every 15 minutes to hourly (75% fewer API calls, zero user-visible change). The instance downgrade halved the hourly rate. Total fix: one afternoon.
User testing
Three rounds with other students. The useful feedback was specific: too many taps to add an item to a meal plan, the dining commons picker not sorted by proximity, no undo on dismissed recommendations, too much whitespace on the home screen. Each one became a GitHub issue, a branch, and a separate PR.
In one two-hour sprint I shipped twelve features against that backlog: favorites, station filtering, sort by rating/calories/protein, skeleton loaders, a trending endpoint, helpfulness votes on reviews, a daily meal plan generator, batch ingredient analysis, recommendation caching, and real-time macro totals. The branching discipline mattered more than the speed; if the next round of testing hated a feature, it could be reverted on its own.
What I got wrong
The biggest waste was the review system. I built chat, helpfulness voting, threaded replies, and an entire social layer before testing whether anyone wanted it. They didn't. Students came for fast menu lookup and meal planning. I tore most of that infrastructure out within two weeks. Lesson older than the project: ship the smallest thing that solves the actual problem, watch users pull on it, build social features only after the pull appears.
Second mistake: monitoring. Cost alerts went up after the spike instead of with the first deployment. Day-one alerts would have caught the run-up in hours instead of weeks.
Where it is now
DavisDine is live on the App Store. Students use it daily to plan around allergens and macros. The backend runs on a single F1 App Engine instance that scales to zero when no one is hungry; operating cost is under a meal swipe per month. Next is widening the cuisine model and pulling in menu data from neighboring colleges so the app travels.