Duetro: a workout log built for two
An iOS app for couples who train together on different routines, with a live view of each other’s workout from across the gym.
iOS
- Swift 5
- SwiftUI + Observation
- ActivityKit + WidgetKit
- Swift Charts
- SQLite
- Sign in with Apple
API
- TypeScript
- Hono on Cloudflare Workers
- Durable Objects + WebSockets
- Prisma
- Postgres on Neon via Hyperdrive
Testing
- SwiftPM package over the shipping files
- Eight API suites, one over a WebSocket
- A design gallery inside the app
Project overview
Team: just me. Product, design, iOS and backend
Created at: Side project, August 2026 to present
What I worked on:
SwiftUI app
Design system
Realtime presence
Offline-first sync
Workers API
Live Activities
My girlfriend and I train together, but never on the same routine, so every session meant craning across the gym to see what set the other was on. Duetro is the app I built so we could stop craning.
Duetro: log together, grow together
The challenge:
Two people, two phones, two routines. Partners rarely lift the same weight, gym Wi-Fi is hostile, and nobody wants to tap refresh between sets.
So the rule became: share the moment, never the numbers.
A pair of colours
You’re blue or pink, and your partner is always the other one, on both phones.
Watch from across the gym
A live card lands on Home when they start. One tap swaps between their lane and yours.
Cheer, then say it
Cheers burst where you throw them. A note arrives as you type it.
Log with one thumb
Weight, reps, tick. Rest starts itself, and the plates draw themselves.
Finish, together
One sheet to confirm, then both axolotls celebrate.
Progress for two
The week for both of you, then every lift with an estimated max over time.
Under the hood:
One room per couple
A Cloudflare Durable Object per couple over a hibernating WebSocket, named server-side from the token.
Offline first, for real
Writes queue in SQLite with client-assigned ids, fold as you type, and one pure function decides retry or give up.
Rest as an instant
A rest is sent as an end time, so one message covers it and the Live Activity counts down alone.
Replay what you missed
A reconnect re-sends presence, and a pending consent request rides the socket upgrade.
Consent, not access
Editing a partner’s weights goes through a gate they can revoke. Mirroring syncs completion only.
A design system as code
One variable Nunito file, 2pt borders, 4pt lips, no shadows, and a press with zero animation.