Skip to content

Workouts: phone-started workouts, milestones 1–8 - #8

Merged
sayed0am merged 32 commits into
mainfrom
workouts
Oct 21, 2026
Merged

sayed0am merged 32 commits into
mainfrom
workouts

Conversation

@sayed0am

@sayed0am sayed0am commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Phone-started workouts, built to the spec OpenStrap Workouts: Feature Spec and its Workout Mode canvas, in the app's glow design language. All eight milestones from the spec's build order are here, one commit each (2–5 and 6–8 grouped).

What's in it

Milestone What it does
1 Record and never lose it Live HR recorded in the background, saved every 10 s; a killed app picks the workout back up; minutes the phone missed are filled from the strap's history after a sync
2 End well "Looks like you've stopped" after 8 calm, still minutes (strap buzz + notification); car detection via a car stereo / hands-free Bluetooth connection (no location, no Google services); non-destructive early end and trim, with undo; comparisons with your last 5 similar workouts and "your usual" zone marks
3 Catch missed sessions Suggestions on Today and Activity for 10+ min at 40 %+ of HR reserve; "Not a workout" remembered; add a workout after the fact from the day's heart rate
4 Workout mode everywhere Zone colours (canvas palette), capsule and tinted tab bar while minimized, Android 16 Live Update notification with a zone bar, Today row, Quick Settings tile, launcher shortcuts. Widgets left out, as agreed
5 Coach through the strap Targets (zone, zone range, time); buzzes on leaving/re-entering the target (held 15 s, cues 40 s apart), halfway and goal; never while a call rings or DND silences everything
6 GPS and maps GPS off by default, outdoor sports only, Android's LocationManager; route discarded unless kept; Heart · Map · Splits pages (MapLibre + OpenFreeMap dark tiles, "route only"); distance/pace; GPX export with 300 m hidden at each end; Health Connect exercise sessions
7 Strength Sets against a target; rest by time or HR level, whichever first; rest-over buzz; lock-screen controls
8 Strap-side sessions 0x0019 SportModule codec + docs/protocol/04; experimental, off by default (debug: --ez strap_sessions true); merges the strap's own record into the phone summary
Also Laps; DB v8→v12 (auto-migrations); CSV export of every new table; README Network use and Location

Testing

  • Unit tests: recorder, suggestions, coaching, route, strength, 0x0019 bytes; all pass.
  • Phone (Helio Strap, Samsung on Android 16), milestone 1: background recording, notification, resume after kill, hold to end.
  • Emulator, with a debug-only strap simulator (--es simulate_hr 140x120,75x600) and geo fix:
    • the stop prompt at 10:08, End at 21:27, then Undo
    • trim
    • start tiles
    • a GPS run with map, splits, summary map and GPX (start/end hidden)
    • strength rest ending early on heart rate
    • laps

Needs the phone and strap

  • Gap filling after a real disconnect + sync
  • Strap buzzes for coaching cues, rest over and the stop prompt; can the patterns be told apart?
  • Car detection with a real car stereo
  • GPS outdoors (accuracy filter, pace), and the location service type on Samsung
  • 0x0019 strap sessions: version reply, does the strap keep its own record, 13 00 timing
  • Live Update chip on One UI; Quick Settings tile and shortcuts on the launcher

Known gaps

  • No FIT export yet (GPX only).
  • No load or recovery-HR comparison for phone workouts: there's no open load model yet.
  • Charge and load don't update live during a workout, and move reminders don't pause.
  • MapLibre adds native libraries; consider per-ABI release APKs.

🤖 Generated with Claude Code

sayed0am and others added 4 commits October 21, 2026 21:14
Start a workout from the Activity tab, record the strap's live heart rate on
the phone, and keep a complete record through disconnects and a killed app.

- Recording: WorkoutSession streams live HR in the background during a
  workout, writes it to Room every 10 s, and resumes a workout left
  recording when the process restarts. New tables app_workout and
  app_workout_hr (DB v8), kept apart from the strap's own workouts.
- Gap filling: after each history sync, whole minutes the phone missed are
  filled from the strap's per-second HR, else its minute records
  (WorkoutRecorder, WorkoutGapFill).
- Screens in the glow language, following the Workout Mode canvas: start
  card with the most-used sports, ready screen, live screen in the zone's
  colour with hold-to-end, capsule and tinted tab bar while minimized, and a
  summary with heart rate coloured by zone.
- Notification: an Android 16 Live Update with a zone bar, Pause/Resume and
  End; the foreground service stays up for the whole workout.
- Zones take the canvas palette and names app-wide; phone workouts join the
  workout lists and the CSV export.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ching

End well (2)
- "Looks like you've stopped": after 8 min near resting heart rate and no
  steps, following an active spell, the strap buzzes and the live screen and
  notification offer "End at 18:50" or "Keep going". A dropped strap never
  triggers it; "Keep going" waits for a new active spell. Toggle on the
  ready screen.
