Skip to content

Fix Today score source badges#778

Merged
ryanbr merged 1 commit into
ryanbr:mainfrom
Quaii:fix-today-score-source-badges
Jul 25, 2026
Merged

Fix Today score source badges#778
ryanbr merged 1 commit into
ryanbr:mainfrom
Quaii:fix-today-score-source-badges

Conversation

@Quaii

@Quaii Quaii commented Jul 24, 2026

Copy link
Copy Markdown

What this PR does

This PR fixes the source badge on the Today score card so it identifies the devices and imports that supplied the score inputs instead of always describing where NOOP performed the calculation.

Computed recovery, strain, and sleep-performance scores now persist their input provider in a dedicated local provenance table. The provenance update is committed atomically with the corresponding score update, while directly imported scores continue to resolve their provider from their existing source ID. Legacy computed scores without recorded provenance hide the badge rather than guessing.

The shared provider mapping covers paired device brands and stable import sources, including Whoop, Apple Watch, Health Connect, Oura, Fitbit, Garmin, Mi Band, Polar, and other registered devices. Mixed inputs are deduplicated and limited to two names in the compact badge. Carried Charge values resolve the provider from the exact day that supplied the displayed score.

The PR also restores the badge's intended position, centered on the top border and aligned with the Rest vessel, and applies the same behavior to the Classic Today view, Liquid Today view, and Android.

The new storage is documented and removed alongside its device or scoped user data. No BLE, protocol, or scoring-math behavior was changed.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • CI / tooling

How it was tested

  • Ran swift test in Packages/WhoopStore: 272 tests passed.
  • Ran ./gradlew testFullDebugUnitTest for Android: passed.
  • Ran the 43 focused TodayExplainabilityTests: passed.
  • Built the NOOPiOS Debug configuration without signing for an iPhone 16 Pro simulator.
  • Installed and launched the final iOS build in the simulator.
  • Manually verified the badge alignment and provider display in the iOS simulator.
  • Ran python3 Tools/i18n_audit.py --ci upstream/main: Android and Apple localization checks passed.
  • Ran git diff --check: passed.
  • The complete macOS test suite was also attempted, but an unrelated existing BugReportTemplateTests file-read test hung; the affected Today test suite completed successfully.

Checklist

  • Swift package tests pass for any package I touched (swift test in Packages/<name>)
  • Android unit tests pass if I touched android/ (./gradlew testFullDebugUnitTest)
  • No new build warnings introduced
  • UI changes use only StrandDesign tokens — no hardcoded colors, fonts, or spacing
  • No hardcoded hex frame bytes; protocol facts live in the schema / decoders
  • Follows the conventions in docs/CONTRIBUTING.md
  • I did not commit generated output (Strand.xcodeproj/) or any secrets/keystores

Related issues

None.

@ryanbr

ryanbr commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Good PR. The migration half — the risky part — is right, and the atomicity claim holds.

Verified:

  • Migrations symmetric. GRDB v29 and Room 22→23 create the same four columns in the same order, same composite PK (deviceId, day, key), same index name idx_scoreInputProvenance_source. Column order matches the entity field order, which is the specific rule CLAUDE.md calls out. Both version numbers are the correct next value (main is GRDB v28 / Room 22).
  • Atomic with the score write@Transaction on the Kotlin dao, and the Swift side shares the transaction via the MetricsCache primitive rather than opening its own. That was the thing that mattered: provenance drifting from the score would make the badge lie.
  • Deletion wired on both platformsDELETE FROM scoreInputProvenance WHERE sourceId = ? in DeviceRegistryStore, the scoped range delete in ScoreInputProvenanceStore, and the Kotlin deleteScoreInputProvenanceFor / …InRange equivalents.
  • Label normalisation identicalbrand.caseInsensitiveCompare("WHOOP") == .orderedSame ? "Whoop" and brand.equals("WHOOP", ignoreCase = true) -> "Whoop" produce the same output, so badges can't diverge by platform.
  • Hiding the badge for legacy rows rather than guessing is the right call and matches the honest-data rule.

Two notes, neither blocking:

The new table rides .noopbak automatically (whole-file copy) and is absent from the CSV export. That's correct here — it's derived provenance, not user data — but worth stating in docs/DATA_MODEL.md alongside the table, since #718 just demonstrated that "which paths carry this" is easy to leave implicit and hard to infer later.

The BugReportTemplateTests hang you hit is worth its own issue if it reproduces — a test that hangs rather than fails will eventually block someone's full-suite run, and it shouldn't be discovered again from scratch.

CI is green across the Swift packages and the i18n gate, and you built and manually checked the badge in the simulator, so the parts I can't verify here are covered.

@ryanbr
ryanbr marked this pull request as ready for review July 25, 2026 06:44
@ryanbr
ryanbr merged commit eedc24e into ryanbr:main Jul 25, 2026
9 checks passed
ryanbr added a commit that referenced this pull request Jul 25, 2026
MARKETING_VERSION/versionName move together per the versioning rule; build numbers
increment independently (207->208 Apple, 298->299 Android).

The staging workflow reads MARKETING_VERSION for its title and asset names, so main
has to carry 9.1.1 for the rolling testing-latest release to be labelled as such —
the previous 9.1.1 staging build was cut from a commit that never landed on main.

This is also the first compile of the app-target changes merged today (#783/#784/#785,
#787, #788, #793, #795, #519/#798, #523, #778), which CI does not cover: app-build.yml
and android.yml are disabled and only the Swift packages and the i18n gate run on PRs.
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.

2 participants