The itch
This started with someone else's design. A while back I came across a concept on X — an abandoned prototype for a subscription tracker that I genuinely liked and couldn't stop thinking about. I have a lot of subscriptions. More than I'd like to admit, and I kept catching myself forgetting how many there were, or what half of them cost, or when they'd hit. The design was dead, the problem was mine, so I figured: maybe I should build this.
I wanted to get to something real fast, so I prototyped the first version with v0 and started iterating from there — more screens, animations, polish, the works. It became a proper little web app. And then I hit a fork.
The fork
I could keep going down the PWA road, or go native. I use iOS, and some of the things I wanted — push notifications for renewal reminders, widgets, the small OS niceties that make an app feel like it belongs on the phone — are somewhere between painful and impossible from a progressive web app. So native it was, even though it meant properly learning Swift. Not from zero: I'd built a few small apps before, enough to know the basics. But nothing like this.
What it does
Cadence tracks recurring subscriptions on monthly or yearly schedules and rolls them up into run-rate totals. Anchor a current balance, an optional income, and a payday, and it projects your end-of-month outlook. That forecast is the reason the app exists — every tracker I'd tried could list my subscriptions, but none of them answered the question I actually had, which is what does my balance look like when the month ends.

Everything is local-first. Your data lives on your phone, and you can export it as JSON and take it anywhere — or import it back. No accounts, no bank linking, no server watching you.

Designing like Apple was watching
I'm meticulous with my work — sometimes to a fault — and I wanted Cadence to be right from both sides: well-engineered underneath, and genuinely nice to use. Native apps have a bar for that, and Apple literally wrote it down.
So I did something slightly unhinged: I transcribed Apple's Human Interface Guidelines into markdown files in my Obsidian vault. Every time I finished a phase of the app, I'd have an agent review the work against them. A HIG compliance check, on demand, for an app with exactly one user at the time.The one user filed a lot of bugs. Worth it — it caught real things, and it kept me honest about details I would've rationalized away at 1 a.m.
The interesting bits
The domain logic lives in CadenceKit, a separate pure SwiftPM package, so the money math is testable in complete isolation from the UI. The app itself follows the MV pattern — no view-models — leaning on @Query, @State, and @Bindable, with pure presenter structs for anything derived. Persistence is SwiftData.
The design decision I'd defend hardest: subscription charges are computed, never stored. BillingSchedule derives charge dates from an anchor and a cycle, and Forecaster projects balances over half-open windows using Decimal money. If the app instead saved predicted charges as records, every edit would turn some of them into lies — change a price, cancel mid-cycle, fix a typo in a renewal date, and now there are stale rows that disagree with reality. By deriving everything on the fly, the forecast can't drift, and the data model stays small enough to hold in your head.
every tick is derived from anchor + cycle at render time — nothing here is stored
The same derivation handles the edges for free: a yearly subscription whose next charge lands in November simply contributes nothing to July's window, and pausing a subscription just stops it from emitting ticks.

The walls
There wasn't one big wall so much as a series of them. The most annoying one wasn't technical at all: I couldn't justify the Apple Developer Program fee. I'm in the middle of moving house — renovations, furniture, the whole bleed — and €99 for a membership lost every argument with a kitchen. No membership means no CloudKit, which means no sync.
What I could do was build as if the membership already existed. The SwiftData schema is written to be CloudKit-legal from day one — no unique constraints, optionals and defaults where the sync engine demands them — so when the time comes, turning on sync should be a configuration change, not a rewrite.Famous last words — but that is exactly what the no-unique-constraints, optionals-everywhere schema rules are buying. The app stays loyal to a version of itself it can't ship yet. I find that weirdly motivating.
Where it stands
I'd say the app is about eighty percent there. Renewal reminders, a home-screen widget for upcoming charges, and backup export/import have all shipped since I started. Between the day job and the move, progress comes in bursts — but the destination is the App Store, and the remaining twenty percent is mostly the unglamorous commercial shell around a core that already works.
I built Cadence because I wanted a subscription tracker that felt personal — free to use, smooth, private, honest about whose data it is — and that could actually forecast, not just list. The apps I tried before weren't bad. They just weren't delightful, and I've stopped believing those are different requirements.
"The details are not the details. They make the design." – Charles Eames