Skip to content

today: window the iOS Latest Workouts feed to the same 14 days as Android - #1703

Merged
ryanbr merged 1 commit into
mainfrom
today-workouts-window
Aug 28, 2026
Merged

ryanbr merged 1 commit into
mainfrom
today-workouts-window

Conversation

@ryanbr

@ryanbr ryanbr commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Addresses #1702, on the decision to match Android's window.

What was actually wrong

The two platforms rendered the same section from different data. Android windows to today − 13 at start of day and says "14 days" in the header. iOS drew from repo.workoutRows(), whose default is days: 4000 — effectively all-time — under a heading that says "Latest", trailed by a count of every workout ever recorded.

One account read two ways: someone who last trained three months ago saw no section on Android and six stale sessions on iOS.

Reading the call sites suggests iOS's behaviour was never a product decision. Android keeps two sets — recentUnion for the feed, separate all-time queries for the Data Sources counts. iOS has a single workouts array serving both, and the section simply used what was already there.

Why the window is applied at the section

This is the part worth reviewing carefully. workouts is shared:

  • the Data Sources "Apple Health · N workouts" count (TodayView.swift:3939)
  • the HR chart's sport glyphs (workoutSpans)

Both are all-time by design. Windowing the array at its source compiles, looks correct, and quietly shrinks two unrelated numbers on the same screen. So TodayView.recentWorkoutsFeed windows at the section and the shared array is left alone.

Also

  • Trailing header now describes the window on both. "14 days" already existed in the catalogue in all ten locales, so there is no new copy and no xcstrings work.
  • "\(workouts.count) total" is gone — it counted all-time while showing six.
  • Tile caps stay 4 (Android) / 6 (iOS), deliberately. That difference is layout, not content: Android is single-column full-width on purpose (the 2×2 grid truncated durations on narrow phones, Future-dated strap (#324): surface a banner + bad-clock diagnostics #332), iOS uses an adaptive grid. Same vertical budget; unifying it would change one platform's density for no reader benefit.

Tests

TodayWorkoutsWindowTests (Swift, runs under app-build) pins the boundary, not the happy path: a session exactly on the cutoff is kept, one second earlier is dropped, and filtering must not reorder. An off-by-one there silently drops a session the other platform still shows — the same class of drift that produced the issue.

TodayWorkoutTapTest gains a parity assertion that runs in ordinary CI and explicitly checks the Data Sources count still reads the unwindowed array. The trap above is guarded, not merely sidestepped once.

Verified

4,713 Android tests, 0 failures with --rerun-tasks --no-build-cache; Tools/i18n_audit.py --ci clean. The Swift half compiles only under app-build — dispatching it, and I will post the result here before this merges.

Note for review

This changes iOS behaviour: the section now disappears when you have not trained in two weeks, matching Android. That is the consequence of the chosen option, not a side effect.

…roid

Reported as #1702. The two platforms rendered the same section from different data: Android
windows to today-13 at start of day and says "14 days" in the header, while iOS drew from
repo.workoutRows(), whose default is days: 4000 — effectively all-time — under a heading that
says "Latest", trailed by a count of every workout ever recorded.

The visible result was that one account read two ways. Someone who last trained three months
ago saw no section at all on Android and six stale sessions on iOS.

Reading the call sites suggests iOS's behaviour was never a product decision. Android keeps
two sets — recentUnion for the feed, separate all-time queries for the Data Sources counts —
while iOS has a single `workouts` array serving both, and the section simply used what was
already there.

That is also why the window is applied at the SECTION rather than to the array. `workouts`
also feeds the Data Sources Apple-workout count and the HR chart's sport glyphs, both all-time
by design; windowing it at the source compiles, looks correct, and quietly shrinks two
unrelated numbers on the same screen.

The trailing header now describes the window on both platforms. "14 days" was already in the
string catalogue in all ten locales, so no new copy.

Tile caps stay 4 on Android and 6 on iOS. That difference is layout, not content: Android is
deliberately single-column full-width (the 2x2 grid truncated durations on narrow phones,
#332), iOS uses an adaptive grid. Same vertical budget, and unifying it would change one
platform's density for no reader benefit.

TodayWorkoutsWindowTests pins the boundary rather than the happy path — a session exactly on
the cutoff is kept, one second earlier is dropped — because an off-by-one there silently drops
a session the other platform still shows. TodayWorkoutTapTest gains a parity assertion that
runs in ordinary CI and checks the Data Sources count still reads the unwindowed array, so the
trap is guarded and not merely sidestepped once.

Verified: 4,713 Android tests, 0 failures with --rerun-tasks --no-build-cache; i18n audit
clean. The Swift half compiles only under app-build.
@ryanbr
ryanbr merged commit f402f2a into main Aug 28, 2026
6 checks passed
@ryanbr
ryanbr deleted the today-workouts-window branch August 28, 2026 07:34
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