Case study
Well-Plated
Senior engineer on an eating-disorder recovery platform's three store apps, building the release pipeline that ships them and fixing an App Attest bug that broke every returning sign-in.
- Client
- Well-Plated
- Dates
- 2025 to now
- merged PRs
- 357
- store listings live, iOS and Android
- 6
- app releases, July to October 2026
- 67
- React Native
- Expo
- TypeScript
- Supabase
- Postgres
- GitHub Actions
- EAS
- Playwright
The problem
Well-Plated supports people recovering from eating disorders. A client logs meals, a caregiver supports them, and a clinical provider follows their progress, each in their own app. Three apps, each on iOS and Android, each with development, staging and production, all on one Supabase backend holding health data.
Shipping that safely is its own job. Each promotion has to reach the store listings of every app that changed, six in all, never ahead of the backend they depend on. A release that quietly doesn’t ship is as bad as one that ships broken, and with health data, sign-in has to work every time.
My role
Senior software engineer since December 2025, on a small team. The product and clinical features are largely my colleagues’ work. My part is the release pipeline: I wrote most of the repository’s CI workflows, including the production deploy, along with auth reliability and the tests that drive all three apps together.
What I built
- Backend deploys. Supabase migrations are checked on every pull request, deployed automatically on merge to each environment’s branch, with a check that catches two migrations claiming the same timestamp.
- Release trains. Each app is versioned on its own with Changesets, and every merge to production tags and publishes a release for each app that changed, from its changelog. A check catches a change to shared code that reaches an app without a version bump.
- Store gates. Android releases used to be parked as drafts; they now ship on merge, and only once the backend deploy they depend on has succeeded. iOS builds go from TestFlight to App Store review on their own, and a person only presses Release once Apple approves. Before building, CI asks App Store Connect whether a version is already approved, and drops the apps that haven’t moved on. A daily job reports any drift between the code and the stores.
- Tests across the apps. Playwright drives the real web builds of all three apps through the golden paths: pairing a client with a caregiver and provider, a meal logged on one side and seen on the other, safety alerts, and sign-in. Staging builds only reach TestFlight and Google Play once the pairing, safety-alert and sign-in paths pass.
Architecture
The three Expo apps share one Supabase project per environment: Postgres with row-level security, and Edge Functions for anything privileged, including device verification at sign-in. A merge to develop deploys the backend; merges to staging and main also build the apps, in parallel with the backend deploy, and hold each store submission until that branch’s gate passes: the golden paths on staging, the backend deploy for Android in production.
The hard part
The device check that broke every returning sign-in
The iOS apps prove at sign-in that they’re running on a real device, using Apple’s App Attest. A new install registers a key with Apple, and every later sign-in signs a challenge with that key, which the server verifies.
In late May 2026 the team reported “Unable to verify device” errors, and the server’s records showed no key had ever been registered on staging. Every error looked the same, so I added logging to the verification function and error reporting to the app that named the exact step that failed. The first bug it pinned down was on the device: the app was asking Apple to register a key Apple had already registered, and I fixed that.
Then a second failure surfaced. Signing out and back in failed on the client and caregiver apps, and the only workaround was to delete and reinstall the app, which worked exactly once per install. The new reporting narrowed it down: the stored key was genuine, the app and the server agreed on what had been signed, and the signature still failed.
The cause was the signature’s format. Apple sends an ECDSA signature as DER, a structure holding the two numbers that make it up. The Web Crypto API the server used expects the same two numbers as one raw 64-byte block. Handed DER, verification doesn’t throw an error; it returns false. So the check for returning users had never once succeeded. Once the first bug was fixed, fresh installs worked, because registering a key checks a certificate, not a signature; signing out is rare, which is why nobody had hit the second. Both were fixed before the first app reached the App Store.
The fix was small: convert the signature before verifying it, with a test that round-trips a real signature, and let the app re-register once if a genuine rejection comes back. I verified it on real devices on staging.
The postmortem I wrote recorded six issues from that week, one of them caught by a colleague’s new cross-app test. Among them, signup flattened every error into one generic message, so a rate limit looked like a bug, and a provider endpoint read clients’ meals through the wrong database client and always reported no meals; I fixed both. And existing tests wrapped their assertions so they couldn’t fail. The prevention work that followed was mine: a sign-out and sign-in checklist on a physical device in the release pull request template, error reporting on the auth functions’ failure paths, a rule that signature code ships with a known-good test vector, integration tests on pull requests to the development branch, and seven dead functions removed.
Outcome
- All three apps are live on the App Store and Google Play, the first on the App Store in June 2026.
- 67 per-app releases tagged and published by the release workflow between July and October 2026.
- Releases that quietly hadn’t shipped now do. Android had been up to five versions behind, its releases parked as drafts; it now ships to everyone on merge once the backend is deployed. iOS had gone a month without a submission; builds now go to App Store review automatically, and a person only presses Release.
- Returning iOS users can sign in, and a broken verifier can’t hide again: signature code has to prove it accepts a real signature.