Hybrid
A training app on the App Store. Type your session as a sentence, import it from Apple Health, and get a score that makes an hour of yoga and an hour of intervals comparable.
Free · iOS 15.1 or later

On the App Store, free, in Sports, rated 16+Rejected by Apple review on the first build, then approvedEvery score is a TRIMP, weighted by heart rate zone, not by timeTraining one sport only caps you: variety is worth up to +25%A session can be typed in plain language and parsed into typed JSON
The problem
Training with other people has an accounting problem nobody wants to admit. An hour of yoga and an hour of intervals are both an hour, so any app that ranks people by duration, by reps or by sessions completed rewards whoever picked the easiest sport. Put four friends who run, lift, cycle and play padel on the same leaderboard and it stops meaning anything.
The second problem is smaller and more annoying. Logging four sets of ten at eighty kilos, then three sets of twelve, then twenty minutes on the bike, takes longer through a form than the workout deserves. Most people give up on logging before they give up on training.
What it does
Build a session across sixteen sports, then invite friends directly or push it to a group. Everyone runs the same plan and the results land side by side.
Describe the session in a sentence instead of filling a form, and it comes back as a structured plan with exercises, sets, weights and blocks, ready to correct before saving.
Record on whatever watch you already own. Afterwards, import the session from Apple Health: the heart rate samples are sliced into five zones, each minute is weighted by the zone it was spent in, and the number that comes out is what the leaderboards rank. That is the whole point of the app in one sentence.
Spread that effort across running, lifting, team sports and mobility and the month's total gets a multiplier. Pile it all into one week and the app says so, because it also watches the ratio between your last seven days and your last twenty-eight.
- Distribution
- App Store, free, rated 16+
- Requires
- iOS or iPadOS 15.1, Mac on Apple Silicon, Apple Vision
- Scoring
- Edwards zone TRIMP, with a MET based fallback
- Heart rate
- Read from Apple Health, never written back
- Backend
- Supabase: Postgres, auth, 81 row-level policies
Architecture
- Apple HealthWorkouts, heart rate
Hybrid
- HybridExpo, React Native
parse-workout, Postgres
- parse-workoutDeno edge function
Language model
- PostgresRow-level security
- Language modelStructured output
Expo and React Native client
One codebase behind the shipped app. Colours, spacing and radii come from a single token file, so a value cannot be typed by hand inside a screen and quietly drift.
Postgres behind row-level security
Eighty-one policies decide who reads what. A friend sees your score and your session, never your account, and the client is never trusted to restrict its own queries.
TRIMP+ scoring
Turns a stream of heart rate samples into one comparable number: time in each of five zones, weighted one to five, then a variety multiplier and a seven against twenty-eight day load ratio on top.
parse-workout edge function
A thin Deno proxy in front of the language model. It exists so the API key lives on a server instead of inside an application anyone can unzip.
Apple Health import
Reads workouts and heart rate samples, writes nothing back, and keys every import on the Apple workout identifier so importing twice does not create the session twice.
Built with
Client
- Expo and React Native
- The single codebase behind the build submitted to review
- React Native Paper
- Components, themed from the one token file that owns colour and spacing
- Zustand and React Query
- Zustand holds the running session, React Query holds everything the server owns
- react-native-healthkit
- Reads workouts and heart rate samples, with no write permission requested
Backend
- Supabase Auth
- Email, Sign in with Apple and Google, with tokens kept in secure device storage
- Deno edge functions
- Six of them: workout parsing, push notifications, load warnings, account deletion
- pg_cron
- Hourly sweep that closes sessions the app never got to close itself
- Postgres realtime
- A friend finishing a session moves your leaderboard without a pull to refresh
Scoring
- Edwards zone TRIMP
- Minutes in zones one to five, weighted one to five, summed
- Tanaka estimate
- Fills in maximum heart rate from age when the athlete never measured theirs
- Compendium MET values
- Scores a session that arrived with no heart rate, so a leaderboard has no holes
- Acute to chronic load ratio
- Last seven days against the last twenty-eight, to say when to back off
Every stage runs on hardware I own
Under the hood
01The score is an integral of heart rate, not a stopwatch
Ranking by duration rewards whoever picked the gentlest sport, and ranking by a perceived effort slider rewards the optimist. So a session is cut into its time in five heart rate zones, each minute counts for one to five points depending on the zone it was spent in, and the total is the score. Maximum heart rate comes from the athlete's profile when they know it, and from their age when they do not. The cost is that the number means nothing without heart rate, and plenty of logged sessions were never recorded on a watch. Those fall back to a metabolic estimate, a published MET value per sport times duration times body weight, calibrated so an hour of running lands roughly where the zone formula would have put it. An approximation, treated as one, because a plausible number beats a hole in a table people compare themselves against.
02Being good at one sport is capped, on purpose
A monthly total that only counts effort makes the correct strategy obvious: pick your strongest sport and repeat it. That is a fine way to get injured and a poor definition of an athlete. So the month's minutes are spread across five families, aerobic, power, anaerobic, team sport and mobility, and the multiplier comes from how evenly they are spread: the coefficient of variation across the five, inverted, capped at plus twenty-five percent, and worth nothing at all until at least two families have minutes in them. It is a value judgement written into a formula, which is the honest way to have one.
03Two scores are written per session, on purpose
An app on the App Store and the database behind it do not upgrade at the same moment. A phone still running last month's build talks to today's schema, and a fresh build can meet a database whose migration has not run yet. So the old score is computed and persisted unconditionally, and the new zone based score is written afterwards inside a try/catch that is allowed to fail. Every read then takes the new score if it exists and the old one otherwise. It looked like clutter while writing it. It is the reason the scoring rewrite shipped without having to coordinate a release with a migration.
04The parser is allowed to say it did not understand
Free text into structured data is exactly where a language model will invent rather than admit defeat. So the response is constrained by a JSON schema instead of being parsed out of prose, the temperature sits near zero, and the model returns a confidence value alongside the plan. Below the threshold nothing is saved: the app shows the clarifying question the model asked and waits. A silently wrong set of weights in someone's training history is worse than a prompt for one more detail.
Gallery




