Skip to content

fix: implement ui/ux bug backlog across navigation, errors, and theme - #63

Merged
YKDBontekoe merged 3 commits into
mainfrom
cursor/ui-ux-bug-fixes-f84f
Jul 6, 2026
Merged

fix: implement ui/ux bug backlog across navigation, errors, and theme#63
YKDBontekoe merged 3 commits into
mainfrom
cursor/ui-ux-bug-fixes-f84f

Conversation

@YKDBontekoe

@YKDBontekoe YKDBontekoe commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the full UI/UX bug & improvement backlog (49 tasks) across bugs, visual glitches, navigation, error UX, theme/dark mode, architecture, accessibility, web responsiveness, cleanup, and tests.

P0 — Bugs & data integrity

  • Fix run carousel overflow via layout tokens and taller carousel height
  • Guard post-run debrief XP save — only mark processed after successful saveCharacter
  • Surface adventure persistence failures via AsyncError in trail panel
  • Wire Privacy Policy / Terms of Service to hosted URLs (url_launcher)
  • Add close/back button to coach chat app bar and model setup screen

P1 — Visual glitches & navigation

  • Deduplicate dashboard health error UI (retry lives in ReadinessCard)
  • Trail error card with retry (replaces loading shimmer)
  • Platform-aware health labels (HealthPlatformLabels) across settings/character/manual run
  • Flat MetricTile variant to fix nested card styling
  • Ellipsize run card source names; meet tap target minimums
  • Simplify health import progress to shimmer-only
  • Dark mode on shell, training, character, run history/route, coach app bar
  • StatefulShellRoute for /, /training, /character deep links
  • RunRouteMissingPage for cold /run-route loads; NotFoundPage error builder
  • Explicit back navigation on run history; remove settings shell bottom padding

P1 — Error UX & onboarding

  • Shared KynosInlineErrorCard with retry across training, quests, daily quest teaser, readiness
  • Coach chat provider error state UI
  • Run route retry + Open in Maps link on web/Android
  • Character empty state with health connect, import, and manual run CTAs
  • Onboarding: Skip → dashboard; Get Started → health permissions + import
  • Router initial location from prefs (no onboarding flash)

P2 — Polish, a11y, web, architecture

  • Shimmer loading in week momentum and gait model cards
  • Layout tokens for chat input clearance, carousel dimensions
  • Hide language picker until i18n ships; sync-load theme pref
  • Skip portrait lock on web; ResponsiveCenter wrapper
  • Semantics on metric tiles, gamekit buttons, page dots, tappable cards
  • Move health import providers + character provider + charts to shared/
  • Riverpod notifiers for health import and manual run pages
  • Standardize healthSummary failure handling; export invalidateHealthProviders
  • Quest save failure propagation to AsyncError

P3 — Cleanup & tests

  • Remove unused today_insight_cards and acwr_guardrail_card
  • Register /nexus-lab route; link from Training tab
  • Extract SettingsAppearanceSection; add replay onboarding action
  • New smoke tests: settings, onboarding, quest provider persistence failure
  • Update README roadmap checkboxes

Validation

  • flutter analyze — no errors
  • flutter test — 130 tests passing
  • flutter build web — succeeds
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Added a 404 “Page not found” screen with a return-to-dashboard action.
    • Extended navigation to Training, Character, and Nexus Lab.
    • Added Privacy Policy/Terms opening and a “Replay onboarding” action.
    • Added health data import and manual run flows, including map opening for missing route points.
  • Bug Fixes

    • Improved inline error states across dashboards, chat, runs, quests, and imports with clearer messages and one-tap retry.
    • Updated theming, spacing, and accessibility (better labels/semantics) plus more consistent responsive layouts.

Addresses the full UI/UX audit: P0 data-integrity fixes, visual glitches,
navigation/deep links, error retry UX, dark mode, architecture refactors,
accessibility polish, and new tests.

Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR reshapes routing and the dashboard shell, moves health and manual-run logic into providers, adds shared inline error UI, updates theming/layout utilities, and revises onboarding/settings flows and tests.

Changes

App Refresh Cohort

