Fix unit/scale preferences ignored across six display sites#3
Open
digitalerdude wants to merge 3 commits into
Open
Fix unit/scale preferences ignored across six display sites#3digitalerdude wants to merge 3 commits into
digitalerdude wants to merge 3 commits into
Conversation
Same bug class as ryanbr#101 (skin temp ignoring the Fahrenheit preference) and ryanbr#45 (Effort scale ignored on the hero gauge), found by sweeping every display site that formats a convertible metric without routing through UnitFormatter. Display-only throughout: stored values stay SI, every stat/trend/threshold is computed on the stored values. Fixed sites: - TodayView (dashboard "Your cards" skin-temp card): formatted the raw deviation with "%+.1f°" — now converts via UnitFormatter.temperatureDeltaFromCelsius (×9/5, no +32 offset for a delta) and shows the resolved unit label ("+0.3 °C" / "+0.5 °F"). - Trends report PDF (RangeReportEngine + TrendsReportView): the exported report hardcoded "°C" for the skin-temp row and showed Strain as the raw 0–100 integer regardless of the #268 Effort-scale choice — in an artifact users may hand to a clinician. RangeReportEngine.build gains effortDisplayFactor / skinTempDisplayFactor / skinTempUnitLabel parameters (defaults keep existing callers and all 21 package tests byte-identical), mirroring WeeklyDigestEngine's effortDisplayFactor precedent, so the headline prose converts too. The page's Avg/Min/Max read-outs convert in valueText; the native 0–100 scale keeps its whole-number style, the 0–21 axis reads to one decimal. - LiquidTodayView (Key Metrics strain tile): showed the raw 0–100 value with a literal "%" — wrong unit, and it ignored the 0–21 axis — while the hero gauge and workout card in the same file already converted. Now routes through effortText with a "/ 100" / "/ 21" suffix. - LiquidTodayView (workout sub-line): hardcoded "%.1f km" — now UnitFormatter.distanceFromMeters, honouring the miles preference. - FusedRecordView ("Your Data, Fused"): FusionFormat hardcoded "%.1f°C" for the absolute skin temperature. The formatter now takes the resolved TemperatureUnit as a required parameter (no default, so future call sites can't silently bypass) and converts via temperatureFromCelsius (full ×9/5 + 32 — absolute, not a delta). - AppModel (illness watch): the fired-signal label "skin temp +0.7 °C" in the Heads-Up card, the amber banner and its notification formatted the raw Δ°C. New UnitPrefs.currentTemperatureUnit() helper resolves the preference for non-view call sites (mirrors currentEffortDisplayFactor), and the label converts through it. Checked and deliberately left unchanged: - CoupledView: pinned to the WHOOP 0–21 axis by documented design. - SkinTempCardsView: the "+0.7 °C" strings are DEBUG preview fixtures. - AICoach context blocks: SI on purpose (model context, not UI). - ActivityFileImporter notes: persisted at import time, Android parity. - Settings/AppleHealth/Workouts/WorkoutDetail: already branch correctly. Android: the Kotlin RangeReport mirror should gain the same three display parameters to stay in lockstep (its UnitFormatter already has the effortDisplayFactor precedent from the WeeklyDigest work). Verified: full macOS Debug build clean; StrandAnalytics RangeReportTests 21/21 green (engine defaults unchanged). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ecker test (WhoopProtocol) failed on the PR run with "the compiler is unable to type-check this expression in reasonable time" at DecoderOracleTests.swift:86 — the one-line optional-chained magnitude ((gx ?? 0) * (gx ?? 0) + ...) blew the type-checker's time budget on the CI runners (it squeaked through on a local arm64 machine, so this never surfaced locally). Pre-existing on the branch point, unrelated to this PR's display fixes; fixed here so the run can go green. Bind the components to concrete Doubles first and sum the squares in a typed sub-expression — same assertions, same tolerance, no behaviour change. All 241 WhoopProtocol tests pass locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Owner
Author
|
CI note: |
build (NOOPiOS, iOS Simulator) failed on the PR gate with "value of type 'Self' has no member 'glassEffect'" at StrandiOS/App/RootTabView.swift:623. The Liquid Glass tab bar (045a302) compiles only against the iOS 26 SDK — the runtime #available guard doesn't help at compile time. The release and testing workflows were already moved to macos-26 for exactly this reason (fork-release.yml: "iOS 26 Liquid Glass (glassEffect) needs the iOS 26 SDK → Xcode 26 → macos-26"), but the app-build.yml PR gate still ran the whole matrix on macos-15. Give each matrix entry its own runner: Strand stays on macos-15 (matching the release macos job), NOOPiOS moves to macos-26. Pre-existing on the branch point, unrelated to this PR's display fixes; fixed here so the run can go green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
Fixes six display sites that formatted a convertible metric without routing through
UnitFormatter— the same bug class as ryanbr#101 (skin temp ignoring the Fahrenheit preference in Deep Timeline) and ryanbr#45 (Effort scale ignored on the Today hero gauge), found by sweeping the codebase for the pattern. Display-only throughout: stored values stay SI, every stat/trend/threshold is still computed on the stored values."%+.1f°", °F preference ignoredUnitFormatter.temperatureDeltaFromCelsius(×9/5, no +32 offset for a delta), signed, with the resolved unit label ("+0.3 °C" / "+0.5 °F")RangeReportEngine+TrendsReportViewRangeReportEngine.buildgainseffortDisplayFactor/skinTempDisplayFactor/skinTempUnitLabel(defaults keep existing callers byte-identical), mirroringWeeklyDigestEngine'seffortDisplayFactorprecedent, so headline prose converts too; the page's Avg/Min/Max read-outs convert invalueTexteffortTextwith a "/ 100" / "/ 21" suffix"%.1f km", miles preference ignoredUnitFormatter.distanceFromMetersFusionFormathardcoded"%.1f°C"for the absolute skin temperatureTemperatureUnitas a required parameter (no default, so future call sites can't silently bypass); converts viatemperatureFromCelsius(full ×9/5 + 32 — absolute, not a delta)UnitPrefs.currentTemperatureUnit()helper for non-view call sites (mirrorscurrentEffortDisplayFactor)Checked and deliberately left unchanged: CoupledView (pinned to the WHOOP 0–21 axis by documented design), SkinTempCardsView (the "+0.7 °C" strings are DEBUG preview fixtures), AICoach context blocks (SI on purpose — model context, not UI), ActivityFileImporter notes (persisted at import time, Kotlin parity), Settings/AppleHealth/Workouts/WorkoutDetail (already branch correctly).
Android follow-up: the Kotlin
RangeReportmirror should gain the same three display parameters to stay in lockstep — itsUnitFormatteralready has theeffortDisplayFactorprecedent from the WeeklyDigest work. The app-side sites above are worth checking on Android too.Type of change
How it was tested
swift testinPackages/StrandAnalytics: 21/21RangeReportTestspass — the new engine parameters default to the current behaviour, so the existing oracle stays byte-identical (the Kotlin twin'sRangeReportTestis unaffected until it mirrors the parameters).xcodebuild -scheme Strand) is clean; the only warnings in the log are pre-existing and unrelated (actor-isolation / localization deprecations).Checklist
swift testinPackages/<name>)android/(not touched — Kotlin mirror flagged as follow-up above)StrandDesigntokens — no hardcoded colors, fonts, or spacingdocs/CONTRIBUTING.mdStrand.xcodeproj/) or any secrets/keystoresRelated issues
Follow-up to ryanbr#101 and ryanbr#45 (same bug class, remaining instances). Touches the display side of #268 (Effort scale) and #457 (trends report).
🤖 Generated with Claude Code