Every Sport, One Rank
A shipped iOS product, designed and built end to end
02
Year — 2026
Categories
Role
- Independent product — ASCENDY, on the App Store
- Product & UX, SwiftUI client, Firebase backend, Cloud Functions
- Store listing, pricing, legal and support surfaces
- Submitted to Apple — June 2026
iOS 17.1+ · App Store · v1.0.3 (build 4)
People who train seriously rarely train one way, and every app on their phone records exactly one slice of it. ASCENDY gives running, lifting, Muay Thai, meditation and cold exposure a single currency — Grind Points — and one rank ladder they all count toward. I designed the product, built the SwiftUI client and the Firebase backend, shipped the subscription, and wrote the store listing. 34,833 lines of Swift, submitted to Apple in June 2026.
Problem
A serious week might hold two runs, three gym sessions, a Muay Thai class, a long walk, an ice bath and an hour of reading. Strava owns the run. A lifting log owns the gym. A calendar owns the intent, and the notes app owns everything that did not fit. Nothing owns the person.
Fragmentation has a second cost: effort scattered across five tools never accumulates into anything you can see, so consistency stays invisible at exactly the moment it needs to feel earned. Solo tracking is easy to abandon — and the open fitness networks that fix that do it by turning training into an audience problem. Meanwhile the apps that do offer identity look identical for everybody using them.
The bet
The bet is that the unit of a training product should be effort, not exercise. Every activity carries an effort multiplier; minutes times that multiplier become Grind Points; points accumulate onto one rank ladder that does not care which discipline earned them. Sixty-eight activities across nine categories — and sixteen of them are not sport at all: reading, deep work, meditation, journaling, cold exposure, sauna.
Scoping it was mostly deciding what not to build. It is not a set/rep/RIR strength log, not a GPS segment network, not a coaching engine with prescribed periodisation. Each of those is a specialist product with an incumbent, and each would have pulled the app back toward a single discipline — which is the exact failure it exists to fix. Breadth, identity and accountability are the centre; specialist depth is deliberately out of scope.
Reading scores 0.3×. An ice bath scores 2.0×. The point is not that they are equal — it is that they are on the same ledger.
Scoring
The scorer is deliberately boring and completely deterministic: duration × multiplier, plus explicit bonuses — +25 for photo proof, +10 for each additional session the same day, +30 at three hours or longer, +100 for a perfect-week contribution — plus one-time milestone awards at 10, 25, 50, 100, 250 and 500 completed sessions. Totals map monotonically onto eight rank thresholds, from Rookie at zero to Ascendant at 300,000.
Rank is where progression meets identity. Six badge lineages rename and re-illustrate all eight tiers — Classic, Empress, Divine, Flutterby, Warpath, Flora — without moving a single threshold, so two people wearing completely different art remain directly comparable. Forty-eight pieces of rank art, one ladder underneath.
Streaks count consecutive days with at least one completed session, and the calculation begins from yesterday when today is still empty — a small mercy that stops the app declaring a streak dead at 9am on a day the user has not finished yet.
The Pro insight surface applies the same arithmetic honesty to measurement. A consistency score composites eight weeks into a number out of 100 — 35 points for frequency (four sessions a week maxes it), 25 for how many of those eight weeks were active at all, 25 for completion rate, and 15 for streak progress toward thirty days — and a rank forecast projects from the average non-zero weekly points of the last four. All of it is derived on device from the user’s own sessions: the only analytics in the product are the user’s own.
The system
- Activity types shipped
- 68
- Categories
- 9
- Effort multiplier range
- 0.3×–2.0×
- Rank tiers
- 8
- Rank artworks
- 48
- Themes, light and dark
- 29
Designing the loop
Version 1.0.3 collapsed planning and logging into one sheet with two live tabs. Both stay mounted while you switch, so a half-entered plan survives the decision to log something that already happened instead. Plan takes a date, a start and a duration in five-minute steps and can repeat daily or weekly to the end of the month; Log back-calculates a start from the duration and finishes at now.
One overlap rule governs both — a proposal conflicts when it starts before an existing session ends and that session starts before the proposal ends — and it is reused verbatim by manual planning, quick logging, repeat creation and the week planner, so four features cannot disagree about what a clash is. Sessions may touch at an endpoint. A repeat that collides on one date still creates every other date.
Two decisions took the most thought and are the ones I would defend hardest. Quick Log persists the completed session before it touches the photo — downsize, cache, upload, write the URLs back, then apply the points — so a failed upload leaves a valid unverified session instead of eating the workout. And a planned session more than two days old renders as expired through a derived status rather than a nightly job rewriting stale documents: presentation logic instead of a cron and a migration.
Suggestions and the week planner are on-device heuristics, not a model, and I describe them that way on purpose. Suggestions prefer favourites that have not come up in two days, discover from categories missing in the last ten sessions, avoid two picks from the same category in a row, and take duration from that sport’s median, then the overall median, then a 45-minute fallback. Plan My Week fills the days you say you are free, avoids repeating a sport on consecutive sessions, and reads its times from your own history.
Accountability, without an audience
The social layer is a closed accountability graph, not a content network. You find people by username prefix against a public-profile mirror that exposes only the discoverable subset of a user record, exchange friend requests, and share the sessions you choose to share. There is no direct messaging, no follower count, no explore tab.
The differentiator is small and it is my favourite thing in the app: feed cards render in the poster’s theme, not the viewer’s. Scrolling the feed shows you other people’s aesthetics instead of repainting everyone into your own palette — which quietly turns customisation from a settings screen into something other people actually see.
Groups carry an eight-character invite code that excludes visually ambiguous characters, a 20- or 50-member capacity, and challenges that can target session count, minutes, points or one specific activity. Leaderboards run over today, this week, this month and all time across friends, group and region; the regional board loads the top 199 and then pins the viewer’s own row with a count of how many people sit above them, so placing 4,000th still tells you something.
The live map is the feature I was most careful with. It publishes a user only while a planned session is actually running, rounds the coordinate to 0.005° — roughly a 500-metre blur — expires the presence document at the session’s finish, and asks for approximate location contextually rather than at launch.
Architecture
The client is SwiftUI — 90 files, ~34,833 lines — over Firebase Auth, Firestore, Storage and Messaging, with four TypeScript Cloud Functions that turn Firestore events into push notifications. The tab shell keeps every tab alive in a ZStack so returning to a screen preserves its state, and feature screens subscribe to app-level events for session saves, completions and deletions, so Home, Calendar and Social update without a relaunch.
Subscriptions run on StoreKit 2, and the part worth reporting is a bug fix. Purchases are stamped with the Firebase user’s account token for audit, but entitlement is deliberately not gated on it — the Apple ID owns the purchase. That single decision fixed restores that had been failing after account changes, Family Sharing and sandbox testing. The service also waits 1.5 seconds and re-checks before it will ever downgrade an active subscriber, because StoreKit’s cache races on foreground.
Voice planning is speech-to-text plus a rule-based intent parser — on-device recognition where the device supports it, no audio recording retained or uploaded — and it shows the parsed session for confirmation before anything is saved. Text moderation runs on device too: lowercase, strip diacritics, map leetspeak, tokenise, then test both tokens and multi-word phrases before publication. The privacy manifest declares no tracking, there is no advertising or analytics SDK in the binary, and location is coarse by construction.
A restore bug taught the sharpest lesson in the codebase: the Apple ID owns the purchase, not my user record.
Build
- Swift files
- 90
- Lines of Swift
- 34,833
- Cloud Functions
- 4
- Firestore collections
- 11
- Tracking SDKs shipped
- 0
- Ascendy Pro
- $2.99 / mo · $29.99 / yr
Auditing my own product
Before writing any of this down I audited ASCENDY against its own source and graded every claim — implemented, public claim, strategy, or roadmap — with production code ranking above the website and the website above my own notes. The point was to be able to say what the app does without checking the marketing copy first.
It found things, and the uncomfortable ones are the useful ones. The website says 72 activities, the store frame at the top of this page says 60+, and the enum holds 68 — three public numbers for one countable fact. “30+ themes” is only true once the six badge lineages are added to the twenty-nine themes. Suggestion cards estimate points with a +20 constant the real calculator never applies, so a preview can read about 20 GP high. The interface presents a 1.0×–2.0× streak multiplier the scorer deliberately does not apply. “AI week planner” describes a local heuristic, not a model. Several privacy behaviours the product promises are enforced by client-side filtering rather than at the database rule layer — presentation, not security. And there is no automated test suite.
None of that is comfortable to publish and all of it is now written down and ranked, with an ordered remediation list: make the trust boundary true at the rule layer first, put the estimate and the scorer behind one function second, generate the marketed counts from the enum so the copy cannot drift again third, build a test spine around scoring, streaks, conflicts and entitlement fourth. Ranked, they become work I can schedule.
Outcome
ASCENDY is on the App Store as a free download with a $2.99 monthly or $29.99 annual subscription and a one-week trial, behind a website carrying its own privacy policy, terms, help centre and public feedback board. Version 1.0.3 is 90 Swift files, four Cloud Functions, eleven Firestore collections and no third-party analytics.
What the project actually taught me is not Swift. It is that a consumer product is a chain of decisions that each have to survive contact with someone who did not make them — a reviewer, a subscriber whose restore fails, a user reading a privacy policy, a friend who sees a themed card in a feed. Building the whole chain alone means every one of those links is yours: the multiplier table, the paywall copy, the deletion cascade, and the App Store frames on this page. The distance between “it works on my phone” and “it is a product” is made almost entirely of those.
Next case study
03 / 11