Two hours to a departure board.
I wanted one thing: to glance at my wrist and know when the next train comes. No app to open, no tapping. Two hours later the thing worked. Getting it onto the watch took the rest of the afternoon.
The problem
What I actually wanted was the platform sign, on my wrist.
That is what a watch complication is: a small live number sitting on the watch face, updating itself without being asked. So I built one.
On the wrist
Here it is, on an actual watch.
Three views: the app, the complication doing its job, and the complication deliberately doing nothing. Everything after this is how it got there.
MI and the following train gets THEN 9M.The shape of it
A number, in a circle, roughly 40 points across.
The whole app reduces to one question: how many minutes until the next train? Everything else is in service of showing that number honestly in a space smaller than a dime.
There are two of them, because I have two commutes, and they are awake at different times:
Morning, into Seattle
- Platform: Mercer Island, westbound
- Awake: 7:00–9:00 AM
Evening, toward Redmond
- Platform: Westlake, eastbound
- Awake: 4:00–5:30 PM
Data comes from OneBusAway Puget Sound, the same live feed behind the official Sound Transit apps. Real vehicle positions, real predictions.
The interesting part
You get about forty updates a day.
Here is the constraint that shapes everything. A watch complication cannot poll. The system hands out something on the order of a few dozen refreshes per day, total, and it decides when. You do not get to ask for more.
A naive version burns through that budget by three in the morning, refreshing all night for nobody, and is stale exactly when you are standing on a platform.
Sleep when it doesn't matter
Outside my two commute windows the complication makes zero network calls and tells the system to wake it at the next opening. Nearly the whole daily allowance lands in the three and a half hours I actually care about.
One fetch covers an hour
Refreshes are rationed, but pre-computed display frames are free. A single fetch generates a frame for every upcoming minute, plus one at each train's exact arrival — one request, a full hour of accurate counting down.
Admit when it's guessing
Between refreshes you are counting against a snapshot up to fifteen minutes old. Frames older than twenty minutes render dimmed — the display says it might be wrong rather than lying confidently.
What the data does when you aren't looking
Transit feeds lie in specific ways.
Most of the real work was not the interface. It was discovering how a live public feed misbehaves, and refusing to pass that through to the number on my wrist.
The countdown to 1970
When no train is actually reporting its position, the feed does not send a null prediction. It sends 0. Trust that field and your watch face confidently counts down to January 1st, 1970. The fix is to treat zero as absence and fall back to the timetable — and to say which one you are showing.
Predictions that were never predictions
The other endpoint is subtler and worse. It always sends an expected arrival time — quietly copying the scheduled time when nothing is live. Infer "this is a real prediction" from the field being present and every timetable guess gets a live indicator. There is a separate flag that carries the truth, and it is the only thing worth believing.
"At stop," thirty minutes away
A train with an uncomputed position reports negative one stops away, bundled with a friendly human-readable label reading "at stop." Pass that through and a train half an hour out appears to be arriving now. That is the failure that makes you miss a train while looking at the thing that was supposed to help.
And one that only shows up downtown
Westlake is an underground station where two lines share a platform. Query it without filtering and you get four trains to Federal Way interleaved with four to Redmond, in equal numbers, all looking identical. Mercer Island never surfaces this — it is a single-line stop. Filtering by route there is defensive; downtown it is the difference between working and useless.
A choice that got reversed
The clock that didn't fit.
The first version used the system's built-in countdown text — the kind that animates itself for free, no work required. Obviously correct, and it was wrong.
A timer renders as minutes and seconds: 12:50. That is five characters. In a sub-dial roughly forty points across, those digits overflow the circle and get clipped into unreadability at exactly the worst moment.
Whole minutes fit with room to spare. And the seconds were false precision anyway — nobody sprints for a train because it says forty-seven seconds. The free animated version went in the app's list view, where there is width for it, and the complication counts in plain minutes.
Where it actually stands
Running, with edges.
It builds, it signs, it installs, and it renders live arrivals on an Apple Watch Series 8 — on the face, in the corner slot, counting down correctly. The core logic — time windows, filtering, the display planner, both feed decoders — is covered by seventy-six tests, several built from real captured responses containing the exact traps above.
Two hours is not enough time to build something finished. It was enough to build something real: a thing that reads a live public feed, works within a genuinely tight budget, and refuses to tell me a confident lie about when the train is coming.
The rest is wearing it and finding out what it gets wrong.