I just published version 1.1 of Tomo Time, my simple Pomodoro timer app. It used to be called Tomatimo, ten years ago. Over the years, I got too busy to deal with the upkeep, Swift kept releasing new versions, the codebase fell further behind every year, and eventually even the UI looked dated because SwiftUI exists now and UIKit doesn’t get invited to the party anymore. The gap got wide enough that catching up felt like its own project.
A couple of weeks opened up on my calendar. I had an old repo and an AI agent ready to help me debug, so I went for it.
Sledgehammer first, precision later
Step one: upgrade the Swift version to something the latest Xcode would work with. Step two: fix the deprecation warnings, and there were a lot of them. Step three: find a SwiftUI bootcamp so I could get the UIKit out entirely.
At that point it felt like I was taking the house down to the beams and rafters just to modernize it. But it worked. The app was theoretically shippable again, assuming the old one had been.
The bug that survived seven years
There was one bug a user reported seven years ago: the app would forget where it was if it went into the background. I’d learned a lot since then, mostly about how aggressively iOS garbage-collects anything not currently on screen. The fix was simple in concept: serialize the timer state to JSON, write it to a file, and resync it to the current time when the app comes back to the foreground.
But my old data model was working against me. Old me didn’t mind double-tracking the same data — I was storing both how much time was left on a timer and when I expected it to end. That was fine when the overhead was minimal, but SwiftUI put more pressure on keeping the model clean, and adding serialization on top of that made the redundancy impossible to ignore. A running timer only cares about its end time, since remaining time is just a calculation from that. A paused or idle timer only knows its remaining duration, because it doesn’t have an end time yet. Tracking both meant every read had to guess which one was actually true.
The right shape for the data
In Kotlin, this would be a clear use case for a sealed class with data class members. Swift’s enum associated values are the closest native analog:
swift
case running(endTime: Date)case paused(remaining: Double)
That change did most of the work, and Swift made the enum Codable automatically, so I got JSON and UserDefaults serialization for free. I didn’t need a private index to track which timer was “currently running” — I could just filter for the one that said it was running. Resyncing to the current time became a matter of finding the first timer with an expiration date still in the future.
It’s Interface Segregation for data models: don’t make a type carry state it doesn’t need. Ten years of drift, and the fix that finally stuck was making the model say what it meant.