Tally - Time Tracker
Cross-platform time tracker for freelancers and small teams - native iOS, Apple Watch, Mac, and a React web dashboard on one shared backend. Free to start, $9.99 one-time for Pro with invoicing and reports, or $4.99/month for Business with team collaboration.
The Problem
At the Apple Developer Academy, I was on a team of five building for a real client. We needed one place where all five of us could log hours, stay in sync, and show the client what we'd actually worked on. Every tool we tried was either built for solo freelancers with no team layer, or bloated enterprise software that cost more than the project was worth.
I built Tally to solve that exactly — a time tracker that works for one person or a whole team, runs on every device they already use, and doesn't charge a monthly subscription just to log hours.
Web Landing Page
The marketing site includes iOS screenshots, a live demo, pricing, and App Store integration - fully responsive down to mobile.
Mobile Experience
The native iOS experience is powered by SwiftUI, ActivityKit Live Activities, and WidgetKit, paired with a fully responsive web landing page and dashboard.
Landing page - mobile layout
iOS App
Timer
Reports
Activity
Team
Client earnings
Invoicing
Widgets
Live Activity
Settings
Android Beta
Track Time
Client Rates
Activity
Reports
Team Dashboard
Sessions
Architecture
Platforms. Tally is a native iOS app (iPhone, Apple Watch) and a separate native macOS target — not Catalyst — paired with a React web dashboard and a Capacitor Android app, all running on a shared Supabase backend. The Mac binary compiles as a true Mac App Store app with platform-specific UI: sidebar navigation, Mac-native auth (no GoogleSignIn SDK on Mac), and proper form styles. iOS and Mac share the same SwiftUI source files; iOS-only APIs are excluded at compile time with #if os(iOS) guards. A native visionOS app is in development — a floating volume timer that stays visible alongside other apps so freelancers can track time without context switching.
Payments. Buying Pro on any platform unlocks every other — RevenueCat keeps iOS (StoreKit), Android (Google Play Billing), and web (Stripe) in sync through a single entitlement, so any platform can verify Pro status without knowing where the purchase happened. Universal Purchase on iOS and Mac means one App Store payment covers both.
Push notifications. A Deno Edge Function on Supabase handles APNs delivery — it signs an ES256 JWT per request, routes each token to sandbox or production based on build type, and auto-removes stale tokens on 410. Device tokens are registered on first launch and the admin can broadcast to all iOS and Mac users from the web dashboard.
Automated email. A weekly digest fires every Monday with each user's hours and earnings. A re-engagement email goes out to users who tracked sessions 7–30 days ago but have gone quiet. Both run as Vercel cron jobs and send via Resend from a verified custom domain.
Auth
Authentication supports email/password, Sign in with Apple, and Sign in with Google on both iOS and web. The web app includes a full forgot password flow - users request a reset link, Supabase emails it via Resend over custom SMTP, and a recovery token handler in the React app detects the link regardless of where Supabase redirects and presents the reset form.
A built-in bug report modal is surfaced on the landing page, login page, and inside dashboard settings - so users can report issues from anywhere without leaving the app.
Web login
Business Tier
The Business tier adds team workspaces with admin and member roles enforced at the database level via row-level security - not just in the UI - plus a shared weekly hour goal per workspace so teams can track progress against a client contract. Invoice generation pulls from tracked sessions with draft → sent → paid status tracking, outstanding balance summaries, and one-click email delivery to clients via Resend. Stripe webhooks, transactional email, and team invites all run in Supabase Edge Functions (Deno), so there's no Node server to maintain or deploy.
Invoicing is powered by Stripe Connect - each user connects their own Stripe account directly from the iOS app, so payment goes straight to the freelancer rather than routing through a platform account. This required implementing the Connect OAuth flow in SwiftUI, handling token storage securely in Supabase, and generating Stripe-hosted payment links at invoice creation time. The result is a full billing workflow - track hours, generate an invoice, collect payment - without the user ever leaving the app or setting up a separate payment tool.
iOS Integration
Lock screen Live Activity
The iOS app integrates deeply with the OS: ActivityKit powers a Live Activity on the lock screen and Dynamic Island so the running timer is always visible without opening the app. WidgetKit adds a home screen widget in small and medium sizes. App Intents expose Siri shortcuts for starting and stopping a timer, and a Focus Mode filter lets Tally automatically activate when a work focus is on.
Apple Watch
The Watch app lets you start, pause, and stop a timer from your wrist — no phone needed. It syncs to the same session in real time.
Tap to start
Timer running
Pause & resume
iPad
The iPad layout uses the extra space for a persistent sidebar — Timer, Reports, Activity, and Team are always one tap away without covering the main view.
Native macOS App
Tally on Mac is a fully native macOS target — not Catalyst, not "iPhone & iPad Apps on Mac." It compiles a true Mac binary, ships in the Mac section of the App Store, and uses Mac-native UI patterns throughout: sidebar navigation, Mac-specific auth (Apple Sign-In and email/password — GoogleSignIn has no Mac SDK), proper form styles, and no iOS sheet behaviors that don't translate.
The Mac and iOS targets share the same SwiftUI source files via Xcode's synchronized folder groups. iOS-only APIs are excluded at compile time with #if os(iOS) guards across the shared views. A matching bundle ID enables Universal Purchase — buy Pro or Business on iPhone and it unlocks on Mac automatically, no second purchase.
Core Features
- Timer with pause/resume and manual session entry
- "Log again" shortcut - re-open the same client and task from any recent session
- Weekly hour goal with per-client sub-goals and progress bars
- Budget tracker per client - progress bar turns red when over budget
- Reports showing hours and earnings side-by-side with period-over-period comparison
- Streak tracking - consecutive days logged, highlighted at 3+ days
- Session calendar and CSV export
- Web app adds voice control - say "start", "pause", or "save" to control the timer hands-free
- Upgrade modal triggered at the client limit - highest-intent upgrade moment surfaced in-context rather than as an error string
- Automated re-engagement emails to lapsed users via Vercel cron + Resend
- Admin push notification broadcast — send APNs messages to all iOS and Mac users from the web dashboard
Web Dashboard
Built with React and Vite - a time tracker has no SEO requirements, so it deploys as static files on Vercel with no framework overhead. The public demo account is seeded with realistic sessions that automatically adjust to the current week - a GitHub Actions cron job runs nightly to delete and re-seed data, so the dashboard always shows meaningful numbers no matter when someone visits.
What Was Hard
This was my first cross-platform project - keeping iOS, watchOS, Mac, and a React web app in sync against one backend required every layer to be correct at once.
Payments were the other wall. Wiring StoreKit and Stripe to write to the same subscription record - so iOS Pro unlocks the web without checking two systems - took longer than any single feature. Getting App Store approval took nearly 10 rejections. Each one came back with a different guideline violation, and I worked through every one until it shipped.
Building the native macOS target added a different layer of complexity. GoogleSignIn has no Mac support, so the Mac target needed its own auth flow. iOS-only APIs — UIApplication, UIImpactFeedbackGenerator, presentation detents — had to be guarded with compile-time checks across dozens of shared views. The entitlements, Info.plist, and xcconfig had to be threaded through the project manually since the Mac target shares iOS source files via Xcode's synchronized folder groups.
User Research
After launch, I manually reviewed the email addresses of users who had created accounts and reached out to 30 of them directly. Most didn't respond — but one did: a senior product designer who has become an ongoing collaborator and the clearest signal I have of who Tally is really for.
Her feedback drove several concrete product decisions:
- Billing cycles per client — she bills different clients on different schedules. I added monthly (by day of month) and weekly (by weekday) billing cycles, plus a "Last Billing Period" button on invoices that auto-fills the correct date range.
- Client settings in the Account tab — she wanted rates and billing cycles accessible without leaving the main app flow. I moved Client Settings into the Account tab with TipKit contextual tips on first use.
- Invoice persistence — name, email, and tax rate now persist across invoices so she isn't re-entering the same info every time.
- Billing cycle reminder notifications — a daily push fires at the start of each billing period ("Time to invoice Acme Corp.") so she never misses an invoice window.
One high-quality respondent who explains their workflow is worth more than 100 survey responses. The app is increasingly tailored to how professional designers manage client work, and every feature in the current version traces back to either a real user need or a specific piece of feedback.
Tally for Apple Vision Pro
Open Tally as a volume and it floats alongside your work — a live timer you can see without switching apps. Tap to pause, tap to resume. Your client hours tracked without breaking focus. Part of my work at the Apple Developer Academy.
Learn more ↗
The Outcome
Nearly 300 downloads and 18K+ App Store impressions since launch — built and shipped solo. Tally is live on the iOS App Store, in beta on Google Play, and the Mac version is in review. The same account works across all of them.
It's the most complex thing I've built — not because any single piece is hard, but because five platforms, two payment systems, and a shared backend all have to be correct at once. It all has to work, and it does.