Skip to content
Build log

Coaching on the wrist

6 min readby Manuel

Structured interval plans and a voice that actually speaks German, built while I was using the intervals myself to chase some speed back.

A coaching cue shown during a workout on Apple Watch

By this point I had a watch that measured a run honestly and saved it properly. But I was training, actually training, and my own training had taught me something: if you want to get faster, structured intervals are a big part of how you get there. A stopwatch does not make you faster. Intervals do. That is exactly why Runara had to learn to coach.

Structure you build on the big screen

Interval plans follow the same philosophy as the rest of the app: you do the thinking on the iPhone, where you have room and a keyboard, and the watch just runs it.

A plan is a sequence of steps, warm-up, work, recovery, cool-down, and freeform, and each step can carry a target: a pace band to hold, or a heart-rate zone to sit in. Early on the steps were anonymous and a little rigid, so I loosened them:

feat(model): PlanStep gains optional name + HR target zone

Now a step can be called "5 x 1 min hard" in your own words and aim at zone 4, or it can be a wide-open "just cruise" block with no target at all. The watch shows a slim header during the run: what this step is for, how much of it is left, and a glance at what is coming next. Open-ended steps get a manual "next" so you can improvise without throwing away the structure, which is exactly how my real workouts go when my legs have an opinion.

Two fixes from this stretch are the ones that made plans feel alive rather than just present:

fix(watch): announce first plan step + auto-split on each step change

Before that, a plan would start in silence and never mark its own boundaries. Now it tells you what the first step is the moment you begin, and every time a step changes it drops a segment marker automatically, so your splits line up with the structure you designed. That one commit is the difference between "a list on your wrist" and "something that runs the workout with you."

A voice that actually speaks German

This is the part I am quietly proud of.

I live in Austria. Half the people who might use this think in German. A coaching cue that announces "one kilometre, five minutes twelve" in clipped English to a German speaker mid-run is not coaching, it is friction. So the audio cues speak both English and German, and they are generated entirely on the device. Nothing is sent to a cloud text-to-speech service, because of course it is not, that would mean shipping your run to a server to read a number back to you.

The watch can speak your splits, time milestones, zone crossings, and interval boundaries. It ducks the volume like Siri does, so it respects whatever you are already listening to, and it is off by default. You turn it on only if you want a voice in your ear.

Getting the German right was more work than the English, and not in the way you would guess. The synthesis was the easy part. The trap was that some of the interval labels were plain text strings that had quietly skipped the normal localization path:

fix(l10n): translate Phase-16 interval labels that bypassed SwiftUI auto-localize

A handful of words that looked translated in the simulator turned out to be hard-coded English hiding in the wrong layer. The kind of bug that only a native speaker, or someone who actually switches their phone to German, would ever notice. I notice, because German is the language this app gets used in around here.

Where the machine helped, and where it could not

Audio cues are a good example of the human-and-machine division I keep coming back to.

Claude was genuinely great at the mechanical breadth: wiring up the speech synthesizer, generating the scaffolding for cue phrases across sixteen different sports in two languages, handling the audio-session plumbing so the ducking behaves. That is a lot of careful, repetitive code, and it produced it quickly and correctly.

What it could not do was tell me whether a German cue sounded natural to an Austrian ear, or whether announcing the split at that exact moment in an interval would help or annoy me, or that a particular label was lying about being localized. Those judgments came from being the person who would actually wear this thing on a Tuesday evening run through Lieboch, trying to hold zone 4 and not think about software. The taste, and the test of "does this actually help me run," stayed mine.

I used these intervals myself while I built them. That feedback loop, build a cue, run with it, come back annoyed by something specific, fix exactly that, is the best part of building your own tools. You are your own most demanding user, and you cannot lie to yourself about whether it works.

Next: the phone has been sitting in your pocket doing nothing this whole time. Time to put it to work, and meet the tiny watch complication that fought me for three rounds.