From 3155ba2a121d5afd2ac0382ae6a0008c822e3aaa Mon Sep 17 00:00:00 2001 From: Fanboynz Date: Tue, 8 Sep 2026 22:58:24 +1200 Subject: [PATCH] fix(charts): anchor the Rest chart, which the wrong key left untouched #2004 anchored "recovery", "sleep_performance" and "strain". On Android the Rest detail key is "rest": sleep_performance is the SERIES this detail reads underneath and is never a detail key here. So Rest kept auto-scaling and looked exactly as it did before, which is what the reporter saw on the build. The test asserted vitalChartYDomain("sleep_performance") and passed, because the function happily answers for a key nothing ever passes. A green test for a key that does not exist is what made this feel verified. Rest is the metric where this was invisible in the worst way: the reporter's 14 readings cover 14 consecutive days, so the gap break draws nothing different and the domain was the ONLY thing that would have changed that chart. Getting the key wrong meant Rest was byte-identical to before. The allow-list is now pinned against the REAL key list the screen dispatches on, so an anchored key that no screen sends fails the test rather than passing it. --- .../com/noop/ui/HealthVitalDetailLogic.kt | 5 +++- .../java/com/noop/ui/VitalChartShapeTest.kt | 23 ++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/com/noop/ui/HealthVitalDetailLogic.kt b/android/app/src/main/java/com/noop/ui/HealthVitalDetailLogic.kt index 978a3ac3b4..8bc65178b3 100644 --- a/android/app/src/main/java/com/noop/ui/HealthVitalDetailLogic.kt +++ b/android/app/src/main/java/com/noop/ui/HealthVitalDetailLogic.kt @@ -54,7 +54,10 @@ internal fun vo2MaxAttributionSource(estimator: Vo2MaxEstimator?): String = */ internal fun vitalChartYDomain(key: String): ClosedFloatingPointRange? = when (key) { - "recovery", "sleep_performance", "strain" -> 0.0..100.0 + // "rest", NOT "sleep_performance": that is the SERIES name this detail reads underneath, and it is + // never a detail key on Android. Writing the series name here compiled, passed a test asserting it, + // and left the Rest chart auto-scaling exactly as before. + "recovery", "rest", "strain" -> 0.0..100.0 else -> null } diff --git a/android/app/src/test/java/com/noop/ui/VitalChartShapeTest.kt b/android/app/src/test/java/com/noop/ui/VitalChartShapeTest.kt index 16759598d8..5053ce2071 100644 --- a/android/app/src/test/java/com/noop/ui/VitalChartShapeTest.kt +++ b/android/app/src/test/java/com/noop/ui/VitalChartShapeTest.kt @@ -20,7 +20,7 @@ class VitalChartShapeTest { @Test fun `percentage metrics anchor to their natural range`() { assertEquals(0.0..100.0, vitalChartYDomain("recovery")) - assertEquals(0.0..100.0, vitalChartYDomain("sleep_performance")) + assertEquals(0.0..100.0, vitalChartYDomain("rest")) assertEquals(0.0..100.0, vitalChartYDomain("strain")) } @@ -34,6 +34,27 @@ class VitalChartShapeTest { assertEquals(0.0..100.0, vitalChartYDomain("strain")) } + /** + * Every key in the allow-list has to be one the screen ACTUALLY receives. The first version anchored + * "sleep_performance", which is the series this detail reads underneath and never a detail key here, + * so Rest kept auto-scaling while a test asserting that key passed. Pinning against the real key list + * is what makes the allow-list checkable rather than plausible. + * + * `realKeys` is a HAND-MAINTAINED mirror of the `when` in `buildVitalDetail`/`buildSeriesVitalDetail`; + * a unit test cannot enumerate a `when`. It catches an anchored key that no screen sends, which is the + * bug that shipped. It does NOT catch a newly added metric, so a new key belongs here too. + */ + @Test + fun `every anchored key is a real detail key`() { + val realKeys = setOf( + "recovery", "strain", "resp", "spo2", "rhr", "hrv", "skin", "rest", + "fitness_age", "vitality", "vo2max_est", "steps_est", + ) + val anchored = realKeys.filter { vitalChartYDomain(it) != null } + assertEquals(setOf("recovery", "rest", "strain"), anchored.toSet()) + assertNull(vitalChartYDomain("sleep_performance")) // the series name, not a detail key + } + /** * Blood oxygen is the case that proves this is not "percentages get 0..100": its real movement lives * in 90..100, so anchoring would flatten the signal into a line at the top. Metrics with no fixed