Skip to content

Today-screen redesign, color/contrast fixes, and honesty bug fixes#146

Merged
abdulsaheel merged 3 commits into
mainfrom
feature/today-redesign-and-bugfixes
Jul 25, 2026
Merged

Today-screen redesign, color/contrast fixes, and honesty bug fixes#146
abdulsaheel merged 3 commits into
mainfrom
feature/today-redesign-and-bugfixes

Conversation

@abdulsaheel

@abdulsaheel abdulsaheel commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

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, but
included because later commits depend on the vector-icon API it introduces.

Bug fixes

  • 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, so a 16/100 night can't get
    described as "strong recovery."
  • Literal "null" for oxygen dips — now falls back to an honest .
  • LF/HF at 6 decimal places next to whole-number RMSSD/SDNN — rounded to
    2dp at the source.
  • Baseline mislabeling — 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".
  • SD1/SD2 relabeling — the unitless ratio and the raw-ms Poincaré axes
    shared one label on the same screen; the ratio is now "SD1/SD2 (ratio)".
  • Skin-temperature visibility-gate bug — the section was accidentally
    gated on spo2 presence instead of skin_temp presence.
  • disclosure.dart reduced-motion crash — a zero-duration AnimatedSize
    trips a real Flutter layout assertion (RenderAnimatedSize mutated during
    its own performLayout); reduced motion now skips the animated wrapper
    entirely instead of animating with Duration.zero.

Color/contrast system

New AppColors.tonalFill(hue) — alpha-blends a hue onto the real
elevated-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 calorie
tile), the metric-card/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. 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_fonts dependency — no new asset pipeline), Manrope kept for
prose — splits "confident numeral" from "humane sentence" into two type
voices.

Today screen redesign

  • Readiness ring renders first, full stop — bigger, a soft glow on the arc
    (the one reserved full-brightness element), spring-entrance, no floating
    satellites competing with the score.
  • Sleep/Heart/Strain/Stress demoted from ring satellites to a quiet strip
    underneath.
  • The AI briefing is a collapsed-by-default one-liner + chevron directly
    under the ring (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 with no BYOK key
    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.

Relocations

  • Oxygen dips: 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). Still
    tappable into its own trend, so no reachability was dropped.
  • Sleep Coach: 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 — all three live in the
    same scroll, so this is a pure reorder.

Other

  • Live HR pulse (live_hr_tile.dart) — the beating-heart/live-dot animation
    was already tied to real data; it now also respects reduced motion.
  • Honest gamification — getRecords() no longer surfaces the resting-HR
    personal record while that metric's own baseline is still "calibrating"
    elsewhere in the app.
  • Personal-baselines badge consolidation — one "N metrics still building
    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 in
    test/history_screens_redesign_test.dart (TimelineContent chip-tap
    ambiguous-finder issues) and are pre-existing on main — verified by
    running the same file against a clean origin/main checkout before this
    branch's changes were reapplied. Not a regression from this PR.
  • flutter build apk --debug — succeeds.

Test plan

  • Open Today — ring renders immediately, AI insight collapsed under it,
    tap to expand
  • Heart tab — confirm Oxygen dips no longer appears
  • Sleep tab — confirm Oxygen dips appears in Nocturnal heart, Sleep Coach
    collapsed + repositioned below Cycles
  • Toggle reduced motion — Disclosure expand/collapse and the Heart live
    pulse both degrade gracefully
  • Dark mode — TRUSTED/PROVISIONAL pills, calorie tile, AI Coach button
    all read as tonal fill + bright text, not flat saturated color

Summary by CodeRabbit

  • New Features

    • Added progressive disclosure sections for AI insights and sleep coaching details.
    • Added overnight oxygen-dip stats to sleep nocturnal details.
    • Added an optional “glow” effect to arc-based gauges, refreshed branded theming/typography/tab icon visuals, and improved reduced-motion accessibility across animated health indicators.
  • Bug Fixes

    • Improved readiness labeling for AI briefings and hid unreliable resting-HR highlights when baselines aren’t trusted.
    • Hardened oxygen-dip display to avoid null/legacy values and improved metric formatting (e.g., HRV rounding).

