Today-screen redesign, color/contrast fixes, and honesty bug fixes#146
Conversation
…lyphs In-progress work already sitting in this working tree before this session's UI/UX pass started (not part of that work — carried forward as-is so the tree stays buildable/committable as one coherent unit). Drops the openstrap_icons illustrated-WebP git dependency in favor of a mixed vector-icon stack (phosphor_flutter, solar_icons, iconsax_flutter, fluentui_system_icons, hugeicons) behind lib/ui/kit/os_icons.dart, so OsAppIcon/AppIcon glyphs can finally be tinted (illustrated art couldn't be recolored). Touches the nav pill's center action (draws its own accent circle now instead of relying on pre-rendered art) and a couple of small doc-comment updates in app.dart/cycle_screen.dart that referenced the old "illustrated" icons.
Five confirmed UI/UX bugs: - Readiness/AI-briefing contradiction: the briefing prompt now injects a low/moderate/good band label alongside the raw readiness score and tells the model the band is authoritative for tone (briefing_engine.dart). - Oxygen dips rendered the literal string "null" when odi_per_hour/value were both absent; now falls back to an honest "—" (detail_cards.dart). - LF/HF rendered at 6 decimal places next to whole-number RMSSD/SDNN; rounded to 2dp at the source (local_repository_impl.dart). - Two legitimately different HRV baselines (Winsorized-EWMA vs simple trailing mean) both read as bare "baseline" on the same screen; relabeled "Baseline (trend)" vs "HRV (RMSSD) · EWMA". - The unitless SD1/SD2 ratio and the raw-ms Poincaré SD1/SD2 axes shared one label on the same screen; the ratio is now "SD1/SD2 (ratio)". - Skin-temperature section visibility was accidentally gated on spo2 presence instead of skin_temp presence (detail_cards.dart). Color/contrast system (lib/theme/tokens.dart, lib/ui/design/bento.dart, lib/ui/kit/kit.dart, lib/ui/kit/charts.dart, lib/ui/design/metric_card.dart, lib/ui/design/controls.dart, lib/ui/screens/screens.dart): - New AppColors.tonalFill(hue) — alpha-blends a hue onto the real elevated-surface baseline (not a raw alpha wash over whatever sits behind the widget), so a colored pill/chip/tile's fill never competes in brightness with its own full-saturation foreground text/icon. Applied to Tag (TRUSTED/PROVISIONAL/EST/BETA/REL), DeltaChip, BaselineDeltaChip, BentoTile's soft + accent tones (incl. the calorie tile), the metric-card and StatTile icon chips, and the AI Coach CTA pill. - New AppColors.brand identity (calm cyan/teal), structurally separate from `coral` (now alert/urgent-only) — retints the Material theme's primary/ FilledButton/focus-ring and the `accent`/`accentSoft` semantic aliases, so nav active-state and routine CTAs stop competing with genuinely low/urgent readiness states for the same hue. - Barlow Condensed for hero/display/metric numerals (already available via the existing google_fonts dependency), Manrope kept for prose — splits "confident numeral" from "humane sentence" as two type voices. Today screen redesign (lib/ui/today/today_screen.dart, lib/ui/design/orbit_score.dart, lib/ui/design/arc_gauge.dart): - Readiness ring renders first, full stop — bigger, soft glow on the arc (ArcGauge.glow, reserved for this one hero element), spring-entrance, no floating satellites competing with the score. - Sleep/Heart/Strain/Stress demoted from ring satellites to a quiet _QuickStatsRow underneath. - The AI briefing is a collapsed-by-default one-liner + chevron directly under the ring (via the new Disclosure component), expanding inline to the full paragraph + bullets — replaces the old always-expanded leading card and the HRV/RHR tiles it restated (falls back to those tiles when no BYOK key is configured, so nobody loses the numbers). New lib/ui/design/disclosure.dart: the progressive-disclosure primitive reused for LF/HF, SD1/SD2, pNN, the Today AI insight, and the Sleep Coach card. Includes a fix for a real Flutter bug hit while adding test coverage: a zero-duration AnimatedSize (the reduced-motion path) trips a RenderAnimatedSize layout assertion; reduced motion now skips the animated wrapper entirely instead of animating with Duration.zero. Oxygen-dips relocation (lib/ui/screens/detail_cards.dart, lib/ui/sleep/sleep_detail_screen.dart, lib/data/local_repository_impl.dart, lib/ui/today/today_screen.dart): moved off the Heart tab and the Today bento grid entirely onto the Sleep tab's "Nocturnal heart" tile (grouped with sleeping HR/nadir/waking HR/breath rate) — it's an overnight signal, not a daytime heart metric. getDayHeart no longer supplies spo2; getDaySleep now does (same bundle field, pure re-exposure). The stat is tappable into its own trend via openTrend, so removing the old Heart-tab row and Today tile didn't drop any reachability. Sleep Coach (lib/ui/insights/coach_cards.dart, lib/ui/sleep/sleep_detail_screen.dart, lib/ui/screens/screens.dart): wrapped in Disclosure (collapsed shows "Tonight you need Xh Ym · Y% of need" + the ring; bedtime/wake/alarm expand on tap), and repositioned from a leading card at the top of the Sleep tab's Today segment to below Cycles and above Nocturnal heart — Sleep Coach, Cycles, and Nocturnal heart all live in the same SleepNightContent scroll, so this is a pure reorder, not a new location. Live HR pulse (lib/ui/heart/live_hr_tile.dart): the beating-heart/live-dot animation was already tied to real data (no animation at all off-wrist/ disconnected) but didn't check reduced-motion; now it does. Honest gamification (lib/data/local_repository_impl.dart): getRecords() no longer surfaces the resting-HR personal record while that metric's own Winsorized-EWMA baseline is still "calibrating" elsewhere in the app — never celebrate a number the app itself hasn't vouched for yet. Personal-baselines badge consolidation (lib/ui/screens/detail_cards.dart): one "N metrics still building baseline" header note replaces one TRUSTED/PROVISIONAL pill per row; only non-trusted rows keep their own tag. Verification: flutter analyze clean (only pre-existing baseline info/warnings), flutter test all green, flutter build apk --debug succeeds.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (12)
🚧 Files skipped from review as they are similar to previous changes (9)
📝 WalkthroughWalkthroughThis PR replaces the illustrated icon dependency with local glyph rendering, adds brand-based design tokens and tonal fills, introduces progressive disclosure and reduced-motion handling, relocates oxygen-dip data to Sleep, restructures Today and AI briefing surfaces, and updates related tests and configuration. ChangesDesign foundation and icon system
Disclosure and visual primitives
Health data and detail surfaces
Today and AI briefing flow
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant TodayScreen
participant TodayVitals
participant Disclosure
participant AiBreakdownScreen
TodayScreen->>TodayVitals: pass briefing and callback
TodayVitals->>Disclosure: render collapsed insight
Disclosure->>TodayVitals: expand bullet details
TodayVitals->>AiBreakdownScreen: open full breakdown or regenerate
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/ui/design/orbit_score.dart (1)
123-132: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDisable the gauge sweep under reduced motion.
animatedCoreis skipped, butArcGaugestill uses its defaultanimate: true, so the readiness arc continues its reveal animation when system animations are disabled. Pass the resolved setting through.Proposed fix
confidence: confidence, glow: glow, + animate: !reduceMotion, center:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/ui/design/orbit_score.dart` around lines 123 - 132, Update the ArcGauge construction in the core widget path to pass the resolved reduced-motion setting to its animate parameter, disabling sweep animation when system animations are disabled while preserving the existing animatedCore behavior and other gauge configuration.
🧹 Nitpick comments (3)
lib/ui/insights/coach_cards.dart (1)
220-224: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHoist the duplicated
_alarmCaption(context.read<AppState>())call.Called twice (null-check, then bang). One local read keeps it single-evaluation and drops the
!.♻️ Proposed refactor
- if (_alarmCaption(context.read<AppState>()) != null) ...[ - const SizedBox(height: Sp.x2), - Text(_alarmCaption(context.read<AppState>())!, - style: AppText.captionMuted), - ], + ?switch (_alarmCaption(context.read<AppState>())) { + final String c => Padding( + padding: const EdgeInsets.only(top: Sp.x2), + child: Text(c, style: AppText.captionMuted), + ), + _ => null, + },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/ui/insights/coach_cards.dart` around lines 220 - 224, Update the widget build logic around `_alarmCaption` to evaluate `_alarmCaption(context.read<AppState>())` once into a local variable, use that variable for the null check and displayed Text value, and remove the non-null assertion while preserving the existing spacing and styling.lib/ui/design/disclosure.dart (1)
68-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMerge the toggle row into one semantics node.
Pressableonly adds tap handling here, so the summary, expand/collapse text, and chevron can still be exposed separately. Wrap the row inMergeSemanticsor an explicitSemantics(button: true, ...)so it reads as one disclosure control.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/ui/design/disclosure.dart` around lines 68 - 111, Update the toggle content in the Pressable within the disclosure widget to expose the summary, expand/collapse label, and chevron as one semantics node. Wrap the existing row with MergeSemantics or an explicit Semantics node marked as a button, while preserving the current tap behavior and visual layout.lib/ui/workouts/workout_types.dart (1)
16-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider dropping the now-redundant 4th tuple field.
Every row now sets the glyph and fallback icon to the same value, per the updated comment. If nothing else in the codebase intentionally diverges the two,
workoutTypeOsIcon/the 4th tuple element could be collapsed intoworkoutTypeIconto remove the duplication — optional, since it's low risk either way.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/ui/workouts/workout_types.dart` around lines 16 - 30, Collapse the redundant fourth tuple field in kWorkoutTypes and retain a single workout icon value per row. Update workoutTypeIcon and workoutTypeOsIcon consumers to use that shared field, preserving the existing icon behavior and removing the duplicated tuple data.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/ai/briefing_engine.dart`:
- Around line 153-157: Update readinessBand and TodayVitals._orbitHero to derive
their status labels from one shared readiness-band helper, using the ring
thresholds of 40 and 66 for Run easy, Steady, and Primed. Remove the duplicated
30/60 classification so briefing and screen outputs remain consistent.
In `@lib/data/local_repository_impl.dart`:
- Around line 1562-1567: Update the rhrStatus lookup in getRecords() to use _sub
with the complete dotted path for baselines.resting_hr.status instead of chained
dynamic indexing. Preserve the existing nullable String result and remove
lowest_rhr whenever the resolved status is not trusted.
In `@lib/theme/theme.dart`:
- Around line 38-67: Update the Barlow Condensed usage in the theme typography
getters, including hero, display, metric, and metricSm, to avoid runtime font
fetching: either bundle the required Barlow Condensed assets and configure them
for local use, or disable Google Fonts runtime fetching and provide the bundled
fallback. Preserve the existing typography styles and ensure readiness numerals
render consistently during offline cold launches.
In `@lib/ui/design/arc_gauge.dart`:
- Around line 198-210: Update the glow branch in the arc-painting method to
avoid drawing a continuous halo when confidence is below 0.4: either gate the
existing glow with the same confidence condition as the dashed foreground arc or
render the glow using the matching dash pattern, while preserving the current
glow behavior for higher-confidence arcs.
In `@lib/ui/insights/coach_cards.dart`:
- Around line 176-228: Gate the Disclosure in the need section on bedMin or
wakeMin being non-null, so it is not rendered when neither bedtime nor wake
content exists. Preserve the existing summaryWidget and child content for cases
where either value is available, reusing the extracted _needSummary helper if
introduced.
In `@lib/ui/screens/screens.dart`:
- Around line 129-144: The comment above the AI Coach CTA contradicts the
implementation: AppIcon and Text use AppColors.accent rather than white. Update
the comment to accurately describe the current accent-colored icon and label,
while preserving the existing styling in the Row, AppIcon, and Text widgets.
In `@lib/ui/today/today_screen.dart`:
- Around line 687-694: Update the documentation for the hasAiBriefing field to
describe its current meaning: it reflects whether coachConfig.hasKey enables the
inline insight rendered below the ring within this widget, not a paragraph card
shown above it. Remove references to the old placement and lockstep decision
logic while preserving the field’s behavior.
- Around line 340-356: Update the TodayVitals navigation callback around _push
and AiBreakdownScreen so it awaits the Navigator.push future, then trigger a
Today rebuild when the breakdown screen returns. Ensure
BriefingStore.read(period) is sampled again after generating and popping the
briefing, replacing the current non-awaiting navigation flow.
- Around line 1253-1259: Update the Stress entry in the today screen’s metric
list to format its numeric score consistently with sibling metrics, rounding it
before converting to a string instead of calling raw toString(). Preserve the
existing nullable behavior when t.stress?.score is absent.
---
Outside diff comments:
In `@lib/ui/design/orbit_score.dart`:
- Around line 123-132: Update the ArcGauge construction in the core widget path
to pass the resolved reduced-motion setting to its animate parameter, disabling
sweep animation when system animations are disabled while preserving the
existing animatedCore behavior and other gauge configuration.
---
Nitpick comments:
In `@lib/ui/design/disclosure.dart`:
- Around line 68-111: Update the toggle content in the Pressable within the
disclosure widget to expose the summary, expand/collapse label, and chevron as
one semantics node. Wrap the existing row with MergeSemantics or an explicit
Semantics node marked as a button, while preserving the current tap behavior and
visual layout.
In `@lib/ui/insights/coach_cards.dart`:
- Around line 220-224: Update the widget build logic around `_alarmCaption` to
evaluate `_alarmCaption(context.read<AppState>())` once into a local variable,
use that variable for the null check and displayed Text value, and remove the
non-null assertion while preserving the existing spacing and styling.
In `@lib/ui/workouts/workout_types.dart`:
- Around line 16-30: Collapse the redundant fourth tuple field in kWorkoutTypes
and retain a single workout icon value per row. Update workoutTypeIcon and
workoutTypeOsIcon consumers to use that shared field, preserving the existing
icon behavior and removing the duplicated tuple data.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 84576aff-127b-4cf0-b8ce-0965ed210e4b
⛔ Files ignored due to path filters (1)
pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (37)
lib/ai/briefing_engine.dartlib/app.dartlib/data/local_repository_impl.dartlib/firebase_options.dartlib/theme/theme.dartlib/theme/tokens.dartlib/ui/ai/ai_breakdown_screen.dartlib/ui/cycle/cycle_screen.dartlib/ui/design/arc_gauge.dartlib/ui/design/bento.dartlib/ui/design/controls.dartlib/ui/design/design.dartlib/ui/design/disclosure.dartlib/ui/design/domains.dartlib/ui/design/metric_card.dartlib/ui/design/nav_pill.dartlib/ui/design/orbit_score.dartlib/ui/heart/live_hr_tile.dartlib/ui/insights/coach_cards.dartlib/ui/kit/charts.dartlib/ui/kit/kit.dartlib/ui/kit/os_icons.dartlib/ui/onboarding/welcome_screen.dartlib/ui/pairing_screen.dartlib/ui/screens/detail_cards.dartlib/ui/screens/screens.dartlib/ui/sleep/sleep_detail_screen.dartlib/ui/today/today_screen.dartlib/ui/workouts/workout_types.dartpubspec.yamltest/core_screens_test.darttest/design_redesign_test.darttest/disclosure_test.darttest/flow_screens_redesign_test.darttest/metric_trend_redesign_test.darttest/os_icons_wiring_test.darttest/workout_enrichment_test.dart
All 9 actionable comments + the 1 outside-diff comment, plus one trivial nitpick folded in while already in that file: - readinessBand (briefing_engine.dart) cut at 30/60 while the Today ring's own word-thresholds (_orbitHero) cut at 40/66 — a 35 or 62 could still read as one tone in the briefing and another on the ring, the exact contradiction this session's fix was meant to eliminate, just moved. readinessBand is now THE single source of truth (40/66); _orbitHero derives its Primed/Steady/Run easy word from it instead of duplicating the thresholds. - getRecords() (local_repository_impl.dart) indexed `_sub(latest, 'baselines')?['resting_hr']?['status']` via chained dynamic `[]`, which throws if resting_hr isn't a Map instead of falling into the honest "not trusted" branch. Now uses _sub's own dotted-path form (`_sub(latest, 'baselines.resting_hr')`), which already walks the whole path defensively. - Barlow Condensed was pulled via google_fonts' runtime HTTP fetch — real risk for an app that's local-first/offline by design (the readiness numeral flickering/falling back on a cold offline launch). Downloaded the actual Bold/ExtraBold static TTFs from the google/fonts OFL repo (SIL Open Font License, OFL.txt included) into assets/fonts/BarlowCondensed/, declared them in pubspec.yaml's `flutter.fonts`, and switched hero/display/metric/metricSm to a plain TextStyle(fontFamily: 'Barlow Condensed') instead of GoogleFonts.barlowCondensed(...) — zero network dependency for these styles now. Manrope stays on google_fonts' runtime fetch (pre-existing, unchanged, out of scope — it only ever labels captions/prose, never the one number someone opens the app to check). - ArcGauge's glow painted a continuous halo even when confidence < 0.4, where the foreground arc is deliberately dashed to signal uncertainty — the glow filled the dashed gaps and undercut that signal. Glow is now suppressed under the same confidence < 0.4 threshold. - Sleep Coach's Disclosure still rendered its "Bedtime & alarm" expand affordance when both bedMin and wakeMin were null (need computed, but the schedule recommendation isn't yet), expanding into an empty column. The always-visible "tonight you need" + ring summary is now extracted into _needSummary(), shown plain (no Disclosure wrapper) when there's no bedtime/wake detail to expand into. Folded in the adjacent trivial nitpick while in this exact code: hoisted the duplicated _alarmCaption(context.read<AppState>()) call into one evaluation via a null-aware switch, dropping the redundant second call + the `!`. - Stale comment in screens.dart's AI Coach CTA still said the icon/label "stay full-brightness white" after the tonal-fill fix changed them to AppColors.accent — comment corrected. - Today's onOpenAiBreakdown callback pushed AiBreakdownScreen without awaiting it, so generating a fresh briefing there and popping back left the ring-adjacent line on the stale placeholder/one-liner until some unrelated rebuild happened to re-sample BriefingStore. _push now returns the route's Future (existing fire-and-forget call sites are unaffected — they just don't await it); the AI-insight callback awaits it and calls setState so the freshly-generated briefing shows immediately. - hasAiBriefing's doc comment still described the pre-reposition "leading card above this widget" placement; updated to describe the current ring-adjacent inline placement. - Stress rendered via raw toString() while every sibling in the demoted quick-stats row formats explicitly (RHR .round(), Strain toStringAsFixed(1)) — could show "34.0" beside "52"/"12.4". Now rounds like RHR. - orbit_score.dart's animatedCore already skipped the ring's entrance pop under reduced motion, but ArcGauge still defaulted to animate: true, so the fill-sweep reveal itself still ran. Threads animate: !reduceMotion through. Skipped the other 2 nitpicks (disclosure.dart MergeSemantics, workout_types.dart's redundant 4th tuple field) — the first is a pre-existing-pattern a11y polish item across all three Disclosure call sites, the second lives entirely in a carried-over file from a prior, unrelated session's icon migration; neither was trivial to fold into the files already being touched here. New tests: readinessBand boundary values pinned at the ring's actual 40/66 cuts (test/ai_briefing_test.dart); getRecords() no longer throws on a malformed baselines.resting_hr shape (test/workout_enrichment_test.dart). Verification: flutter analyze clean (same pre-existing baseline items), flutter test 605/608 green (the 3 failures are the same pre-existing history_screens_redesign_test.dart failures verified against bare origin/main before this branch's work, unrelated to this PR), flutter build apk --debug succeeds — confirmed the bundled Barlow Condensed TTFs are actually packaged in the built APK.
Summary
Session-long UI/UX pass: a Today-screen redesign, a color/contrast system fix,
several relocations (Oxygen dips, Sleep Coach), and five confirmed honesty
bugs. Based on top of the just-merged lookback/timeline-scrub work
(
feat/lookback-day-history,fix/lookback-data-gate,fix/recap-graph-scrub)— branched before that landed, then fast-forwarded onto it and reconciled.
Also carries an in-progress, pre-existing icon-pack migration (illustrated →
tintable vector glyphs) that was already sitting uncommitted in this working
tree before this session's work started — separated into its own commit
(
chore(icons): …) since it's not part of this session's design work, butincluded because later commits depend on the vector-icon API it introduces.
Bug fixes
low/moderate/good band label alongside the raw readiness score and tells
the model the band is authoritative for tone, so a 16/100 night can't get
described as "strong recovery."
"null"for oxygen dips — now falls back to an honest—.2dp at the source.
(Winsorized-EWMA vs. simple trailing mean) both read as bare "baseline" on
the same screen; relabeled "Baseline (trend)" vs. "HRV (RMSSD) · EWMA".
shared one label on the same screen; the ratio is now "SD1/SD2 (ratio)".
gated on
spo2presence instead ofskin_temppresence.disclosure.dartreduced-motion crash — a zero-durationAnimatedSizetrips a real Flutter layout assertion (
RenderAnimatedSizemutated duringits own
performLayout); reduced motion now skips the animated wrapperentirely instead of animating with
Duration.zero.Color/contrast system
New
AppColors.tonalFill(hue)— alpha-blends a hue onto the realelevated-surface baseline instead of a raw alpha wash over whatever sits
behind the widget, so a colored pill/chip/tile's fill never competes in
brightness with its own full-saturation foreground text/icon. Applied to the
honesty
Tag(TRUSTED/PROVISIONAL/EST/BETA/REL),DeltaChip,BaselineDeltaChip,BentoTile's soft + accent tones (incl. the calorietile), the metric-card/StatTile icon chips, and the AI Coach CTA pill.
New
AppColors.brandidentity (calm cyan/teal), structurally separate fromcoral(now alert/urgent-only) — retints the Material theme'sprimary/FilledButton/focus-ring and the
accent/accentSoftsemanticaliases, so nav active-state and routine CTAs stop competing with genuinely
low/urgent readiness states for the same hue. Full brightness/saturation is
now reserved for exactly one hero element: the readiness ring's glow.
Barlow Condensed for hero/display/metric numerals (already available via the
existing
google_fontsdependency — no new asset pipeline), Manrope kept forprose — splits "confident numeral" from "humane sentence" into two type
voices.
Today screen redesign
(the one reserved full-brightness element), spring-entrance, no floating
satellites competing with the score.
underneath.
under the ring (new
Disclosurecomponent), expanding inline to the fullparagraph + bullets — replaces the old always-expanded leading card and the
HRV/RHR tiles it restated (falls back to those tiles with no BYOK key
configured, so nobody loses the numbers).
New
lib/ui/design/disclosure.dart: the progressive-disclosure primitivereused for LF/HF, SD1/SD2, pNN, the Today AI insight, and the Sleep Coach
card.
Relocations
entirely, onto the Sleep tab's "Nocturnal heart" tile (grouped with
sleeping HR/nadir/waking HR/breath rate) — it's an overnight signal, not a
daytime heart metric.
getDayHeartno longer suppliesspo2;getDaySleepnow does (same bundle field, pure re-exposure). Stilltappable into its own trend, so no reachability was dropped.
Disclosure(collapsed shows "Tonight you needXh Ym · Y% of need" + the ring; bedtime/wake/alarm expand on tap), and
repositioned from a leading card at the top of the Sleep tab's Today
segment to below Cycles and above Nocturnal heart — all three live in the
same scroll, so this is a pure reorder.
Other
live_hr_tile.dart) — the beating-heart/live-dot animationwas already tied to real data; it now also respects reduced motion.
getRecords()no longer surfaces the resting-HRpersonal record while that metric's own baseline is still "calibrating"
elsewhere in the app.
baseline" header note replaces one TRUSTED/PROVISIONAL pill per row.
Verification
flutter analyze— clean (only pre-existing baseline info/warnings,unchanged from before this branch).
flutter test— 605 passed, 3 failing. The 3 failures are intest/history_screens_redesign_test.dart(TimelineContentchip-tapambiguous-finder issues) and are pre-existing on
main— verified byrunning the same file against a clean
origin/maincheckout before thisbranch's changes were reapplied. Not a regression from this PR.
flutter build apk --debug— succeeds.Test plan
tap to expand
collapsed + repositioned below Cycles
pulse both degrade gracefully
all read as tonal fill + bright text, not flat saturated color
Summary by CodeRabbit
New Features
Bug Fixes