The problem
People on GLP-1 medication are tracking something calorie apps and generic medication reminders do not model: which dose, which injection site, where they are on the escalation ladder, and what side effects came with each step. A hard constraint runs through the whole product — every screen touching dose or pharmacokinetics carries a not-medical-advice notice, and the app never recommends a dose change.
What it does
Logs each dose with milligrams and injection site, suggests the next site from a rotatable 3D body map, tracks your own titration ladder with prompts to discuss step-ups with a prescriber, and records side effects and weight over time. Anonymous-first: a device gets an account immediately, with optional sign-in later that merges the history.
How it's built
Deriving injection sites by scanning the mesh
A Node script parses the GLB binary by hand, extracts only positions and indices, and discards the source mannequin’s skin rig entirely — its joints have mismatched inverse-bind matrices, so evaluating the skeleton collapses the body into a blob. It then derives the six site markers by scanning at anatomical height fractions and snapping to the nearest real surface point, so no coordinates are hand-tuned and a second body figure costs nothing.
React Three Fiber on WebGPU, in React Native
The standard GL backend renders nothing on Apple Silicon simulators, so the viewer runs on a Metal-backed WebGPU canvas via three’s WebGPU renderer — which needs an async init before first render and a hand-rolled canvas shim. Because that canvas has no DOM event surface, R3F’s pointer events never fire, so tap selection is a manual raycast fed from a gesture race: drag orbits, clean tap selects.
Entitlements computed from rows, never a cached boolean
Stripe and RevenueCat webhooks write one subscription table discriminated by platform. There is no denormalised isPro flag; access is computed from the rows — active or trialing, past-due inside a grace window, cancelled before period end, or a lifetime row with no end. Each provider has its own idempotency table and an append-only audit log, specifically so a cross-provider bug is debuggable.
An edge auth bridge that strips what it is about to set
The app can’t hold a session cookie, so it presents a signed device token verified at the edge. The verification is inlined rather than imported, because the shared module pulls in a database client that isn’t edge-compatible. The security detail is that the proxy deletes any client-supplied identity header before setting the verified one, so everything downstream can trust it.
A paywall that distrusts a non-null offering
It refuses to render unless all three expected packages are present with a real localised price string, because the store silently drops products unavailable in a user’s storefront — which would otherwise show hardcoded dollar fallbacks beside a real pound price. It also hard-fails loudly if a release build resolves a test key, so a dead paywall cannot ship.
The hard part
The 3D body map, three times over: a rig broken in a way that only surfaces at evaluation time, a rendering path that is a dead end on the standard stack, and hit-testing that had to be rebuilt from a raw raycaster because the canvas has no events. The quieter difficulty is the anonymous-to-account merge — one transaction with an explicit conflict rule per unique constraint, reparenting through the parent record rather than touching child rows, and nulling the anonymous user’s unique billing columns before the real account can adopt them. The repo also records a failure honestly: this app shipped the config half of over-the-air updates without the module that drives them, so updates published happily and no device ever asked.
Stack
- Next.js
- React Native
- Expo
- react-three-fiber
- Skia
- Anthropic
- OpenAI
- RevenueCat
Built, shipped and maintained by me. Try Titrra, or see the rest of the work.