Try it
It is on the App Store. That is the demo.
There is no sandbox to build and no demo account to hand out. It is on the App Store, it is free, and it installs like any other app. Scoring works without a watch: describe a session, save it, and the metabolic fallback gives you a number.
Heart rate zones need a session recorded on a watch and imported from Apple Health.
What broke
Apple rejected the first build, and not for a bug
Build one was refused on two guidelines. There was no way to delete an account from inside the app, and the Sign in with Apple flow asked again for a name and an email that Apple had already handed over. Both were policy, both were knowable weeks earlier, and both cost a full review cycle to find out. Account deletion is not a feature you bolt on at the end either: it decides what happens to the groups and workouts a leaving user created, which is a schema question. The rule taken from it is to read the guidelines covering authentication and account lifecycle before writing the authentication screens.
Sessions that never ended locked people out of starting new ones
Force quit the app mid workout and the row stayed in progress forever. The next time that user pressed Start, a guard rejected them for already having an active session, and the client only logged the error to the console: the button simply did nothing, with no message. It took someone telling me in person to find it. The fix had to land at four levels, from resuming the existing session instead of refusing it, up to an hourly job that closes stale sessions for phones that never come back. The lesson is narrower than it looks: a guard that can refuse must own the way back out, and an error the user never sees is a bug that reaches you as silence.
Row-level security instead of trusted client queries
Writing eighty-one policies on a solo project felt like ceremony, and the day an avatar upload started failing without a single error anywhere, it looked like proof of that. The cause was one missing check clause on one policy. But the same strictness is why a leaderboard that joins across users could ship without auditing every query for a leaked column. A database that refuses by default is worth more than the afternoon it costs.
There is no pipeline, and the review agents run when I remember
The repository has seven specialised review agents, for design tokens, accessibility, performance and security, and they earn their place. They are also invoked by hand. There is no continuous integration and no test suite, so nothing refuses a commit that breaks a contrast ratio or hardcodes a spacing value. Calling that enforcement would read better than the truth, which is that it is a checklist with good ergonomics.
Links
Source
Not public
Not public.