- Car detection without location or Google services: a car stereo or
  hands-free kit connecting over Bluetooth asks at once.
- Early ends and trims are non-destructive (recordedEnd, shownStart), with
  undo; the summary says what the app did. Trim the start and end on the
  heart-rate chart.
- Summary compares with your last 5 similar workouts of the same sport, and
  marks "your usual" on each zone bar.

Missed sessions (3)
- Awake stretches of 10+ min at 40 %+ of heart-rate reserve that aren't a
  workout become a card on Today and Activity: Save as workout (choose the
  sport) or Not a workout (remembered for that range).
- "Add a workout you didn't record": pick the range on the day's heart rate.
  Both are built from the strap's stored minutes.

Entry points (4)
- Start card rebuilt from the app's stat tiles; a Start workout row on
  Today; a Quick Settings tile; Start run / Start strength launcher
  shortcuts. All open the ready screen.

Coaching (5)
- Targets on the ready screen (a zone, a zone range, a time); Start takes
  the target's colour. The strap buzzes on leaving and re-entering the
  target (held 15 s, cues 40 s apart), at halfway and at the goal, each with
  its own pattern; never while a call rings or DND silences everything.

Also: DB v10 (app_workout_dismissed, app_workout_route, GPS and target
columns), CSV export of the new tables, MapLibre added for milestone 6, and
a debug-only strap simulator (simulate_hr) for testing on the emulator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GPS and maps (6)
- GPS for outdoor sports through Android's own location service (no Google
  services), off by default; asked for only when turned on. The route is
  used live for distance, pace and the map, then discarded unless "Keep
  route on this phone" is on. Without location, distance comes from the
  strap's steps (≈).
- Live screen pages Heart · Map · Splits: MapLibre with OpenFreeMap's dark
  tiles (no key), the route in zone colours, GPS accuracy, "route only" (no
  tiles), and per-km splits with pace and heart rate.
- Summary: distance, the kept route on a map, Export GPX with the first and
  last 300 m left out; phone workouts go to Health Connect as exercise
  sessions (GPS distance with them).
- The foreground service adds the location type for a GPS workout.

Strength (7)
- Sets against an optional target, "Start set" / "Set done"; rest ends at
  its time (+30 s to extend) or sooner once heart rate holds below a chosen
  level, and the strap buzzes. Lock-screen notification with the rest
  counting down. Sets on the summary.

Strap-side sessions (8, experimental, off by default)
- SportModule 0x0019 codec (version, config, state, keep-alive, pushes)
  with byte tests from the teardown's examples; docs/protocol/04.
- When on (debug: --ez strap_sessions true): START/PAUSE/RESUME/STOP follow
  the phone workout, START again after a reconnect, and the strap's "heart
  rate back to normal" asks whether to end. A strap workout synced for the
  same time shows on the phone summary ("From the strap"). Unverified on
  hardware.

DB v11 (app_workout_set; rest and sets target). README: Network use
(OpenFreeMap tiles) and Location permission.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Lap button left of Pause on the live screen (as on the design) and a Lap
  action in the notification; a short buzz confirms. Laps are stored as
  marks on the workout (DB v12) and listed on the summary with time,
  distance (GPS) and average heart rate.
- Start-card tiles wrap long sport names ("Strength training").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sayed0am sayed0am changed the title Workouts milestone 1: record from the phone and never lose it Workouts: phone-started workouts, milestones 1–8 Oct 21, 2026
sayed0am and others added 25 commits October 21, 2026 23:07
Auto-start: while idle, live heart rate is sampled for 30 s every 3 minutes,
and streams on while it's up or the feet are busy. Once an effort has lasted
10 minutes (the missed-workout rule, on live readings) the strap buzzes and
"Looks like a workout" offers to record it from where it began, with the
readings since; walk, run or free training from the cadence. On by default,
with a switch under Start a workout.

Notifications:
- Everyday: Recovery and Charge as the title, problems first (Retry, low
  battery, sync progress), last sync as the time, Start workout and Sync now;
  a quiet mode on the You tab.
- Workout: zone colour (the whole card where Live Updates can't show, as on
  Samsung), target progress, distance and pace, a disconnected state, and a
  two-step End.
- A "saved" summary after a workout, with Undo when it ended on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the zone colour

The zone stays on the bar and in the text; the line says "Waiting for heart
rate" before the first reading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lighter grey while paused or disconnected; the logo's red stays the accent
where the notification isn't colorized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each other CPU type carried its own ~13 MB copy of the map engine. Release
APK 55.6 MB -> 18.4 MB; debug 100 MB -> 62 MB. The emulator on Apple silicon
is arm64 too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rimmer

- Pause, Lap and End are flat like Start and Done (white Pause, dark Lap and
  End with a red stop and hold ring); the live card keeps the glow.