…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.
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 54ad7a9c-1e91-4994-aee0-ae1afc8c4447

📥 Commits

Reviewing files that changed from the base of the PR and between 9b46c10 and 78a454e.

⛔ Files ignored due to path filters (2)
  • assets/fonts/BarlowCondensed/BarlowCondensed-Bold.ttf is excluded by !**/*.ttf
  • assets/fonts/BarlowCondensed/BarlowCondensed-ExtraBold.ttf is excluded by !**/*.ttf
📒 Files selected for processing (12)
  • assets/fonts/BarlowCondensed/OFL.txt
  • lib/ai/briefing_engine.dart
  • lib/data/local_repository_impl.dart
  • lib/theme/theme.dart
  • lib/ui/design/arc_gauge.dart
  • lib/ui/design/orbit_score.dart
  • lib/ui/insights/coach_cards.dart
  • lib/ui/screens/screens.dart
  • lib/ui/today/today_screen.dart
  • pubspec.yaml
  • test/ai_briefing_test.dart
  • test/workout_enrichment_test.dart
🚧 Files skipped from review as they are similar to previous changes (9)
  • lib/ui/screens/screens.dart
  • lib/ui/design/arc_gauge.dart
  • test/workout_enrichment_test.dart
  • lib/ui/insights/coach_cards.dart
  • lib/data/local_repository_impl.dart
  • lib/theme/theme.dart
  • lib/ai/briefing_engine.dart
  • lib/ui/design/orbit_score.dart
  • lib/ui/today/today_screen.dart

📝 Walkthrough

Walkthrough

This 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.

Changes

Design foundation and icon system