Layer / File(s) Summary
Routing refactor and error/missing pages
lib/app/router.dart, lib/app/shell_page.dart, lib/app/not_found_page.dart, lib/features/dashboard/presentation/pages/run_route_missing_page.dart, lib/app/shell_navigation_scope.dart, lib/features/coach_chat/presentation/widgets/coach_chat_app_bar.dart, lib/features/coach_chat/presentation/widgets/model_setup_screen.dart
Adds StatefulShellRoute.indexedStack routing with dashboard/training/character branches, a themed 404 page, a run-route-missing page, and pop-or-go close/back handlers in app bars and setup screens.
Health and settings provider flows
lib/shared/providers/shared_preferences_provider.dart, lib/shared/providers/shared_preferences_provider.g.dart, lib/features/settings/providers/settings_provider.dart, lib/features/settings/providers/health_import_provider.dart, lib/features/settings/providers/health_import_provider.g.dart, lib/features/settings/providers/manual_run_provider.dart, lib/features/settings/providers/manual_run_provider.g.dart, lib/shared/providers/health_import_providers.dart, lib/shared/providers/health_providers.dart, lib/shared/providers/health_providers.g.dart, lib/infrastructure/health/health_infrastructure_providers.dart, lib/infrastructure/health/platform_imported_health_store_web.dart, lib/main.dart
Moves shared preferences behind Riverpod, adds health import and manual run notifiers, rewires settings persistence, and updates health-provider invalidation and use-case wiring.
Inline error cards and shared UI updates
lib/shared/widgets/kynos_inline_error_card.dart, lib/shared/widgets/widgets.dart, lib/shared/widgets/kynos_loading_line.dart, lib/shared/widgets/kynos_page_dots.dart, lib/shared/widgets/metric_tile.dart, lib/shared/widgets/gait_model_card.dart, lib/shared/widgets/run_card.dart, lib/features/dashboard/presentation/widgets/readiness_card.dart, lib/features/dashboard/presentation/widgets/daily_quest_teaser.dart, lib/features/dashboard/presentation/pages/dashboard_page.dart, lib/features/dashboard/presentation/pages/run_history_page.dart, lib/features/dashboard/presentation/pages/run_route_page.dart, lib/features/dashboard/presentation/pages/training_page.dart, lib/features/dashboard/presentation/widgets/week_momentum_card.dart, lib/features/dashboard/presentation/widgets/character_glance_card.dart, lib/features/dashboard/presentation/widgets/gait_teaser_card.dart, lib/features/coach_chat/presentation/pages/coach_chat_page.dart, lib/features/coach_chat/presentation/widgets/message_list.dart, lib/features/character/presentation/widgets/quest_card.dart, lib/features/character/presentation/widgets/trail_run_game_panel.dart, lib/features/character/providers/adventure_provider.dart, lib/features/character/providers/quest_provider.dart, lib/features/dashboard/providers/post_run_debrief_provider.dart, test/features/character/quest_provider_test.dart, test/features/settings/health_import_progress_card_test.dart
Adds KynosInlineErrorCard and shared loading/tap-target/accessibility updates, then wires retry behavior into multiple async error states and provider failures.
Theming, layout, and shared utilities
lib/core/theme/layout.dart, lib/core/constants/app_constants.dart, lib/shared/utils/health_platform_labels.dart, lib/shared/utils/navigation_utils.dart, lib/shared/utils/url_opener.dart, lib/shared/widgets/responsive_center.dart, lib/shared/widgets/charts/*, lib/features/character/presentation/pages/character_page.dart, lib/features/dashboard/presentation/pages/run_history_page.dart, lib/features/dashboard/presentation/widgets/connect_healthkit_card.dart, lib/features/dashboard/presentation/widgets/last_run_preview.dart, lib/features/dashboard/presentation/widgets/trend_carousel.dart, lib/features/settings/presentation/pages/settings_page.dart, lib/features/settings/presentation/widgets/settings_appearance_section.dart, lib/features/settings/presentation/widgets/health_import_progress_card.dart, lib/features/training/presentation/pages/training_page.dart, lib/features/training/presentation/widgets/past_runs_list.dart, lib/features/training/presentation/widgets/trend_cards.dart, pubspec.yaml
Replaces GoogleFonts/AppTheme usage with context.kynosTheme, adds layout constants, extracts platform-aware labels, adds external URL launching for legal links, and updates shared chart/widget imports and responsive layout helpers.
Onboarding flow and tests
lib/features/onboarding/presentation/onboarding_page.dart, lib/features/onboarding/providers/onboarding_provider.dart, lib/features/onboarding/providers/onboarding_provider.g.dart, test/features/onboarding/onboarding_page_test.dart, test/features/settings/settings_page_test.dart, test/widget_test.dart, README.md
Adds async onboarding completion helpers with mounted checks, reset support, semantic dot indicators, and widget coverage; also marks roadmap items complete.
Misc
lib/main.dart
Restricts portrait orientation lock to non-web platforms.

Estimated code review effort: 4 (Complex) | ~75 minutes

Possibly related PRs

  • YKDBontekoe/KYNOS#31: Shares design-system and shared-widget foundation work, including layout/token and shell-related UI changes.
  • YKDBontekoe/KYNOS#42: Related to the health import flow and Apple Health export parsing that this PR wires into provider-driven UI.
  • YKDBontekoe/KYNOS#55: Related through the trail run game panel and its updated error/retry behavior.

Poem

A hop through routes, both new and bright,
With cards that catch each error's plight. 🐇
Themes align, semantics sing,
And providers hum through every wing.
This bunny nods: neat paths, good light—
The burrow’s tidy, swift, and bright! 🥕

🚥 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 accurately summarizes the PR’s main scope: broad UI/UX fixes spanning navigation, errors, and theme work.
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.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
@YKDBontekoe
YKDBontekoe marked this pull request as ready for review July 6, 2026 07:38

@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: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lib/features/character/presentation/widgets/gamekit_panel.dart (1)

73-100: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Exclude the descendant semantics here
Text(label, ...) still contributes its own accessibility node, so this button can be announced with the label twice. Add excludeSemantics: true to the Semantics wrapper so the custom label is the only one exposed.

🤖 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/features/character/presentation/widgets/gamekit_panel.dart` around lines
73 - 100, The Semantics wrapper in gamekit_panel.dart is exposing both the
custom label and the Text(label, ...) descendant, causing duplicate
accessibility announcements. Update the Semantics widget around the
GestureDetector/Container in the relevant build method to exclude descendant
semantics so only the explicit label is exposed, while keeping the button role
and tap behavior unchanged.
lib/features/character/providers/quest_provider.dart (1)

26-47: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Reward can be lost after a partial save failure. If saveCharacter fails after saveQuests succeeds, the quest stays completed but the XP/stat gain never lands. The retry action on QuestPanel only invalidates questProvider, so it reloads the already-completed quests and never retries the character update. Make the quest completion and reward grant atomic, or persist a retryable “reward pending” state.

🤖 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/features/character/providers/quest_provider.dart` around lines 26 - 47,
The quest completion flow in quest_provider.dart is not atomic: `saveQuests` can
succeed before `saveCharacter`, leaving the quest completed but the reward
unapplied. Update the `completeQuest` logic so the quest state and character
reward are committed together, or persist a retryable pending-reward state that
survives failures. Use the existing `repo.saveQuests`, `charRepo.saveCharacter`,
and `runnerCharacterProvider` invalidation paths to ensure a retry can reapply
the XP/stat gain instead of only reloading completed quests.
🧹 Nitpick comments (14)
lib/features/dashboard/presentation/pages/run_route_missing_page.dart (1)

19-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated pop-or-fallback navigation logic.

The same if (context.canPop()) { context.pop(); } else { context.go(fallback); } block is repeated verbatim here, in model_setup_screen.dart, and in coach_chat_app_bar.dart. Consider extracting a small shared helper (e.g. in lib/shared/utils/) such as void popOrGo(BuildContext context, String fallbackRoute) to keep this DRY and centralize any future changes (e.g. adding analytics or Semantics consistently).

🤖 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/features/dashboard/presentation/pages/run_route_missing_page.dart` around
lines 19 - 28, The back-button handler in the route-missing page duplicates the
same pop-or-fallback navigation logic used elsewhere. Extract the repeated
`context.canPop() / context.pop() / context.go(fallback)` behavior into a shared
helper such as `popOrGo(BuildContext context, String fallbackRoute)` in a common
utils location, then update the `IconButton` callback here to call that helper
so the navigation behavior stays centralized across
`run_route_missing_page.dart`, `model_setup_screen.dart`, and
`coach_chat_app_bar.dart`.
lib/features/coach_chat/presentation/widgets/model_setup_screen.dart (1)

52-65: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Close button missing accessible label.

Unlike the parallel close button added in coach_chat_app_bar.dart (which wraps the IconButton in Semantics(label: 'Close coach chat', button: true) and sets tooltip: 'Close'), this IconButton has neither a tooltip nor a Semantics label. Screen readers will announce it as an unlabeled button.

♻️ Proposed fix
       appBar: showClose
           ? AppBar(
               leading: IconButton(
                 icon: const Icon(Icons.close_rounded),
+                tooltip: 'Close',
                 onPressed: () {
                   if (context.canPop()) {
                     context.pop();
                   } else {
                     context.go(Routes.dashboard);
                   }
                 },
               ),
             )
           : 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/features/coach_chat/presentation/widgets/model_setup_screen.dart` around
lines 52 - 65, The close control in model_setup_screen.dart is missing an
accessible label, unlike the matching button in coach_chat_app_bar.dart. Update
the AppBar leading IconButton used when showClose is true to include the same
accessibility treatment: add a tooltip and wrap it with a Semantics label/button
role so screen readers announce it properly. Use the existing close button
implementation in coach_chat_app_bar.dart as the reference for the IconButton
setup.
lib/app/router.dart (1)

42-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated SharedPreferences key instead of reusing the provider.

'onboarding_completed' is hardcoded here, duplicating the private _key in OnboardingCompleted (lib/features/onboarding/providers/onboarding_provider.dart). If that key ever changes, initialLocation silently desyncs from the actual completion state (redirect would still self-correct on first frame, but causes an avoidable flash of the wrong screen). Prefer reading the existing provider directly instead of re-implementing the read.

♻️ Proposed fix
 final routerProvider = Provider<GoRouter>((ref) {
-  final prefs = ref.watch(sharedPreferencesProvider);
-  final hasCompletedOnboarding = prefs.getBool('onboarding_completed') ?? false;
+  final hasCompletedOnboarding = ref.read(onboardingCompletedProvider);
   final refresh = _RouterRefreshNotifier(ref);
🤖 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/app/router.dart` around lines 42 - 48, The initial route logic in
`GoRouter` is duplicating the onboarding completion key by reading
`SharedPreferences` directly instead of reusing the existing
`OnboardingCompleted` provider. Update `router.dart` to derive
`hasCompletedOnboarding` from the provider used by onboarding state, so
`initialLocation` stays in sync with the source of truth and avoids hardcoded
key drift. Refer to `GoRouter` setup and the onboarding provider
(`OnboardingCompleted`) when wiring the check.
lib/features/dashboard/presentation/widgets/character_glance_card.dart (1)

65-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Leftover GoogleFonts.inter usage in a theming-migration PR.

This XP/"Train weakest" row still uses GoogleFonts.inter(fontSize:..., color:...) while the rest of this layer migrates typography to Theme.of(context).textTheme + context.kynosTheme colors (see the character_page.dart and gait_teaser_card.dart changes in this same PR). Worth aligning for consistency with the stated migration goal.

🤖 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/features/dashboard/presentation/widgets/character_glance_card.dart`
around lines 65 - 78, The “Train weakest” row in CharacterGlanceCard is still
using GoogleFonts.inter, which is inconsistent with the typography/theme
migration used elsewhere. Update the Text widgets in character_glance_card.dart
to use Theme.of(context).textTheme styles combined with context.kynosTheme
colors, matching the patterns used in character_page.dart and
gait_teaser_card.dart, and remove the direct GoogleFonts.inter usage from this
widget.
lib/features/character/presentation/pages/character_page.dart (1)

156-161: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No feedback path for health permission request failure.

ref.read(healthPermissionsProvider.notifier).request() is fired without awaiting or listening for errors/loading. If the permission request fails or is denied, the user gets no indication — the button just does nothing visible. Consider ref.listen(healthPermissionsProvider, ...) to surface a snackbar/error state, consistent with the inline-error-card pattern used elsewhere in this PR.

🤖 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/features/character/presentation/pages/character_page.dart` around lines
156 - 161, The health permission request in CharacterPage is fire-and-forget, so
failures or denials are invisible to the user. Update the `FilledButton` action
or surrounding `CharacterPage` logic to observe `healthPermissionsProvider`
state and surface errors/loading, using `ref.listen` on
`healthPermissionsProvider` to show a snackbar or inline error consistent with
the existing error-card pattern. Keep the request call in
`healthPermissionsProvider.notifier.request()` but wire its failure path to
user-facing feedback.
lib/shared/widgets/kynos_page_dots.dart (1)

24-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a single semantics node for the whole indicator.

Wrapping each dot individually may cause screen readers to stop on every dot while swiping through the row. A single Semantics around the Row (e.g., label: 'Page ${activeIndex + 1} of $count') with children excluded is a more common a11y pattern for page indicators and reduces traversal noise.

🤖 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/shared/widgets/kynos_page_dots.dart` around lines 24 - 35, The page
indicator currently adds a separate Semantics node for each dot in
kynos_page_dots.dart, which creates unnecessary screen reader traversal. Update
the page indicator widget to use a single Semantics wrapper around the entire
Row in the relevant widget/build method, with the label based on the current
active page and the count, and exclude the individual dot children from
accessibility so only one announcement is exposed.
lib/shared/widgets/gait_model_card.dart (1)

86-86: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Loading state loses progress semantics for screen readers.

Replacing LinearProgressIndicator() with KynosLoadingLine drops the built-in progress-bar semantics that assistive tech relies on to announce a busy/loading state; per the widget's implementation it's a plain shimmer Container with no Semantics wrapper (only an optional visible label). This affects every usage of KynosLoadingLine across the PR (e.g. also week_momentum_card.dart line 49). Given this cohort is focused on accessibility, consider wrapping KynosLoadingLine itself with Semantics(label: ..., liveRegion: true) or similar at the shared-widget level.

🤖 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/shared/widgets/gait_model_card.dart` at line 86, The shared loading
widget is missing progress semantics, so screen readers no longer get a
busy/loading announcement when KynosLoadingLine is used. Update KynosLoadingLine
itself to include appropriate Semantics (for example, a label and liveRegion on
the widget’s root) so every caller, including gait_model_card.dart and
week_momentum_card.dart, inherits accessible loading behavior without
duplicating wrapper logic.
lib/shared/widgets/responsive_center.dart (1)

1-3: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Import tokens via theme.dart barrel per guidelines.

This shared widget imports core/theme/layout.dart directly rather than the theme.dart barrel used elsewhere for tokens/theme access.

As per coding guidelines, "Shared widgets must import tokens and theme via package:kynos/core/theme/theme.dart".

🤖 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/shared/widgets/responsive_center.dart` around lines 1 - 3, The shared
widget is importing theme tokens directly from core/theme/layout.dart instead of
the required theme.dart barrel. Update the imports in ResponsiveCenter to use
package:kynos/core/theme/theme.dart for token/theme access, and remove the
direct layout.dart import so the widget follows the shared-widget guideline
consistently.

Source: Coding guidelines

test/features/onboarding/onboarding_page_test.dart (1)

1-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for skip/get-started navigation flows.

This test only checks initial rendering. The new async _skipOnboarding/_getStarted logic (with its provider-driven navigation and permission-request branching) has no test coverage. Consider adding tests that tap "Skip" and "Get Started" (overriding healthPermissionsProvider as needed) and assert the resulting route/provider state.

Want me to draft these additional test cases?

🤖 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 `@test/features/onboarding/onboarding_page_test.dart` around lines 1 - 27, The
onboarding widget test only covers initial rendering and misses the async
navigation paths in OnboardingPage, especially the _skipOnboarding and
_getStarted flows. Add widget tests that tap the Skip and Get Started actions,
override healthPermissionsProvider (and any other required providers) to control
the permission branch, and assert the expected navigation or provider-state
changes after the taps. Use the existing OnboardingPage and onboarding_provider
symbols to locate the test targets.
lib/features/dashboard/presentation/widgets/readiness_card.dart (1)

147-157: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Insights error state has no retry, unlike the readiness summary above.

For consistency with the new inline-error/retry pattern (and KynosInlineErrorCard used just above in the same widget), consider offering a retry affordance here too, rather than a static "Insights unavailable" message.

🤖 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/features/dashboard/presentation/widgets/readiness_card.dart` around lines
147 - 157, The insights error state in readiness_card.dart is static and does
not offer a retry path, unlike the readiness summary and the nearby
KynosInlineErrorCard pattern. Update the todayInsightsState.when error branch in
the ReadinessCard widget to render a retry affordance instead of only the
“Insights unavailable” Text, wiring it to the same retry callback/action used by
the other inline error state so users can recover consistently.
lib/features/settings/providers/health_import_provider.dart (1)

16-83: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider @freezed for HealthImportState.

This is a hand-written state class with manual copyWith/clear-flags boilerplate. Per guidelines, data models should use @freezed for immutability, copyWith, equality, and hashCode. Freezed's default copyWith doesn't support explicit-null clearing, so this may have been a deliberate choice, but consider using freezed with a sentinel-based copyWith or a wrapper (e.g., Wrapped<T?>) to align with the convention.

As per coding guidelines, "All data models must use @freezed for immutability, copyWith, equality, and hashCode."

🤖 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/features/settings/providers/health_import_provider.dart` around lines 16
- 83, HealthImportState is a hand-written mutable-style state model with custom
copyWith and clear flags, but it should follow the project convention of using
`@freezed` for immutability, equality, and hashCode. Refactor the
HealthImportState class in health_import_provider.dart to a Freezed data class,
keeping the existing fields and state semantics used by HealthImportState and
its copyWith behavior. If explicit null clearing is still needed for fields like
error, progressMessage, and importedWorkout, preserve that via a
Freezed-friendly sentinel/wrapper approach rather than manual boilerplate.

Source: Coding guidelines

lib/features/dashboard/providers/post_run_debrief_provider.dart (1)

79-84: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Align saveCharacter with the repository contract
CharacterRepository.saveCharacter still returns Future<Failure?> (lib/domain/repositories/character_repository.dart:6-8), while the rest of the repository API uses named records. Update the save methods to the same record-based shape so success/failure handling stays consistent.

🤖 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/features/dashboard/providers/post_run_debrief_provider.dart` around lines
79 - 84, The save flow is still using a nullable Failure return from
saveCharacter, which is inconsistent with the repository’s record-based API.
Update CharacterRepository.saveCharacter and its implementation to return the
same named record shape used by the other methods, then adjust
PostRunDebriefProvider’s save handling to read the record fields instead of
checking for null so success and failure are handled consistently.

Source: Coding guidelines

lib/features/character/providers/adventure_provider.dart (1)

144-145: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the repeated Failure → AsyncError handling.

The same catch block is duplicated in advance(), performCombatAction(), and retreatFromEncounter(). A small private helper (e.g. wrapping the guarded body in a _runGuarded(Future<void> Function() action) that sets _actionInFlight, awaits action(), and maps Failure to AsyncError) would remove the triplication and give a single place to add logging (logger) if these failures should be observable.

♻️ Sketch of a shared helper
Future<void> _runGuarded(Future<void> Function() action) async {
  if (_actionInFlight) return;
  _actionInFlight = true;
  try {
    await action();
  } on Failure catch (failure, stackTrace) {
    state = AsyncError(failure, stackTrace);
  } finally {
    _actionInFlight = false;
  }
}

Also applies to: 216-217, 239-240

🤖 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/features/character/providers/adventure_provider.dart` around lines 144 -
145, The `advance()`, `performCombatAction()`, and `retreatFromEncounter()`
methods in `AdventureProvider` duplicate the same `Failure` to `AsyncError`
catch handling. Extract that shared guarded execution into a private helper such
as `_runGuarded(Future<void> Function() action)` that manages `_actionInFlight`,
awaits the action, and converts `Failure` into `state = AsyncError(...)`, then
have those three methods call the helper instead of repeating the same try/catch
logic.
lib/features/dashboard/presentation/pages/run_history_page.dart (1)

118-135: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Full-page retry error UI duplicated across three files; consider reusing the shared error card.

This Column/Text/Gap/FilledButton retry layout is essentially repeated in run_route_page.dart (Lines 52-69) and coach_chat_page.dart (Lines 104-144). The PR just added KynosInlineErrorCard for exactly this pattern (message + optional retry action); consider reusing/adapting it (or extracting a shared full-page error widget) instead of duplicating the layout three times.

Two smaller nits on top:

  • 'Failed to load runs: $e' surfaces the raw exception toString() to end users; consider a friendlier copy with the technical detail only logged (logger).
  • This page renders the error text in a muted kynos.secondaryLabel, while run_route_page.dart uses kynos.move (the color also used by KynosInlineErrorCard's error icon) — the inconsistency makes this error state visually harder to distinguish from normal text.
🤖 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/features/dashboard/presentation/pages/run_history_page.dart` around lines
118 - 135, The full-page retry error layout in run_history_page should not be
duplicated; reuse KynosInlineErrorCard or extract a shared full-page error
widget for the same message + retry pattern used in run_route_page and
coach_chat_page. Update the error text in the relevant builder to avoid showing
the raw exception directly to users, and log the technical details separately
via logger. Also align the error styling with the shared error treatment by
using the same error color convention as KynosInlineErrorCard instead of
kynos.secondaryLabel.
🤖 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/app/shell_page.dart`:
- Around line 56-67: The Dashboard shortcuts currently use context.go, which
bypasses the shell branch state and resets the nested navigation stack. Update
DashboardTab to accept branch-switch callbacks from ShellPage, and wire those
callbacks to navigationShell.goBranch(...) for the Training and Character
actions so each branch restores its preserved stack instead of navigating to the
root. Use the DashboardTab and ShellPage symbols to locate the callback wiring
and shortcut handlers.

In `@lib/features/character/presentation/widgets/trail_run_game_panel.dart`:
- Around line 27-30: The TrailRunGamePanel error state is exposing raw exception
text in the user-facing KynosInlineErrorCard message. Update the error handling
in the AdventureSession/TrailRunGamePanel builder to use a static, friendly
message like the other KynosInlineErrorCard usages in this PR, and keep the raw
error out of the UI while preserving the existing retry behavior via
ref.invalidate(adventureSessionProvider).

In `@lib/features/dashboard/presentation/widgets/character_glance_card.dart`:
- Around line 27-33: The Semantics wrapper in character_glance_card should
expose the card as one accessibility node instead of combining with child text;
update the CharacterGlanceCard build path to use excludeSemantics: true on the
Semantics around KynosCard. Also make the label in that widget conditional on
onViewCharacter so “tap to view” is only included when the card is actually
interactive, while keeping button tied to onViewCharacter != null.

In `@lib/features/dashboard/presentation/widgets/gait_teaser_card.dart`:
- Around line 28-86: Update the Semantics wrapper in gait_teaser_card.dart
around the KynosCard so the label matches whether onViewTraining is null: use a
tappable label only when the card is actionable, and a non-action label when
disabled. Also set excludeSemantics on the outer Semantics and ensure the
visible content inside the Column (header, MetricTile widgets, and calibration
text) remains readable without being announced twice.

In `@lib/features/onboarding/presentation/onboarding_page.dart`:
- Around line 79-88: The _getStarted flow in onboarding_page.dart completes
onboarding too early, which lets the router guard redirect away before the
health-import navigation runs. In _getStarted, move completeOnboarding() until
after the handoff to Routes.healthImport, or otherwise bypass the guard during
this transition so context.go(Routes.healthImport) can execute reliably. Use the
_getStarted method and onboardingCompletedProvider.notifier.completeOnboarding()
as the key spots to update.

In `@lib/features/settings/presentation/pages/health_import_page.dart`:
- Around line 21-46: The import error transition is being split across multiple
state updates, so the snackbar listener in healthImportPage can miss failures.
Update confirmImport() in the healthImport provider/notifier so it sets error
and isImporting: false in the same state change, or introduce a distinct error
token that changes per failure, then keep the healthImportProvider listener in
health_import_page reacting to that single visible transition.

In `@lib/features/settings/presentation/pages/manual_run_page.dart`:
- Around line 88-100: The manual run error listener is suppressing repeated
validation feedback because ref.listen in ManualRunPage only calls _showError
when next.error differs from previous?.error. Update the ManualRunPage listener
so every failed submit triggers visible feedback even when the error message is
identical, using another change signal from manualRunProvider/saveRun() or by
tracking a submit attempt/state transition instead of comparing error values
alone.

In `@lib/features/settings/providers/health_import_provider.dart`:
- Around line 223-247: The ZIP import success path in _importZip leaves
zipPreview and pickedFile intact, so the preview stays visible and the same
export can be re-imported. Update the success branch in
health_import_provider.dart to clear the ZIP import state after a successful
import, either by resetting zipPreview and pickedFile in state.copyWith or by
marking the ZIP import as completed so the preview cannot be reused.

In `@lib/features/settings/providers/manual_run_provider.dart`:
- Around line 9-36: ManualRunState is a hand-rolled mutable-style data model and
should be converted to a Freezed model to match project guidelines. Update
ManualRunState in manual_run_provider.dart to use `@freezed` (and
`@JsonSerializable` only if needed), remove the manual copyWith implementation,
and define the state as an immutable union/data class so Freezed generates
copyWith, equality, and hashCode. Keep the existing fields and defaults intact
while moving the state definition to the generated Freezed pattern used
elsewhere in the codebase.
- Around line 51-63: The validation errors in saveRun() are emitted with the
same string each time, so ref.listen in manual_run_page.dart can miss repeated
failures when the message doesn’t change. Update ManualRunProvider.saveRun and
the related state model so each validation failure carries a fresh
identifier/nonce or similar freshness marker, and have the listener key off that
instead of error string equality. Use the existing saveRun method and the
state.copyWith(error: ...) flow to locate the change.

In `@lib/features/settings/providers/settings_provider.dart`:
- Line 3: `settings_provider.dart` is depending on `sharedPreferencesProvider`
from the onboarding feature, which violates the shared-provider boundary and
creates cross-feature imports. Move the `sharedPreferencesProvider` definition
out of `onboarding_provider.dart` into a dedicated shared provider under
`shared/providers/`, then update `settings_provider.dart`, onboarding code,
tests, and the app-level override to import the new shared location. Use the
existing `sharedPreferencesProvider` symbol as the migration target and remove
the onboarding import from settings once the provider is relocated.

In `@lib/shared/utils/url_opener.dart`:
- Around line 5-9: The openExternalUrl function currently only checks the
boolean result from launchUrl, so a PlatformException can escape and bypass the
_openLegalUrl fallback. Update openExternalUrl to wrap the launchUrl call in
try/catch, catch platform launch failures, and return false on any exception so
callers like _openLegalUrl can still trigger showUrlLaunchError.

---

Outside diff comments:
In `@lib/features/character/presentation/widgets/gamekit_panel.dart`:
- Around line 73-100: The Semantics wrapper in gamekit_panel.dart is exposing
both the custom label and the Text(label, ...) descendant, causing duplicate
accessibility announcements. Update the Semantics widget around the
GestureDetector/Container in the relevant build method to exclude descendant
semantics so only the explicit label is exposed, while keeping the button role
and tap behavior unchanged.

In `@lib/features/character/providers/quest_provider.dart`:
- Around line 26-47: The quest completion flow in quest_provider.dart is not
atomic: `saveQuests` can succeed before `saveCharacter`, leaving the quest
completed but the reward unapplied. Update the `completeQuest` logic so the
quest state and character reward are committed together, or persist a retryable
pending-reward state that survives failures. Use the existing `repo.saveQuests`,
`charRepo.saveCharacter`, and `runnerCharacterProvider` invalidation paths to
ensure a retry can reapply the XP/stat gain instead of only reloading completed
quests.

---

Nitpick comments:
In `@lib/app/router.dart`:
- Around line 42-48: The initial route logic in `GoRouter` is duplicating the
onboarding completion key by reading `SharedPreferences` directly instead of
reusing the existing `OnboardingCompleted` provider. Update `router.dart` to
derive `hasCompletedOnboarding` from the provider used by onboarding state, so
`initialLocation` stays in sync with the source of truth and avoids hardcoded
key drift. Refer to `GoRouter` setup and the onboarding provider
(`OnboardingCompleted`) when wiring the check.

In `@lib/features/character/presentation/pages/character_page.dart`:
- Around line 156-161: The health permission request in CharacterPage is
fire-and-forget, so failures or denials are invisible to the user. Update the
`FilledButton` action or surrounding `CharacterPage` logic to observe
`healthPermissionsProvider` state and surface errors/loading, using `ref.listen`
on `healthPermissionsProvider` to show a snackbar or inline error consistent
with the existing error-card pattern. Keep the request call in
`healthPermissionsProvider.notifier.request()` but wire its failure path to
user-facing feedback.

In `@lib/features/character/providers/adventure_provider.dart`:
- Around line 144-145: The `advance()`, `performCombatAction()`, and
`retreatFromEncounter()` methods in `AdventureProvider` duplicate the same
`Failure` to `AsyncError` catch handling. Extract that shared guarded execution
into a private helper such as `_runGuarded(Future<void> Function() action)` that
manages `_actionInFlight`, awaits the action, and converts `Failure` into `state
= AsyncError(...)`, then have those three methods call the helper instead of
repeating the same try/catch logic.

In `@lib/features/coach_chat/presentation/widgets/model_setup_screen.dart`:
- Around line 52-65: The close control in model_setup_screen.dart is missing an
accessible label, unlike the matching button in coach_chat_app_bar.dart. Update
the AppBar leading IconButton used when showClose is true to include the same
accessibility treatment: add a tooltip and wrap it with a Semantics label/button
role so screen readers announce it properly. Use the existing close button
implementation in coach_chat_app_bar.dart as the reference for the IconButton
setup.

In `@lib/features/dashboard/presentation/pages/run_history_page.dart`:
- Around line 118-135: The full-page retry error layout in run_history_page
should not be duplicated; reuse KynosInlineErrorCard or extract a shared
full-page error widget for the same message + retry pattern used in
run_route_page and coach_chat_page. Update the error text in the relevant
builder to avoid showing the raw exception directly to users, and log the
technical details separately via logger. Also align the error styling with the
shared error treatment by using the same error color convention as
KynosInlineErrorCard instead of kynos.secondaryLabel.

In `@lib/features/dashboard/presentation/pages/run_route_missing_page.dart`:
- Around line 19-28: The back-button handler in the route-missing page
duplicates the same pop-or-fallback navigation logic used elsewhere. Extract the
repeated `context.canPop() / context.pop() / context.go(fallback)` behavior into
a shared helper such as `popOrGo(BuildContext context, String fallbackRoute)` in
a common utils location, then update the `IconButton` callback here to call that
helper so the navigation behavior stays centralized across
`run_route_missing_page.dart`, `model_setup_screen.dart`, and
`coach_chat_app_bar.dart`.

In `@lib/features/dashboard/presentation/widgets/character_glance_card.dart`:
- Around line 65-78: The “Train weakest” row in CharacterGlanceCard is still
using GoogleFonts.inter, which is inconsistent with the typography/theme
migration used elsewhere. Update the Text widgets in character_glance_card.dart
to use Theme.of(context).textTheme styles combined with context.kynosTheme
colors, matching the patterns used in character_page.dart and
gait_teaser_card.dart, and remove the direct GoogleFonts.inter usage from this
widget.

In `@lib/features/dashboard/presentation/widgets/readiness_card.dart`:
- Around line 147-157: The insights error state in readiness_card.dart is static
and does not offer a retry path, unlike the readiness summary and the nearby
KynosInlineErrorCard pattern. Update the todayInsightsState.when error branch in
the ReadinessCard widget to render a retry affordance instead of only the
“Insights unavailable” Text, wiring it to the same retry callback/action used by
the other inline error state so users can recover consistently.

In `@lib/features/dashboard/providers/post_run_debrief_provider.dart`:
- Around line 79-84: The save flow is still using a nullable Failure return from
saveCharacter, which is inconsistent with the repository’s record-based API.
Update CharacterRepository.saveCharacter and its implementation to return the
same named record shape used by the other methods, then adjust
PostRunDebriefProvider’s save handling to read the record fields instead of
checking for null so success and failure are handled consistently.

In `@lib/features/settings/providers/health_import_provider.dart`:
- Around line 16-83: HealthImportState is a hand-written mutable-style state
model with custom copyWith and clear flags, but it should follow the project
convention of using `@freezed` for immutability, equality, and hashCode. Refactor
the HealthImportState class in health_import_provider.dart to a Freezed data
class, keeping the existing fields and state semantics used by HealthImportState
and its copyWith behavior. If explicit null clearing is still needed for fields
like error, progressMessage, and importedWorkout, preserve that via a
Freezed-friendly sentinel/wrapper approach rather than manual boilerplate.

In `@lib/shared/widgets/gait_model_card.dart`:
- Line 86: The shared loading widget is missing progress semantics, so screen
readers no longer get a busy/loading announcement when KynosLoadingLine is used.
Update KynosLoadingLine itself to include appropriate Semantics (for example, a
label and liveRegion on the widget’s root) so every caller, including
gait_model_card.dart and week_momentum_card.dart, inherits accessible loading
behavior without duplicating wrapper logic.

In `@lib/shared/widgets/kynos_page_dots.dart`:
- Around line 24-35: The page indicator currently adds a separate Semantics node
for each dot in kynos_page_dots.dart, which creates unnecessary screen reader
traversal. Update the page indicator widget to use a single Semantics wrapper
around the entire Row in the relevant widget/build method, with the label based
on the current active page and the count, and exclude the individual dot
children from accessibility so only one announcement is exposed.

In `@lib/shared/widgets/responsive_center.dart`:
- Around line 1-3: The shared widget is importing theme tokens directly from
core/theme/layout.dart instead of the required theme.dart barrel. Update the
imports in ResponsiveCenter to use package:kynos/core/theme/theme.dart for
token/theme access, and remove the direct layout.dart import so the widget
follows the shared-widget guideline consistently.

In `@test/features/onboarding/onboarding_page_test.dart`:
- Around line 1-27: The onboarding widget test only covers initial rendering and
misses the async navigation paths in OnboardingPage, especially the
_skipOnboarding and _getStarted flows. Add widget tests that tap the Skip and
Get Started actions, override healthPermissionsProvider (and any other required
providers) to control the permission branch, and assert the expected navigation
or provider-state changes after the taps. Use the existing OnboardingPage and
onboarding_provider symbols to locate the test targets.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 56ef8a42-0232-4d98-bef2-36269bf41484

📥 Commits

Reviewing files that changed from the base of the PR and between 0708a52 and 62f7563.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (77)
  • README.md
  • lib/app/not_found_page.dart
  • lib/app/router.dart
  • lib/app/shell_page.dart
  • lib/core/constants/app_constants.dart
  • lib/core/theme/layout.dart
  • lib/features/character/presentation/pages/character_page.dart
  • lib/features/character/presentation/widgets/gamekit_panel.dart
  • lib/features/character/presentation/widgets/quest_card.dart
  • lib/features/character/presentation/widgets/trail_run_game_panel.dart
  • lib/features/character/providers/adventure_provider.dart
  • lib/features/character/providers/adventure_provider.g.dart
  • lib/features/character/providers/character_provider.dart
  • lib/features/character/providers/quest_provider.dart
  • lib/features/character/providers/quest_provider.g.dart
  • lib/features/coach_chat/presentation/pages/coach_chat_page.dart
  • lib/features/coach_chat/presentation/widgets/coach_chat_app_bar.dart
  • lib/features/coach_chat/presentation/widgets/message_list.dart
  • lib/features/coach_chat/presentation/widgets/model_setup_screen.dart
  • lib/features/dashboard/presentation/pages/dashboard_page.dart
  • lib/features/dashboard/presentation/pages/run_history_page.dart
  • lib/features/dashboard/presentation/pages/run_route_missing_page.dart
  • lib/features/dashboard/presentation/pages/run_route_page.dart
  • lib/features/dashboard/presentation/widgets/acwr_guardrail_card.dart
  • lib/features/dashboard/presentation/widgets/character_glance_card.dart
  • lib/features/dashboard/presentation/widgets/connect_healthkit_card.dart
  • lib/features/dashboard/presentation/widgets/daily_quest_teaser.dart
  • lib/features/dashboard/presentation/widgets/gait_teaser_card.dart
  • lib/features/dashboard/presentation/widgets/last_run_preview.dart
  • lib/features/dashboard/presentation/widgets/metric_detail_sheet.dart
  • lib/features/dashboard/presentation/widgets/readiness_card.dart
  • lib/features/dashboard/presentation/widgets/today_insight_cards.dart
  • lib/features/dashboard/presentation/widgets/trend_carousel.dart
  • lib/features/dashboard/presentation/widgets/week_momentum_card.dart
  • lib/features/dashboard/providers/post_run_debrief_provider.dart
  • lib/features/dashboard/providers/post_run_debrief_provider.g.dart
  • lib/features/onboarding/presentation/onboarding_page.dart
  • lib/features/onboarding/providers/onboarding_provider.dart
  • lib/features/onboarding/providers/onboarding_provider.g.dart
  • lib/features/settings/presentation/pages/health_import_page.dart
  • lib/features/settings/presentation/pages/manual_run_page.dart
  • lib/features/settings/presentation/pages/settings_page.dart
  • lib/features/settings/presentation/widgets/health_import_progress_card.dart
  • lib/features/settings/presentation/widgets/settings_appearance_section.dart
  • lib/features/settings/providers/health_import_provider.dart
  • lib/features/settings/providers/health_import_provider.g.dart
  • lib/features/settings/providers/manual_run_provider.dart
  • lib/features/settings/providers/manual_run_provider.g.dart
  • lib/features/settings/providers/settings_provider.dart
  • lib/features/settings/providers/settings_provider.g.dart
  • lib/features/training/presentation/pages/training_page.dart
  • lib/features/training/presentation/widgets/past_runs_list.dart
  • lib/features/training/presentation/widgets/trend_cards.dart
  • lib/infrastructure/health/health_infrastructure_providers.dart
  • lib/main.dart
  • lib/shared/providers/character_providers.dart
  • lib/shared/providers/character_providers.g.dart
  • lib/shared/providers/health_import_providers.dart
  • lib/shared/providers/health_providers.dart
  • lib/shared/providers/health_providers.g.dart
  • lib/shared/utils/health_platform_labels.dart
  • lib/shared/utils/url_opener.dart
  • lib/shared/widgets/charts/chart_placeholder.dart
  • lib/shared/widgets/charts/hrv_chart.dart
  • lib/shared/widgets/charts/load_chart.dart
  • lib/shared/widgets/gait_model_card.dart
  • lib/shared/widgets/kynos_inline_error_card.dart
  • lib/shared/widgets/kynos_page_dots.dart
  • lib/shared/widgets/metric_tile.dart
  • lib/shared/widgets/responsive_center.dart
  • lib/shared/widgets/run_card.dart
  • lib/shared/widgets/widgets.dart
  • pubspec.yaml
  • test/features/character/quest_provider_test.dart
  • test/features/onboarding/onboarding_page_test.dart
  • test/features/settings/health_import_progress_card_test.dart
  • test/features/settings/settings_page_test.dart
💤 Files with no reviewable changes (5)
  • test/features/settings/health_import_progress_card_test.dart
  • lib/features/dashboard/presentation/widgets/acwr_guardrail_card.dart
  • lib/features/settings/presentation/widgets/health_import_progress_card.dart
  • lib/features/dashboard/presentation/widgets/today_insight_cards.dart
  • lib/infrastructure/health/health_infrastructure_providers.dart

Comment thread lib/app/shell_page.dart Outdated
Comment thread lib/features/character/presentation/widgets/trail_run_game_panel.dart Outdated
Comment thread lib/features/dashboard/presentation/widgets/gait_teaser_card.dart
Comment thread lib/features/onboarding/presentation/onboarding_page.dart
Comment thread lib/features/settings/providers/health_import_provider.dart Outdated
Comment on lines +9 to +36
class ManualRunState {
const ManualRunState({
required this.start,
this.isSaving = false,
this.error,
this.saveSucceeded = false,
});

final DateTime start;
final bool isSaving;
final String? error;
final bool saveSucceeded;

ManualRunState copyWith({
DateTime? start,
bool? isSaving,
String? error,
bool? saveSucceeded,
bool clearError = false,
}) {
return ManualRunState(
start: start ?? this.start,
isSaving: isSaving ?? this.isSaving,
error: clearError ? null : (error ?? this.error),
saveSucceeded: saveSucceeded ?? this.saveSucceeded,
);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

ManualRunState should use @freezed per project guidelines.

This is a hand-rolled class with manual copyWith. Per coding guidelines, data models must use @freezed (plus @JsonSerializable where relevant) for immutability, copyWith, equality, and hashCode.

As per coding guidelines, lib/**: "All data models must use @freezed for immutability, copyWith, equality, and hashCode."

🤖 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/features/settings/providers/manual_run_provider.dart` around lines 9 -
36, ManualRunState is a hand-rolled mutable-style data model and should be
converted to a Freezed model to match project guidelines. Update ManualRunState
in manual_run_provider.dart to use `@freezed` (and `@JsonSerializable` only if
needed), remove the manual copyWith implementation, and define the state as an
immutable union/data class so Freezed generates copyWith, equality, and
hashCode. Keep the existing fields and defaults intact while moving the state
definition to the generated Freezed pattern used elsewhere in the codebase.

Source: Coding guidelines

Comment thread lib/features/settings/providers/manual_run_provider.dart
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:kynos/domain/entities/cloud_data_level.dart';
import 'package:kynos/features/onboarding/providers/onboarding_provider.dart';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Cross-feature provider import: move sharedPreferencesProvider to shared/providers/.

settings_provider.dart now imports sharedPreferencesProvider from the onboarding feature. This is a cross-feature provider dependency and forces other consumers (e.g. the settings widget test) to import onboarding internals for an unrelated, general-purpose dependency.

As per coding guidelines, "Do not import one feature's providers from another feature," and "All providers must live inside the feature they serve, or in shared/providers/."

♻️ Suggested direction
-import 'package:kynos/features/onboarding/providers/onboarding_provider.dart';
+import 'package:kynos/shared/providers/shared_preferences_provider.dart';

Move the sharedPreferencesProvider definition itself out of onboarding_provider.dart into a new lib/shared/providers/shared_preferences_provider.dart, then update all importers (onboarding, settings, tests, and the app-level override).

Also applies to: 42-44, 59-80, 89-92, 100-103

🤖 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/features/settings/providers/settings_provider.dart` at line 3,
`settings_provider.dart` is depending on `sharedPreferencesProvider` from the
onboarding feature, which violates the shared-provider boundary and creates
cross-feature imports. Move the `sharedPreferencesProvider` definition out of
`onboarding_provider.dart` into a dedicated shared provider under
`shared/providers/`, then update `settings_provider.dart`, onboarding code,
tests, and the app-level override to import the new shared location. Use the
existing `sharedPreferencesProvider` symbol as the migration target and remove
the onboarding import from settings once the provider is relocated.

Source: Coding guidelines

Comment thread lib/shared/utils/url_opener.dart
Wire shell branch navigation for dashboard shortcuts, fix onboarding
health-import handoff race, and use errorToken for repeated import/manual-run
failures. Move sharedPreferencesProvider to shared/providers, add popOrGo
helper, improve inline error cards and accessibility semantics.

Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
@YKDBontekoe
YKDBontekoe merged commit 852b07f into main Jul 6, 2026
12 of 13 checks passed
@YKDBontekoe
YKDBontekoe deleted the cursor/ui-ux-bug-fixes-f84f branch July 6, 2026 08:09

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/shared/widgets/kynos_page_dots.dart (1)

2-2: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Import tokens via theme.dart, not directly from spacing.dart.

As per coding guidelines, "Shared widgets must import tokens and theme via package:kynos/core/theme/theme.dart, use context.kynosTheme, use Gap for spacing...". This file imports package:kynos/core/theme/spacing.dart directly under a tokens alias, bypassing the theme.dart boundary.

🤖 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/shared/widgets/kynos_page_dots.dart` at line 2, The shared widget is
importing spacing tokens directly from spacing.dart instead of going through the
theme boundary. Update kynos_page_dots.dart to import
package:kynos/core/theme/theme.dart and use the theme/tokens exposed there
rather than the direct tokens alias from spacing.dart, keeping the widget
aligned with the shared widget guidelines and using the existing theme access
patterns in this file.

Source: Coding guidelines

🧹 Nitpick comments (1)
lib/shared/widgets/kynos_page_dots.dart (1)

27-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Magic-number offsets on token values.

tokens.Spacing.xs + 2 and tokens.Spacing.xs - 1 obscure the intended dot size/margin. Prefer dedicated named constants (or exact token values) instead of ad-hoc arithmetic on tokens.

🤖 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/shared/widgets/kynos_page_dots.dart` around lines 27 - 37, The dot sizing
and spacing in kynos_page_dots.dart use ad-hoc arithmetic on tokens.Spacing
values, which obscures intent. Update the widget-building logic in the dot
Container to replace tokens.Spacing.xs + 2 and tokens.Spacing.xs - 1 with
explicit named constants or exact token values, and keep the sizing/margin
definitions centralized and readable within the dot rendering code.
🤖 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/features/coach_chat/presentation/widgets/model_setup_screen.dart`:
- Around line 52-64: The AppBar close control in model_setup_screen.dart has
redundant accessibility semantics because IconButton.tooltip already provides
the accessible label. Update the AppBar leading widget so the close action is
exposed by one source only, either remove the outer Semantics wrapper around the
IconButton or remove the tooltip, and keep the close action using the existing
popOrGo(context, Routes.dashboard) handler.

---

Outside diff comments:
In `@lib/shared/widgets/kynos_page_dots.dart`:
- Line 2: The shared widget is importing spacing tokens directly from
spacing.dart instead of going through the theme boundary. Update
kynos_page_dots.dart to import package:kynos/core/theme/theme.dart and use the
theme/tokens exposed there rather than the direct tokens alias from
spacing.dart, keeping the widget aligned with the shared widget guidelines and
using the existing theme access patterns in this file.

---

Nitpick comments:
In `@lib/shared/widgets/kynos_page_dots.dart`:
- Around line 27-37: The dot sizing and spacing in kynos_page_dots.dart use
ad-hoc arithmetic on tokens.Spacing values, which obscures intent. Update the
widget-building logic in the dot Container to replace tokens.Spacing.xs + 2 and
tokens.Spacing.xs - 1 with explicit named constants or exact token values, and
keep the sizing/margin definitions centralized and readable within the dot
rendering code.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ae7c03e7-620f-4592-93b5-d7de2e54590a

📥 Commits

Reviewing files that changed from the base of the PR and between 62f7563 and 9525b0e.

📒 Files selected for processing (37)
  • lib/app/router.dart
  • lib/app/shell_navigation_scope.dart
  • lib/app/shell_page.dart
  • lib/features/character/presentation/pages/character_page.dart
  • lib/features/character/presentation/widgets/gamekit_panel.dart
  • lib/features/character/presentation/widgets/trail_run_game_panel.dart
  • lib/features/character/providers/quest_provider.dart
  • lib/features/character/providers/quest_provider.g.dart
  • lib/features/coach_chat/presentation/widgets/coach_chat_app_bar.dart
  • lib/features/coach_chat/presentation/widgets/model_setup_screen.dart
  • lib/features/dashboard/presentation/pages/run_history_page.dart
  • lib/features/dashboard/presentation/pages/run_route_missing_page.dart
  • lib/features/dashboard/presentation/widgets/character_glance_card.dart
  • lib/features/dashboard/presentation/widgets/gait_teaser_card.dart
  • lib/features/dashboard/presentation/widgets/readiness_card.dart
  • lib/features/onboarding/presentation/onboarding_page.dart
  • lib/features/onboarding/providers/onboarding_provider.dart
  • lib/features/onboarding/providers/onboarding_provider.g.dart
  • lib/features/settings/presentation/pages/health_import_page.dart
  • lib/features/settings/presentation/pages/manual_run_page.dart
  • lib/features/settings/providers/health_import_provider.dart
  • lib/features/settings/providers/health_import_provider.g.dart
  • lib/features/settings/providers/manual_run_provider.dart
  • lib/features/settings/providers/manual_run_provider.g.dart
  • lib/features/settings/providers/settings_provider.dart
  • lib/infrastructure/health/platform_imported_health_store_web.dart
  • lib/main.dart
  • lib/shared/providers/shared_preferences_provider.dart
  • lib/shared/providers/shared_preferences_provider.g.dart
  • lib/shared/utils/navigation_utils.dart
  • lib/shared/utils/url_opener.dart
  • lib/shared/widgets/kynos_loading_line.dart
  • lib/shared/widgets/kynos_page_dots.dart
  • lib/shared/widgets/responsive_center.dart
  • test/features/onboarding/onboarding_page_test.dart
  • test/features/settings/settings_page_test.dart
  • test/widget_test.dart
💤 Files with no reviewable changes (1)
  • lib/features/onboarding/providers/onboarding_provider.g.dart
✅ Files skipped from review due to trivial changes (6)
  • lib/shared/utils/navigation_utils.dart
  • lib/features/character/providers/quest_provider.g.dart
  • lib/shared/providers/shared_preferences_provider.g.dart
  • lib/infrastructure/health/platform_imported_health_store_web.dart
  • lib/features/settings/providers/health_import_provider.g.dart
  • lib/features/settings/providers/manual_run_provider.g.dart
🚧 Files skipped from review as they are similar to previous changes (20)
  • lib/features/character/presentation/widgets/gamekit_panel.dart
  • test/features/settings/settings_page_test.dart
  • lib/shared/utils/url_opener.dart
  • lib/features/dashboard/presentation/pages/run_route_missing_page.dart
  • lib/shared/widgets/responsive_center.dart
  • lib/features/character/presentation/widgets/trail_run_game_panel.dart
  • lib/features/dashboard/presentation/widgets/gait_teaser_card.dart
  • lib/main.dart
  • lib/features/dashboard/presentation/widgets/character_glance_card.dart
  • lib/features/settings/providers/manual_run_provider.dart
  • lib/features/dashboard/presentation/pages/run_history_page.dart
  • lib/features/settings/presentation/pages/manual_run_page.dart
  • lib/features/character/providers/quest_provider.dart
  • lib/features/character/presentation/pages/character_page.dart
  • lib/features/settings/providers/settings_provider.dart
  • lib/features/settings/presentation/pages/health_import_page.dart
  • lib/features/coach_chat/presentation/widgets/coach_chat_app_bar.dart
  • test/features/onboarding/onboarding_page_test.dart
  • lib/features/onboarding/presentation/onboarding_page.dart
  • lib/features/settings/providers/health_import_provider.dart

Comment on lines +52 to +64
appBar: showClose
? AppBar(
leading: Semantics(
label: 'Close',
button: true,
child: IconButton(
icon: const Icon(Icons.close_rounded),
tooltip: 'Close',
onPressed: () => popOrGo(context, Routes.dashboard),
),
),
)
: null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Flutter IconButton tooltip semantics duplicate Semantics label Tooltip accessibility

💡 Result:

In Flutter, the IconButton widget's tooltip property serves as the primary mechanism for both providing a visual description on long-press and defining the accessible label for screen readers [1][2][3]. When you provide a string to the tooltip property of an IconButton, Flutter automatically includes that text in the semantics tree for accessibility [2][3][4]. Because the IconButton widget internally handles this mapping, providing an additional Semantics wrapper with a redundant label can lead to accessibility issues, such as duplicate announcements or confusing reading orders [5][6]. Key points regarding accessibility and duplication: 1. Unified Labeling: The tooltip property is intended to act as the accessibility label for the button [7]. Using it effectively replaces the need for a separate semantic label in most cases [8][7]. 2. Avoiding Duplication: If you use both a Tooltip and an explicit Semantics label, screen readers may announce the information twice or in an unexpected order [6]. It is best practice to rely on the tooltip property for icon-only buttons [7]. 3. Internal Implementation: The IconButton widget wraps its internal child in a Tooltip widget (when a tooltip is provided) and then wraps the entire result in a Semantics widget [4]. This structure ensures that the tooltip message is the primary source for screen reader announcements [2]. 4. Troubleshooting Screen Readers: If you encounter issues where a tooltip is not being announced, ensure that the button is not being obscured by other semantics nodes and verify that you are not accidentally overriding the label with an external Semantics wrapper [6]. In summary, for standard icon buttons, you should define the accessibility label directly via the tooltip property and avoid wrapping the widget in additional Semantics nodes unless you need to override the default behavior entirely [7][4][6].

Citations:


Remove the redundant semantics wrapper or tooltip

IconButton.tooltip already contributes the accessible label, so wrapping the button in Semantics(label: 'Close', button: true) can make screen readers announce the control twice. Keep one source of semantics only.

🤖 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/features/coach_chat/presentation/widgets/model_setup_screen.dart` around
lines 52 - 64, The AppBar close control in model_setup_screen.dart has redundant
accessibility semantics because IconButton.tooltip already provides the
accessible label. Update the AppBar leading widget so the close action is
exposed by one source only, either remove the outer Semantics wrapper around the
IconButton or remove the tooltip, and keep the close action using the existing
popOrGo(context, Routes.dashboard) handler.

github-actions Bot pushed a commit that referenced this pull request Jul 6, 2026
## [1.12.2](v1.12.1...v1.12.2) (2026-07-06)

### Bug Fixes

* implement ui/ux bug backlog across navigation, errors, and theme ([#63](#63)) ([852b07f](852b07f))
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 1.12.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants