The phone app is closed. The watch still starts the session.

Here’s the situation we built Pomo’s Wear OS app around. Your phone is in a bag under the library desk. At some point you swiped Pomo out of the recent apps, or Android closed it to save memory. You look at your watch and tap Begin.

For that to start a real session (one that ends on time, rings at the end and lands in your ledger), four things have to work without the phone app running. This post walks through each, with what we got wrong along the way.

1. The watch doesn’t count

The obvious way to show a countdown on a watch is to have the phone send the time left every second. It’s also the way that falls apart the first time Bluetooth drops.

Pomo’s watch never counts on its own. It holds one number, the moment the current phase ends, and every second it shows the difference between that and its own clock. The phone sends a new end time when something changes: a Begin, a pause, a skip. Between those, the two devices can be completely out of touch and still agree.

That only works if the two clocks agree, and they don’t quite. So every update from the phone is stamped with the phone’s time, and the watch measures how far its own clock is ahead. The catch is that the measurement includes however long the update took to arrive. Our first version re-measured on every update, so one that sat in a queue through a Bluetooth drop and arrived minutes late set the watch minutes behind the phone. Delay can only make the reading bigger, so the watch now keeps the smallest reading it has seen, and only accepts a bigger one if it’s the answer to a sync the watch asked for a moment ago, or if the kept one is more than six hours old.

It’s the same model the phone app uses for its own timer, and the same one the web timer on this site uses. Pomo also rounds the seconds down everywhere, on the phone, the watch and the lock screen, so they all flip to the next second together instead of one being a beat behind.

2. Getting the tap across

A tap on the watch has to reach a phone that may be asleep, out of range for a moment, or both. So every command goes two ways at once:

  • a message, which is fast but is simply lost if the phone isn’t listening;
  • a data item, which the watch stores and the system syncs whenever the phone is next reachable. It survives a Bluetooth drop or a sleeping phone, and its arrival wakes Pomo’s listener on the phone.

That means the phone can receive the same command twice, or receive an old one late. Each command carries a token, the time of the tap, nudged up if needed so every token is larger than the last, and the phone applies a token once and never applies one older than a token it has already seen.

The token used to live in memory, and there used to be two of them. When the watch’s clock was corrected backwards (it happens, after a time sync), a restarted watch app issued tokens smaller than ones the phone had already applied, and the phone politely refused every command until the clock caught up. The token is now a single stored counter on the watch, so it only ever goes up.

The token doubles as a timestamp. When a Begin arrives late because it waited out a gap in the connection, the phone starts the session from the moment you tapped, not the moment the message landed. A token from the future, if the watch’s clock is ahead, is treated as now.

3. Applying it without the app

The phone’s running session is a small file. When a command arrives and Pomo’s main app isn’t running, a piece of native Android code reads that file, applies the same transition the app’s own engine would, and writes it back. Then it moves or cancels the end-of-session alarm, updates the notification on the phone, and sends the new state to the watch. When you next open Pomo, the app reads the same file and picks up exactly there, and the command is marked as done so it isn’t applied a second time.

That works for Begin, Pause, Resume, End and Skip. Begin starts whatever length the phone has armed, with the label you picked on the wrist; End records the focus you let go, if you’d held it for at least a minute, the same rule the app uses.

Three different things can write that file while the app is closed: the alarm firing at the end of a phase, a command from the watch, and the Pause button in the phone’s notification. Early on, each read the file and wrote it back without waiting for the others, so a pause from your wrist that landed in the same instant as the end of a phase could be silently undone, or the other way round. Every read-and-write of the file now holds the same lock.

The one command that doesn’t run this way is +5, adding five minutes to a break. It waits until you next open Pomo on the phone. If you’ll want extra break time, add it before you put the phone away.

4. The end of the session

When the phase ends, the phone’s alarm rings (that’s Ring at session end in Pomo’s Alerts settings, on by default), and the phone tells the watch. The watch turns its screen on with a full-screen alert, Session complete or Break over, and vibrates in the pattern you chose on the phone until you answer. It doesn’t play sound; the phone does. Tap Stop on either and both go quiet. If nobody answers, the phone gives up after two minutes and takes the watch with it.

None of this needs the watch app to be open either. The alert is its own screen.

What the watch won’t do

The watch refuses to start a session it can’t finish. If the phone is missing the permission to post notifications or set its alarm, the phone can’t tell you the session has ended, so the watch shows one line naming exactly what’s off (“Exact alarms are off”, for example) and Open Pomo on your phone, and hides Begin. It’s annoying for the thirty seconds it takes to fix, and much less annoying than a focus that silently overruns by an hour.

If the watch’s countdown reaches zero and it still hasn’t heard anything, it says Time’s up and asks the phone to catch up, rather than pretending to know what happened.

The practical side, how to install it and what each button does, is in the Wear OS guide.