Layer / File(s) Summary
Design tokens, theme, and local icons
lib/theme/*, lib/ui/kit/os_icons.dart, pubspec.yaml, lib/firebase_options.dart
Brand palette roles, typography, theme accents, tonal fills, local OsIcon glyph rendering, icon-pack dependencies, bundled fonts, and platform Firebase values are updated.
Icon consumers and validation
lib/ui/cycle/*, lib/ui/workouts/*, test/*icon*, test/*redesign*
Screen icons, workout fallbacks, and icon-related tests use the local glyph set.

Disclosure and visual primitives

Layer / File(s) Summary
Progressive disclosure
lib/ui/design/disclosure.dart, lib/ui/design/design.dart, test/disclosure_test.dart
Adds an expandable Disclosure widget with custom summaries, accessibility semantics, animated expansion, and reduced-motion behavior.
Gauges, fills, shadows, and motion
lib/ui/design/*, lib/ui/heart/live_hr_tile.dart, lib/ui/kit/*, lib/ui/onboarding/*, lib/ui/pairing_screen.dart
Adds gauge glow support, standardizes tonal fills and brand shadows, updates navigation icon rendering, and suppresses decorative animations when motion is reduced.

Health data and detail surfaces

Layer / File(s) Summary
Payload and record changes
lib/data/local_repository_impl.dart, lib/ui/sleep/sleep_detail_screen.dart
Rounds LF/HF values, moves SPO2 data to Sleep payloads, gates resting-HR records on trusted baselines, and adds tappable nocturnal oxygen-dip metrics.
Heart and Sleep presentation
lib/ui/screens/detail_cards.dart, lib/ui/insights/coach_cards.dart, lib/ui/screens/screens.dart
Collapses dense metric groups, consolidates baseline statuses, embeds Sleep Coach content, and removes Heart respiratory oxygen-dip tiles.
Surface validation
test/core_screens_test.dart, test/metric_trend_redesign_test.dart, test/workout_enrichment_test.dart
Tests oxygen-dip placement and null handling, baseline labels, and trusted-baseline record behavior.

Today and AI briefing flow

Layer / File(s) Summary
Readiness briefing contract
lib/ai/briefing_engine.dart
Adds readiness bands and serializes the authoritative band into AI prompts with corresponding tone rules.
Today inline insight and quick stats
lib/ui/today/today_screen.dart, test/core_screens_test.dart, test/design_redesign_test.dart
Moves AI insight beneath the readiness ring, adds expandable bullets and regeneration access, hides duplicate HRV/RHR tiles, removes orbit satellites, and adds a quick-stats row.
Today behavior tests
test/core_screens_test.dart, test/design_redesign_test.dart, test/ai_briefing_test.dart
Verifies collapsed and expanded AI insight states, placeholder actions, absent-briefing behavior, quick-stats routing, and readiness-band boundaries.

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
Loading

Possibly related PRs

  • OpenStrap/edge#121: Both changes adjust how Today’s readiness value is surfaced and labeled.

Suggested reviewers: dannymcc

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main Today redesign, visual updates, and data honesty fixes in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/today-redesign-and-bugfixes

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Disable the gauge sweep under reduced motion.

animatedCore is skipped, but ArcGauge still uses its default animate: 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 value

Hoist 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 win

Merge the toggle row into one semantics node. Pressable only adds tap handling here, so the summary, expand/collapse text, and chevron can still be exposed separately. Wrap the row in MergeSemantics or an explicit Semantics(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 value

Consider 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 into workoutTypeIcon to 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

📥 Commits

Reviewing files that changed from the base of the PR and between db356e6 and 9b46c10.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (37)
  • lib/ai/briefing_engine.dart
  • lib/app.dart
  • lib/data/local_repository_impl.dart
  • lib/firebase_options.dart
  • lib/theme/theme.dart
  • lib/theme/tokens.dart
  • lib/ui/ai/ai_breakdown_screen.dart
  • lib/ui/cycle/cycle_screen.dart
  • lib/ui/design/arc_gauge.dart
  • lib/ui/design/bento.dart
  • lib/ui/design/controls.dart
  • lib/ui/design/design.dart
  • lib/ui/design/disclosure.dart
  • lib/ui/design/domains.dart
  • lib/ui/design/metric_card.dart
  • lib/ui/design/nav_pill.dart
  • lib/ui/design/orbit_score.dart
  • lib/ui/heart/live_hr_tile.dart
  • lib/ui/insights/coach_cards.dart
  • lib/ui/kit/charts.dart
  • lib/ui/kit/kit.dart
  • lib/ui/kit/os_icons.dart
  • lib/ui/onboarding/welcome_screen.dart
  • lib/ui/pairing_screen.dart
  • lib/ui/screens/detail_cards.dart
  • lib/ui/screens/screens.dart
  • lib/ui/sleep/sleep_detail_screen.dart
  • lib/ui/today/today_screen.dart
  • lib/ui/workouts/workout_types.dart
  • pubspec.yaml
  • test/core_screens_test.dart
  • test/design_redesign_test.dart
  • test/disclosure_test.dart
  • test/flow_screens_redesign_test.dart
  • test/metric_trend_redesign_test.dart
  • test/os_icons_wiring_test.dart
  • test/workout_enrichment_test.dart

Comment thread lib/ai/briefing_engine.dart
Comment thread lib/data/local_repository_impl.dart
Comment thread lib/theme/theme.dart
Comment thread lib/ui/design/arc_gauge.dart Outdated
Comment thread lib/ui/insights/coach_cards.dart Outdated
Comment thread lib/ui/screens/screens.dart
Comment thread lib/ui/today/today_screen.dart
Comment thread lib/ui/today/today_screen.dart Outdated
Comment thread lib/ui/today/today_screen.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.
@abdulsaheel
abdulsaheel merged commit a4f36f0 into main Jul 25, 2026
1 check passed
@abdulsaheel
abdulsaheel deleted the feature/today-redesign-and-bugfixes branch July 25, 2026 13:21
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