- The zone bar's marker is the Recovery/Charge gauge dot in the zone colour,
  bright up to it and faint after, in the app and the notification.
- Trim is a video trimmer (Snapchat, Polarsteps): one rounded frame whose
  thick sides are the handles, over the workout as zone-coloured dot columns;
  what's left out fades back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Avg | time | Max (or distance and pace/avg), labels under the numbers with
thin rules between (WHOOP, Oura on Mobbin); the row centred between the card
and the buttons, which sit a little higher.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It squeezes on each beat (lub, then a smaller dub) and relaxes, staying as
dark throughout; each beat also nudges and squashes it a new way, so the colour
surges past a different side. The old beat faded the core 20%, which read as a
glitchy flash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Android raises a foreground service's MIN channel to LOW, so Quiet never hid
the status-bar icon. Since Android 14 the notification can be swiped away
with the service still running; it now stays away until a workout starts or
a new problem (connection failed, low battery) needs it. Debug: the strap
simulator takes simulate_battery.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GlowSwitch is white with a black knob when on and dim grey when off, like the
workout buttons; the Material switches (alarms, phone alerts, firmware server)
use it too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…live pages

Route.clean starts the route at the first of five agreeing fixes within 20 m,
so a cold start's far-off fix doesn't count, and distance, pace, splits and
the map line follow Route.track, which takes a fix only once it's further
than both fixes' error from the last. Standing at home now stays at 0 m.
The live map centres on the latest fix before there's a line, Heart, Map
and Splits swipe as well as tap, and the MapLibre logo is gone (the
attribution button stays).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The live screen no longer scrolls: Map and Splits take the heart card's
height (splits scroll inside their panel), so swiping never moves the stats
or controls. The map fills its page; the note about map tiles moves to the
GPS switch on the ready screen. After a Lap press, "Lap 3 · 1:24" shows the
lap under way under the moving time, and the phone gives a short haptic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Minimizing the live screen shrinks it, corners rounding, into the capsule
above the tab bar (and back on a tap), over 550 ms, keeping the live screen
composed through the move so it isn't rebuilt as it starts. On Today, Start
workout is a white pill under the Workouts header, above the day's workouts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Running, it shows the sport and clock with a live dot and opens the live
screen; paused, "Resume walk · 12:34" resumes at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 2×2 grid becomes a white "Start walk" pill for your usual sport (like
Today's) with the next sports and All sports as chips under it. "Notice
workouts" is renamed Auto-detect workouts, the term other apps use, and its
line says the strap buzzes and asks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uto-detect line

The strap files movement it logged on its own as sport 223, "Activity"; it
counted toward your most-used sports and took a chip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…off foot

Start a workout on Activity is four alike round buttons (most used first,
then All sports) sharing the width. The ready screen opens as a circle from
where it was tapped and shrinks back into it; a workout starting replaces it
at once. Distance from steps is only for sports done on foot (runs, walks,
hikes); cycling, swimming and the rest show average and max heart rate
instead, and the GPS switch says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thirteen sports that were another one under a second name, and the strap's
generic Activity, are kept for naming recorded workouts but no longer
offered. The rest sit in seven sections (Running & walking, Gym, Cycling,
Water, Mind & body, Racket & ball, Outdoors) as a four-wide grid of the same
round buttons as Start a workout, each sport with its own Phosphor icon; the
current one is ringed. Search filters within the sections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…shorter lists

The ready screen slides in and out like every other screen, and minimizing
the live workout slides it down and fades while its capsule rises, both at
the app's 240 ms. A workout with under a minute of moving time is deleted as
it ends, with a toast. Today lists the day's last three workouts; Activity's
list is four rows tall, scrolls inside its panel with a bar in the margin,
and keeps its scroll from moving the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orchid

Recovery was grey whenever OpenStrap's score was missing, so it read as
"no data" through the learning nights and even with the strap's estimate
on the card. Learning is now a muted teal, and the strap's estimate picks
the card colour like a real score. Healthspan's lavender sat next to
Sleep's blue; orchid keeps it clearly apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sayed0am and others added 3 commits October 22, 2026 02:44
The card shows an age ("34 · 3 years younger"), not a span of healthy
years, and the inputs are habits, not blood markers. It's also WHOOP's
feature name. Only user-facing text changes; code names stay Healthspan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On, the switch in a glow card is a deeper shade of the card's colour with
a white knob, so it belongs to the card. Switches in lists and sheets stay
monochrome.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a foldable half-open during a live workout, the standing half shows the
workout's heart card as big as it fits and the flat half keeps the figures
and the Lap, Pause and End controls. Flat on a table that's top and bottom;
stood up like a book, left and right. A minimized workout leaves the
tabletop heart card as it was.

The controls row is capped at a thumb's reach and centred, so on a wide
screen Lap and End stay beside Pause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sayed0am
sayed0am merged commit 6855163 into main Oct 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant