Skip to content
Jonathan's Page

Track Updates

— Comments — life, tinker, tech, software, apps — 6 min read

It has been seven months since the last update, and Track has grown… a lot. 😅 A quick git log says over a thousand commits since April. Spring and summer were mostly about keeping the lights on, then October happened, and I couldn’t stop adding things. Here’s the tour. (The screenshots are the real app, signed in to a made-up demo account on a copy of the backend running on my laptop, so none of it is my wife’s or my actual log. 😉)

Keeping the lights on

Most of the early months were maintenance. I upgraded to Expo 56, dropped expo-camera in favor of VisionCamera (opens in a new window) alone, rebuilt the swipe actions on list rows, and swapped the old pickers for Expo UI (opens in a new window)’s native ones. Later I moved to gluestack (opens in a new window) v5 and NativeWind (opens in a new window) 5.

I also took a detour through Kubernetes (opens in a new window). I wrote the manifests for a cluster on Linode, a logging stack, health checks, and even a cost estimate… and then decided that was a lot of monthly bill for an app this small. 😂 The manifests are still in the repo, but production still runs on Docker Compose, reached through a Cloudflare Tunnel (opens in a new window).

The more useful work was resilience: the app now rides out a gateway hiccup instead of failing the request, and if the backend is really down, it goes into maintenance mode that recovers by itself once things come back. I also moved the heavy write paths (logging the basket, saving the profile, searching) into single Postgres functions, so one tap is one round trip instead of five.

The backend, today

The last post ended with Postgres (opens in a new window), PostgREST (opens in a new window), a Bun websocket service, and Better Auth (opens in a new window) behind nginx (opens in a new window). That core is still there, but it has picked up some neighbors. Each one is a small Bun (opens in a new window) service in its own container, and nginx routes to them by path:

Track's backend architectureThe iOS app, web browsers and Claude reach nginx through a Cloudflare tunnel. Nginx routes by path to web, admin, auth, PostgREST, socket, ai, lookup and integrations services, which sit on PostgreSQL. A push service drains a Postgres outbox to Expo, a render service runs headless Chromium for the ai service, and every container's logs go through Alloy to Loki and Grafana, with errors sent to Sentry.iOS appExpo · widgets · Live ActivitiesBrowserpublic site + portalClaudeMCP connectorCloudflare Tunnel → nginx (reverse proxy, TLS headers, routes by path)web/TanStack Startsite, portal, recipe linksadmin/adminTanStack Startbehind two-factorauth/api/authBetter Authsessions, OAuth for ClaudePostgREST/restthe API, generatedfrom the schemasocket/socketBun websocketsLISTEN / NOTIFY → appai/aiClaude: AI mode,check-ins, MCP toolslookup/lookupUSDA + Nutritionixkeys and shared cacheintegrations/integrationsAnyList recipesand meal planPostgreSQLtables, row-level security, RPCs,notifications, push outbox, plansrenderheadless Chromium forpages that need JS,used by aipushdrains the outbox toExpo push: nudges,reactions, check-insEvery container → Alloy → Loki → GrafanaJSON logs credited to the signed-in user · errors to Sentry · log panels in the admin site
Track's backend today: one nginx out front, a handful of small Bun services, and Postgres doing most of the work.

Logs from every container now go through Alloy (opens in a new window) into Loki (opens in a new window) and Grafana (opens in a new window), each request line is credited to whoever was signed in, and errors land in Sentry (opens in a new window). The admin site has its own log panels, so I can see what a user hit without leaving the page.

AI mode

This is my favorite addition. You can now just describe what you ate (“two eggs, a slice of sourdough with butter, and a black coffee”) or snap a photo, and Claude (opens in a new window) figures out the foods and puts them in the basket for you to check before logging. It searches USDA and Nutritionix first, reads a restaurant’s or brand’s published nutrition (PDFs included) when it can find one, and only estimates when nothing has the food. Estimates are marked as such everywhere they show up, and a photo of a plate gets cropped so each food gets its own picture.

To pick the model and effort level, I built a small eval: a set of meals with known answers, run against the real tools with Nutritionix answered from a cache. I landed on Sonnet as the default.

I also wired up a Claude connector (opens in a new window) (OAuth through Better Auth, plus an MCP (opens in a new window) endpoint), so you can ask Claude about your log or add to it from the Claude app itself.

Recipes got serious

Recipes used to be a list of ingredients with nutrition. Now they’re actual recipes:

Recipes, with tags to filter by

Recipes, with tags to filter by

A recipe page to read, cook or log

A recipe page to read, cook or log

Cook mode starts with what to get ready

Cook mode starts with what to get ready

A step’s time becomes a timer

A step’s time becomes a timer

The timer as a Live Activity

The timer as a Live Activity

A cook in progress, back on home

A cook in progress, back on home

Social

The social bits were basic before. Now you can share your day with the people you’ve connected with, and you get a feed of friends’ meals and finished days, with reactions, comments, and nudges (as push notifications). Tap a friend’s entry to log it too — handy when we eat the same dinner.

There’s also a weekly check-in, written by Claude, for just you or a group of up to four. It looks at the week as a whole: goals, trends, health numbers, and days you mark as a fast, so a low day reads as intentional instead of a miss.

The feed: friends’ meals, named from their day

The feed: friends’ meals, named from their day

A friend’s day, with reactions and a nudge

A friend’s day, with reactions and a nudge

And the rest

The home screen widget

The home screen widget

The basket, waiting to be logged

The basket, waiting to be logged

Stats over the last month

Stats over the last month

The nutrition report, as a PDF or CSV

The nutrition report, as a PDF or CSV

The menu, with the weekly check-in

The menu, with the weekly check-in

Track is still a private beta, and it’s not headed to the App Store. Still, it’s been a blast to build. As always, feel free to reach out if you want to know more or see a demo!

A shout-out

None of this would have happened at this pace without Claude Code (opens in a new window). Of the 1,089 commits since the last post, 730 were co-authored with it. It wrote code, chased bugs on the simulator, drafted plans, and did the big refactors I never would have had the patience for. I still decide what Track should be and review what lands, but it turned a side project’s evenings into a lot more app. Thanks, Claude! 🙌 (It even helped write this post and draw that diagram. 😉)