Skip to content

feat: add workout summary screen and enhance app theme colors - #50

Merged
Devasy merged 7 commits into
Revamp-of-UIfrom
claude/fitness-app-pain-points-sclt63
Jun 16, 2026
Merged

feat: add workout summary screen and enhance app theme colors#50
Devasy merged 7 commits into
Revamp-of-UIfrom
claude/fitness-app-pain-points-sclt63

Conversation

@Devasy

@Devasy Devasy commented Jun 10, 2026

Copy link
Copy Markdown
Owner
  • Implemented WorkoutSummaryScreen to display post-workout details including duration, volume, sets, and exercises.
  • Added visual elements such as trophy header and muscle groups trained section.
  • Refactored AppTheme to centralize color management through AppColors, improving maintainability and consistency across the app.
  • Updated theme properties for better visual coherence and modern styling.

Summary by CodeRabbit

Release Notes

New Features

  • AI Coach for conversational training guidance and recommendations
  • AI-powered program generator to create personalized workouts
  • Routine Optimizer to refine existing training plans with AI insights
  • Personal Records tracking and dedicated records analytics tab
  • Readiness metrics integration with Health Connect for training readiness insights
  • Weekly AI-generated training insights and recommendations

Improvements

  • Major UI refresh across dashboard, analytics, and routines screens
  • New activity heatmap and muscle focus visualizations
  • Enhanced onboarding with welcome flow
  • Redesigned analytics dashboard with readiness and weekly insights cards

Bug Fixes & Updates

  • Version updated to 2.0.0 with build optimizations

Adds the read side of Health Connect and a home-screen readiness card:

- IHealthConnectService: read permissions + sleep/RHR/HRV/HR queries
- ReadinessCalculator: pure scoring vs personal 14-day baseline
  (sleep 0.5 / resting HR 0.3 / HRV 0.2, renormalized over available
  components; only adverse deviation penalized; 5-sample minimum)
- ReadinessManager: daily baseline cache + 30-min snapshot TTL in
  settings storage; every failure degrades to noData, never throws
- ReadinessCard on dashboard (self-hides without data) with details
  bottom sheet; opt-in toggle in profile Health Connect section
- New READ_SLEEP / READ_HEART_RATE / READ_RESTING_HEART_RATE /
  READ_HEART_RATE_VARIABILITY manifest permissions

https://claude.ai/code/session_01FTgsHTfbvXsxwTUe74UrYe
@Devasy
Devasy changed the base branch from main to Revamp-of-UI June 10, 2026 04:19
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 837ffd7d-e897-442f-87ce-ae82bca62107

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR introduces a comprehensive UI refresh and feature expansion for the RepForge workout tracking app. It adds conversational AI coaching capabilities with Gemini integration, redesigns the home dashboard with insights and analytics, implements a routine optimizer screen, refreshes the history/analytics/exercise library interfaces, adds first-launch onboarding, introduces personal records tracking, and updates Android build configuration to SDK 36 with manifest health permissions.

Changes

UI Refresh and Feature Implementation

Layer / File(s) Summary
Release and Build Configuration
workout-logger/RELEASE_NOTES.md, workout-logger/android/app/build.gradle.kts, workout-logger/android/app/src/main/AndroidManifest.xml, workout-logger/android/app/src/main/kotlin/com/workoutlogger/workout_logger/MainActivity.kt
Version bumped to 2.0.0 (Build 21), Android SDK compilation hardcoded to 36 with extension 19, manifest updated with internet and health read permissions (sleep, HR, HRV), app label templated via placeholder, removed MainActivity Kotlin stub.
Data Models for AI Coaching and Readiness
workout-logger/lib/models/models.dart
Added ChatMessage, Conversation (with kind discriminator), QuestionSpec, AnswerSpec, PendingQuestions for AI coaching persistence and question flows; added ReadinessBand, HealthSample, SleepPeriod, ReadinessBaseline, ReadinessSnapshot for training readiness tracking; added PersonalRecord model; enhanced DropsetEntry with UUID-backed ID field; added Routine.copyWith.
App Initialization with AI Services and Managers
workout-logger/lib/main.dart
Wired GeminiAiService, ConversationManager, PRManager, ReadinessManager into provider tree; initialized Gemini with API key/model, loadUsage error handling, PR backfill from sessions, readiness refresh as background task, version/name prompt computation.
First-Launch Onboarding and Version Update
workout-logger/lib/screens/onboarding_screen.dart
New WelcomePage widget for name entry with submission flow (persist to SettingsProvider, mark version as seen, trigger onComplete callback); showVersionUpdateSheet function for modal bottom sheet with "What's new" feature list (personal records, records tab).
Home Dashboard Redesign with Streak, Stats, Insights
workout-logger/lib/screens/home_screen.dart
Replaced bottom navigation with RFNavBar and IndexedStack; dashboard now includes greeting header with nav affordances, streak hero (Mon–Sun dots, conditional resume/routine action), stats grid with sparklines (workouts/volume/sets/duration), activity heatmap, weekly muscle volume heatmap, recent workouts list, and weekly AI-generated insights card with Gemini context and persistence.
AI Coach Screen and Conversational UI
workout-logger/lib/screens/ai_coach_screen.dart
New AiCoachScreen hosting AiCoachViewModel with conversational chat UI, auto-scroll behavior, role-based message bubbles (user/assistant with markdown), streaming output, suggestion chips, conversation history bottom-sheet management, new chat action, and "API Key Required" empty state.
Routine Optimizer Screen and Conversational Flow
workout-logger/lib/screens/routine_optimizer_screen.dart
New RoutineOptimizerScreen for analyzing routines via multi-turn Gemini conversation; includes message bubbles, streaming text rendering, pending question cards (RFQuestionCard) with answer submission, conversation history/selection/deletion in bottom sheet, active-session highlighting.
AI Program Generator Screen
workout-logger/lib/screens/ai_program_generator_screen.dart
New AiProgramGeneratorScreen for generating TrainingProgram via Gemini; manages prompt input, generation state/progress, error display, live preview with weeks/phases/deloads summary, save action (persists via ProgramManager), regenerate action (haptic feedback), and quick-prompt chips.
Analytics Screen Redesign and Records Tab
workout-logger/lib/screens/analytics_screen.dart
Converted from TabController/TabBarView to custom pill-tab navigation with _tab index; delegated Overview/Exercises/Targets to imported widgets; implemented in-file Records tab with filter enums (all/thisMonth/byExercise), sort enums (recent/heaviest), PR grouping by exercise, newest-PR hero card, and reusable _FilterChip/_PRCard/_PRStat UI components.
History Screen Redesign with Calendar and Search
workout-logger/lib/screens/history_screen.dart
Converted HistoryScreen to StatefulWidget with CustomScrollView; added animated toggle-able search bar, calendar month grid (intensity shading, PR indicators, day selection navigation), lifetime summary card (count/volume), per-day intensity computation from session volume; refactored session list to _HistoryCard components with SessionDetailsSheet, delete confirmation flow, and overflow menu (edit/sync/delete).
Exercise Library Search, Filter, and Details
workout-logger/lib/screens/exercise_library_screen.dart
Refactored ExerciseLibraryScreen with search query and muscle filter state, custom _Header/_SearchBar/_MuscleFilterChips/_MuscleGroup widgets; added _openDetails for ExerciseDetailsSheet bottom-sheet presentation; rewrote ExerciseSelectorScreen with selection tracking, GlowButton, and tightened onExercisesSelected callback type.
Edit Workout Session with Editable Components
workout-logger/lib/screens/edit_workout_session_screen.dart
Refactored to use EditableExerciseCard, EditableExerciseLog/EditableSet models; state now tracks _notesCtrl/_durationCtrl/_exercises; initState converts session to editable structure preserving metadata; new _save flow with validation, duration parsing, exercise list filtering, session.copyWith update, WorkoutProvider persistence, success/error snackbars; new _SectionCard/_TapField/_StyledField helper widgets.
Add Custom Exercise Screen Refactor
workout-logger/lib/screens/add_custom_exercise_screen.dart
Refactored with _category and _muscleId state; added async _save() method (validate form, call WorkoutProvider.addCustomExercise, show snackbars); replaced segmented/inline grid with _CategoryTile and _MuscleGrid components; replaced ElevatedButton with GlowButton; updated to AppColors theming.
Program Screens Refactor and Updates
workout-logger/lib/screens/programs/program_detail_screen.dart, workout-logger/lib/screens/programs/programs_screen.dart, workout-logger/lib/screens/programs/program_designer_screen.dart
ProgramDetailScreen now uses ProgramWeekTile widget and AppColors theming; ProgramsScreen updated with SafeArea, RFEmptyState, 3-button floating action menu (JSON import/AI generate/new program), AiProgramGeneratorScreen navigation with snackbar feedback; ProgramDesignerScreen changed exercise list from SizedBox to ConstrainedBox with shrinkWrap for improved dialog layout.
Routines Screen Redesign with Optimizer Gating
workout-logger/lib/screens/routines_screen.dart
Redesigned with Stack/CustomScrollView slivers, quick-start card, all-routines list, programs section, empty-state, new-routine button; removed CreateRoutineScreen/RoutineDetailScreen from this file (now imported); _RoutineCard simplified to show name/exercise-count with play (start workout), edit (CreateRoutineScreen), and optimize (gates to RoutineOptimizerScreen if ≥3 sessions).
Profile Screen Readiness and Data Import/Export
workout-logger/lib/screens/profile_screen.dart
Added _requestReadinessPermission method and _isRequestingReadinessPermission state; refactored Health Connect/readiness reconciliation logic; updated import flow to use FilePicker + JSON validation; centralized snackbar styling via _showSnack with AppColors; refactored build to compose external section widgets (PreferencesSection, HealthConnectSection, DataManagementSection, AiSettingsSection, CloudSyncSection, AboutSection) plus _buildHero sliver.
Dashboard Supporting Widgets
workout-logger/lib/screens/widgets/dashboard_widgets.dart
New WeekActivityStrip (7-column week dot strip with session indicators), StatGrid (2×2 card grid with SettingsProvider formatting), QuickActionTile (tappable icon + label), RecentWorkoutsSection (empty-state or session list with "See All" navigation).
Activity and Body Heatmap Widgets
workout-logger/lib/screens/widgets/activity_heatmap.dart, workout-logger/lib/screens/widgets/body_heatmap.dart
ActivityHeatmap renders 14×7 grid from 98-length data list with transposed column-by-column filling, per-cell opacity-based intensity coloring, optional glow shadow for high values, "Less/More" legend; BodyHeatmapWidget uses CustomPaint to draw body silhouette and muscle-volume heat overlays with normalized opacity clamping.
Calendar Month Grid Widget
workout-logger/lib/screens/widgets/calendar_grid.dart
New CalendarDayData model (intensity 0-3, hasPr boolean); CalendarMonthGrid computes month offsets, renders weekday headers and 7-column GridView, _DayCell applies intensity-based background/text opacity, borders for selected/today/future states, optional PR dot indicator, and tappable day selection.
Analytics Overview Tab and Supporting Cards
workout-logger/lib/screens/widgets/analytics_overview.dart
New AnalyticsOverviewTab with weekly Volume Trend card (4W/12W/All toggle, delta vs prior period, no-data empty chart), Muscle Focus card (recovery %, growth slopes, top muscles list with heatmap), Workout Frequency grid (4 week buckets with session counts); includes _RangeToggle, _MuscleFocusRow, _ChartCard, _EmptyChart, and _fmtK formatting.
Editable Exercise Card and Set Components
workout-logger/lib/screens/widgets/editable_exercise_card.dart
New EditableExerciseLog/EditableSet models and OnSetChanged callback; EditableExerciseCard renders exercise header, set list (EditableSetRow), add-set control, delete dialog; EditableSetRow manages weight/reps controllers + dropset toggle + drops list; EditableDropRow for per-drop editing; _NumField standardizes numeric input.
Exercise Input Section for Workout Flow
workout-logger/lib/screens/widgets/exercise_input_section.dart
New ExerciseInputSection widget for complete set-entry UI including conditional program metadata banner (target sets/reps/rest/tempo), AI recommendation card with confidence-to-color mapping and apply action (haptic feedback), standard/dropset input rows with controllers/steppers, "LOG SET" button, previous sets chips, last-session section.
Exercise Details Sheet
workout-logger/lib/screens/widgets/exercise_details_sheet.dart
New ExerciseDetailsSheet widget showing exercise header (icon, category/custom chips), muscle activation rows with progress bars, conditional "Last Session" section (weight/reps chips), conditional "Growth trend" section (slope + R²); custom exercise delete with confirmation dialog and success/error snackbars.
Documentation and Configuration Updates
.gitignore, CLAUDE.md, docs/superpowers/specs/2026-06-08-conversational-routine-optimizer-design.md
Updated .gitignore to exclude graphify-out/, repforge_backup_*.json, .claude/; added graphify guidance section to CLAUDE.md (knowledge graph navigation, query/path/explain commands, update workflow); created comprehensive routine optimizer design specification (UX flow, tool schema, pause/resume, RFQuestionCard, view model, Conversation.kind, navigation gating, test plan).

Possibly Related PRs

  • Devasy/Workout-logger#49: Implements the Gemini AI service and conversation persistence that this PR directly wires into app initialization and uses across AI coach/optimizer screens.
  • Devasy/Workout-logger#39: Establishes the WorkoutProvider.startWorkoutSafely and showWorkoutConflictDialog helpers that this PR's home/routines screens reuse throughout.
  • Devasy/Workout-logger#22: Introduces comprehensive CLAUDE.md contributor documentation that this PR extends with the new graphify guidance section.

@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 46.15385% with 238 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.74%. Comparing base (339b185) to head (38fc684).

Files with missing lines Patch % Lines
...out-logger/lib/screens/widgets/readiness_card.dart 0.80% 123 Missing ⚠️
...ut-logger/lib/services/health_connect_service.dart 0.00% 49 Missing ⚠️
workout-logger/lib/screens/profile_screen.dart 0.00% 36 Missing ⚠️
...t-logger/lib/screens/widgets/profile_sections.dart 0.00% 16 Missing ⚠️
...ogger/lib/services/managers/readiness_manager.dart 91.58% 9 Missing ⚠️
workout-logger/lib/services/settings_provider.dart 42.85% 4 Missing ⚠️
workout-logger/lib/main.dart 80.00% 1 Missing ⚠️
Additional details and impacted files
@@               Coverage Diff                @@
##           Revamp-of-UI      #50      +/-   ##
================================================
+ Coverage         41.56%   41.74%   +0.18%     
================================================
  Files                68       71       +3     
  Lines             11457    11896     +439     
================================================
+ Hits               4762     4966     +204     
- Misses             6695     6930     +235     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 28

Caution

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

⚠️ Outside diff range comments (2)
workout-logger/lib/main.dart (1)

93-93: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Hoist ApiService out of build().

Provider<ApiService>.value(value: ApiService()) creates a fresh service every time WorkoutLoggerApp rebuilds, which breaks the composition-root singleton assumption used by the rest of this file.

Suggested fix
   static final SettingsProvider _settingsProvider = SettingsProvider(_storageService);
+  static final ApiService _apiService = ApiService();
   // HealthSyncManager uses the in-memory settings flag — no storage I/O on sync.
   static final HealthSyncManager _healthSyncManager =
       HealthSyncManager(_healthConnectService, _settingsProvider);
@@
-        Provider<ApiService>.value(value: ApiService()),
+        Provider<ApiService>.value(value: _apiService),

Based on learnings, "Wire all services through dependency injection in main.dart via AppInitializer using constructor injection".

🤖 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 `@workout-logger/lib/main.dart` at line 93, The Provider is instantiating
ApiService inside WorkoutLoggerApp.build which recreates the service on every
rebuild; instead construct a single ApiService once (hoist it out of build) and
inject it via your AppInitializer/constructor injection so the app uses a
singleton instance. Locate the Provider<ApiService>.value(value: ApiService())
entry and replace it so it references the hoisted ApiService instance provided
by AppInitializer (or passed into WorkoutLoggerApp's constructor), ensuring
ApiService is created only once and wired through the DI root.

Source: Learnings

workout-logger/lib/screens/programs/program_designer_screen.dart (1)

660-676: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider performance impact of shrinkWrap: true.

Line 666 adds shrinkWrap: true to a ListView.builder inside a dialog. While necessary for the ConstrainedBox to work correctly, shrinkWrap can have performance implications if the list is long. Given that the filtered list is limited to 20 items (line 641: .take(20)), this should be acceptable, but document this constraint if it's critical.

🤖 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 `@workout-logger/lib/screens/programs/program_designer_screen.dart` around
lines 660 - 676, Add a brief inline comment next to the
ListView.builder/ConstrainedBox explaining why shrinkWrap: true is used and that
the list is intentionally bounded (filtered is limited via .take(20)), so the
performance cost is acceptable; reference the ListView.builder, shrinkWrap,
ConstrainedBox and the filtered.take(20) limitation to make the rationale clear
for future maintainers.
🤖 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 `@CLAUDE.md`:
- Around line 298-301: Update the CLAUDE.md prerequisite so the instruction
about reading graphify-out/GRAPH_REPORT.md is conditional: change the absolute
"ALWAYS read graphify-out/GRAPH_REPORT.md" statement to check for the file's
existence and provide a fallback (e.g., "If graphify-out/GRAPH_REPORT.md exists,
read it first; otherwise use the project's README or run `graphify update .`
and/or `graphify init` to generate the graph") and adjust the subsequent
guidance (the wiki/index.md note and graphify commands) to reference the same
conditional flow; modify the sentence containing "ALWAYS read
graphify-out/GRAPH_REPORT.md" and related lines in CLAUDE.md to implement this
fallback wording.

In `@workout-logger/lib/main.dart`:
- Around line 180-184: The check that computes versionChanged incorrectly treats
a null settings.lastSeenVersion as "seen"; update the logic that sets
versionChanged (near the use of settings.getCurrentVersion(), settings.userName,
and settings.lastSeenVersion) so that if lastSeenVersion is null it counts as a
change/unseen—i.e., versionChanged should be true when the user has a name
(needsName is false) and (settings.lastSeenVersion is null OR
settings.lastSeenVersion != version); update the versionChanged expression
accordingly.

In `@workout-logger/lib/models/models.dart`:
- Around line 327-330: Routine.copyWith currently preserves the original List
reference for exerciseIds causing shared mutable state; update Routine.copyWith
to defensively clone list-backed fields (e.g., use exerciseIds == null ?
List.from(this.exerciseIds) : List.from(exerciseIds) or .toList()) when
constructing the new Routine so the new instance gets its own list. Apply the
same defensive copying pattern to the other model copyWith methods mentioned
(the copyWith implementations around the other model blocks referenced in the
comment) so no list-backed field retains the original mutable reference.

In `@workout-logger/lib/screens/ai_coach_screen.dart`:
- Line 382: The call to AppColors.surface.withValues uses the withValues API;
update the call at AppColors.surface.withValues so it uses the correct named
parameter syntax and a valid alpha in the 0.0–1.0 range (e.g., alpha: 0.9 for
90% opacity); if your intention was a different opacity (e.g., 10% transparent),
adjust the numeric value accordingly and keep the named parameter alpha to match
the method signature.
- Around line 817-840: The _AiAvatar StatelessWidget lacks a const constructor;
add a const constructor like "const _AiAvatar({Key? key}) : super(key: key);" to
the _AiAvatar class and ensure its fields (if any) are final/immutable, then
update instantiations of _AiAvatar to use const where possible so the widget can
be created as a compile-time constant.

In `@workout-logger/lib/screens/ai_program_generator_screen.dart`:
- Line 50: Extract the error message into a local variable (e.g., final String
msg = 'Add your Gemini API key in Profile → AI Features first.') and then call
setState with a simple, single-statement assignment that uses that variable
(reference _error and setState) instead of embedding the full string expression
inline; update the code around the setState call that assigns to _error so it
becomes more readable and easier to refactor later.

In `@workout-logger/lib/screens/analytics_screen.dart`:
- Around line 86-134: Replace the hardcoded BorderRadius.circular(14) and
BorderRadius.circular(11) in _buildPillTabBar with the design-system radius
tokens from AppRadius (e.g., AppRadius.large for the container and
AppRadius.small for the pill) so the Container decoration and the
AnimatedContainer decoration use BorderRadius.circular(AppRadius.<token>)
instead of numeric literals; update both occurrences and ensure AppRadius is
imported/available.

In `@workout-logger/lib/screens/edit_workout_session_screen.dart`:
- Around line 390-395: The onSetChanged handler (the callback updating log.sets
in EditWorkoutSessionScreen) fails to clear stale drop entries when isDropset
flips false; update the handler inside setState so that when isDropset is false
you explicitly set log.sets[setIndex].drops = null (or empty list per
WorkoutSet.drops contract) in addition to updating weight/reps/isDropset,
ensuring _save() will not serialize old drops for non-dropset rows.

In `@workout-logger/lib/screens/exercise_library_screen.dart`:
- Around line 201-225: The TextField is uncontrolled, so clearing the suffix
icon only updates _query but not the visible text; make the field controlled by
creating a TextEditingController in the State (e.g., _searchController), pass
controller: _searchController into the TextField, wire the controller to the
existing onChanged callback (keep onChanged: onChanged), and in the suffix
GestureDetector onTap call _searchController.clear() and then onChanged('') to
keep UI and state in sync; also ensure you initialize the controller (optionally
with initial text) and dispose it in dispose().
- Around line 483-495: The tap handler for ExerciseCard is mutating _selectedIds
regardless of selectionMode; change the onTap callback in the widget builder to
first check selectionMode and only call setState and toggle _selectedIds when
selectionMode is true (leave taps untouched when selectionMode is false). Update
the code near the ExerciseCard creation where sel is computed from _selectedIds
and the onTap lambda is defined so that the toggle logic is gated by the
selectionMode boolean to avoid entering a hidden selection state.

In `@workout-logger/lib/screens/history_screen.dart`:
- Around line 307-320: The calendar nav buttons use a hardcoded
BorderRadius.circular(8); replace that with the design token (e.g.,
AppRadius.someRadius) in the Container decoration for the left and right
navigation buttons so they use the design system. Locate the
GestureDetector/Container widgets that update _calendarMonth and _selectedDay
(the left chevron button with Icon(Icons.chevron_left_rounded) and the
corresponding right chevron) and swap BorderRadius.circular(8) for the
appropriate AppRadius token used elsewhere in the app.
- Around line 236-260: The Container's decoration uses a hardcoded
borderRadius.circular(10); replace that literal with the design token from
AppRadius (e.g., use AppRadius.small) so the GestureDetector/Container
decoration consistently uses the design system token (update
BorderRadius.circular(10) to BorderRadius.circular(AppRadius.small) or
equivalent AppRadius token and add any necessary import).
- Around line 434-455: The Container decoration uses a hardcoded borderRadius:
BorderRadius.circular(12); replace this with the design token from your radius
system (e.g. use AppRadius.<appropriateToken>) so the code becomes borderRadius:
BorderRadius.circular(AppRadius.<token>) or
BorderRadius.all(Radius.circular(AppRadius.<token>)); update the instance in
history_screen.dart (the Container/TextField search bar decoration) to reference
the AppRadius token instead of 12.

In `@workout-logger/lib/screens/home_screen.dart`:
- Around line 464-507: Replace the hardcoded BorderRadius.circular(12) in the
Start/Resume button's Container decoration with the design system token (e.g.,
AppRadius.medium or the appropriate AppRadius constant) so the
GestureDetector/Container uses the AppRadius token for borderRadius; update the
BoxDecoration in the widget where borderRadius is set to reference AppRadius
instead of BorderRadius.circular(12) and ensure imports for the design tokens
are present if needed.
- Around line 308-328: Replace the hardcoded BorderRadius.circular(12) used in
the Container inside the GestureDetector (the profile button) with the
design-system radius token from AppRadius (e.g., AppRadius.* appropriate token)
so the widget uses the shared radius values; update the Container's decoration
to use BorderRadius.circular(...) -> BorderRadius.all(AppRadius.<token>) or
directly the token shape as your design system expects, ensuring the rest of the
properties (color, border) remain unchanged and the ProfileScreen navigation and
Icon usage are not modified.
- Around line 278-306: Replace the hardcoded borderRadius.circular(12) in the AI
coach button Container decoration with the design token from the app's radius
tokens (use AppRadius.small instead of 12) so the snippet uses
BorderRadius.circular(AppRadius.small) (update the Container/BoxDecoration in
the HomeScreen build where borderRadius.circular(12) is used for the AI coach
button that navigates to AiCoachScreen).

In `@workout-logger/lib/screens/onboarding_screen.dart`:
- Around line 61-156: The UI uses hardcoded spacing and padding values (28, 12,
16, 14) inside the Column and TextField decorations instead of the design
tokens; update those literals to use the appropriate tokens (e.g., replace
horizontal padding 28 with AppSpacing.[sm/md/lg] or a consistent token, replace
SizedBox height 12 with AppSpacing.[xs/sm], and replace contentPadding
horizontal/vertical 16/14 with AppSpacing tokens) to match the design system and
keep existing usage of AppRadius and AppSpacing elsewhere; ensure changes are
applied around the Container/Padding/ SizedBox/TextField (referencing
_controller, TextField, AppRadius, AppSpacing) so spacing is consistent across
the onboarding_screen.dart.

In `@workout-logger/lib/screens/profile_screen.dart`:
- Around line 205-207: The code treats ShareResultStatus.dismissed as a
successful export; change the condition so only ShareResultStatus.success
triggers _showSnack('Backup exported successfully!', AppColors.success) and
handle ShareResultStatus.dismissed separately (e.g., call _showSnack with a
canceled/neutral message and AppColors.warning or do nothing) so users aren't
misled that a backup left the app; update the check of result.status (and any
surrounding export/share function) to only consider ShareResultStatus.success as
success and add an else-if for ShareResultStatus.dismissed to show an
appropriate cancellation message.

In `@workout-logger/lib/screens/routine_optimizer_screen.dart`:
- Around line 473-497: The _OptimizerAvatar StatelessWidget lacks a const
constructor; add a const constructor (const _OptimizerAvatar({Key? key}) :
super(key: key);) to the _OptimizerAvatar class and update all places that
instantiate _OptimizerAvatar to use const where applicable so the widget can be
const-constructed and leverage compile-time optimizations.

In `@workout-logger/lib/screens/routines_screen.dart`:
- Around line 494-518: The current onTap handler in the GestureDetector blocks
navigation to RoutineOptimizerScreen when sessionCount < 3; make the threshold
configurable and/or change the behavior to warn instead of blocking: extract the
hardcoded "3" into a configurable constant or a user setting (e.g.,
optimizerMinSessions) and reference it where sessionCount is computed, and if
you prefer a soft warning, replace the early return with a SnackBar warning that
still calls Navigator.push to RoutineOptimizerScreen(routine: routine) (or add
an override confirmation dialog) so users can proceed after 1–2 sessions if they
choose.

In `@workout-logger/lib/screens/widgets/activity_heatmap.dart`:
- Around line 23-41: Replace the hardcoded spacing numbers in GridView.builder's
SliverGridDelegateWithFixedCrossAxisCount with the design system tokens (use
AppSpacing.small for crossAxisSpacing and mainAxisSpacing) so spacing comes from
the design system; update the SliverGridDelegateWithFixedCrossAxisCount
constructor in the activity_heatmap.dart GridView.builder (references:
GridView.builder, SliverGridDelegateWithFixedCrossAxisCount, crossAxisSpacing,
mainAxisSpacing) to use AppSpacing tokens instead of the literal 3.

In `@workout-logger/lib/screens/widgets/analytics_overview.dart`:
- Around line 361-403: Replace the hardcoded BorderRadius.circular(10) and
BorderRadius.circular(7) in the _RangeToggle widget with the design-system
radius tokens (use the appropriate AppRadius token(s) from your design system,
e.g. AppRadius.medium/AppRadius.small) so both the container decoration and the
inner AnimatedContainer use AppRadius instead of numeric literals; update the
BoxDecoration.borderRadius in _RangeToggle and the borderRadius on the inner
AnimatedContainer to reference the AppRadius token(s).

In `@workout-logger/lib/screens/widgets/calendar_grid.dart`:
- Around line 58-67: The SliverGridDelegateWithFixedCrossAxisCount in
GridView.builder currently uses hardcoded spacing (crossAxisSpacing: 4,
mainAxisSpacing: 4); replace those literals with the design system tokens (e.g.,
AppSpacing.small or the appropriate AppSpacing constant) so both
crossAxisSpacing and mainAxisSpacing reference AppSpacing instead of numeric
values; update the GridView.builder/SliverGridDelegateWithFixedCrossAxisCount
instantiation accordingly to use the token names.
- Around line 188-203: Replace the hardcoded borderRadius.circular(8) inside the
Container's BoxDecoration (in the day cell widget returned by GestureDetector)
with the design-system token (e.g., AppRadius.small) so it uses the AppRadius
value instead of a magic number; update the borderRadius property to
BorderRadius.circular(AppRadius.small) or the appropriate AppRadius token to
match the design system.

In `@workout-logger/lib/screens/widgets/editable_exercise_card.dart`:
- Around line 331-352: The weight input is hardcoded to 'kg' in the _NumField
(using _weightCtrl/_weightFocus and onWeightChanged), causing wrong
display/edits in lb mode; update the suffix to read the unit label from
SettingsProvider (or equivalent settings getter) and ensure displayed value and
parsing honor the current unit by converting stored kg to the user unit when
initializing _weightCtrl.text and converting back to kg before calling
widget.onWeightChanged; apply the same change for the other occurrence around
the 531-554 block so both editor rows use the settings-based unit and proper
conversions.
- Around line 18-45: EditableExerciseLog and EditableSet are mutable but must be
immutable and updated via copyWith; change all fields on EditableExerciseLog and
EditableSet to final, make constructors constant/positional as appropriate,
ensure lists (sets and drops) are stored as unmodifiable (e.g.,
List.unmodifiable) and then add copyWith methods on both classes
(EditableExerciseLog.copyWith and EditableSet.copyWith) that let callers replace
fields or produce modified copies of nested lists (e.g., return a new
EditableExerciseLog with a modified sets list where an EditableSet is replaced
via its copyWith, and EditableSet.copyWith should allow updating weight, reps,
isDropset, drops, timeTaken, and timestamp). Update any code that previously
mutated fields directly to use these copyWith helpers and to replace lists
atomically rather than mutating in place.

In `@workout-logger/lib/screens/widgets/exercise_details_sheet.dart`:
- Around line 233-244: The Text currently hardcodes a '+' before the slope which
causes "+-..." when growthModel.slope is negative; update the widget that builds
the label (the Text containing
'+${settings.toDisplay(growthModel.slope).toStringAsFixed(1)}
${settings.unitLabel} volume/session') to prepend '+' only when
growthModel.slope > 0 (e.g., compute a sign = growthModel.slope > 0 ? '+' : ''
and render "$sign${settings.toDisplay(growthModel.slope).toStringAsFixed(1)}
${settings.unitLabel} volume/session"), keeping the existing settings.toDisplay
and unitLabel usage so negative values retain their '-' sign.

In `@workout-logger/lib/screens/widgets/exercise_input_section.dart`:
- Around line 877-892: The deload banner is using hardcoded Colors.amber in two
places; replace those with the design token from AppColors (e.g.,
AppColors.amber or the project's deload/warning token) so the theme system is
used consistently. Update the Border.all color branch (where it currently uses
week.isDeload ? Colors.amber.withValues(alpha: 0.4) ...) and the Icon color
(Icon(..., color: Colors.amber)) to use the matching AppColors token and its
withValues(alpha: ...) variant; keep the week.isDeload condition and existing
sizing/padding intact.

---

Outside diff comments:
In `@workout-logger/lib/main.dart`:
- Line 93: The Provider is instantiating ApiService inside
WorkoutLoggerApp.build which recreates the service on every rebuild; instead
construct a single ApiService once (hoist it out of build) and inject it via
your AppInitializer/constructor injection so the app uses a singleton instance.
Locate the Provider<ApiService>.value(value: ApiService()) entry and replace it
so it references the hoisted ApiService instance provided by AppInitializer (or
passed into WorkoutLoggerApp's constructor), ensuring ApiService is created only
once and wired through the DI root.

In `@workout-logger/lib/screens/programs/program_designer_screen.dart`:
- Around line 660-676: Add a brief inline comment next to the
ListView.builder/ConstrainedBox explaining why shrinkWrap: true is used and that
the list is intentionally bounded (filtered is limited via .take(20)), so the
performance cost is acceptable; reference the ListView.builder, shrinkWrap,
ConstrainedBox and the filtered.take(20) limitation to make the rationale clear
for future maintainers.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7e85d8a3-0135-4882-996b-c369b7b0bb54

📥 Commits

Reviewing files that changed from the base of the PR and between 36c519c and 38fc684.

📒 Files selected for processing (98)
  • .gitignore
  • CLAUDE.md
  • docs/superpowers/specs/2026-06-08-conversational-routine-optimizer-design.md
  • workout-logger/RELEASE_NOTES.md
  • workout-logger/android/app/build.gradle.kts
  • workout-logger/android/app/src/main/AndroidManifest.xml
  • workout-logger/android/app/src/main/kotlin/com/workoutlogger/workout_logger/MainActivity.kt
  • workout-logger/lib/main.dart
  • workout-logger/lib/models/models.dart
  • workout-logger/lib/screens/add_custom_exercise_screen.dart
  • workout-logger/lib/screens/ai_coach_screen.dart
  • workout-logger/lib/screens/ai_program_generator_screen.dart
  • workout-logger/lib/screens/analytics_screen.dart
  • workout-logger/lib/screens/edit_workout_session_screen.dart
  • workout-logger/lib/screens/exercise_library_screen.dart
  • workout-logger/lib/screens/history_screen.dart
  • workout-logger/lib/screens/home_screen.dart
  • workout-logger/lib/screens/onboarding_screen.dart
  • workout-logger/lib/screens/profile_screen.dart
  • workout-logger/lib/screens/programs/program_designer_screen.dart
  • workout-logger/lib/screens/programs/program_detail_screen.dart
  • workout-logger/lib/screens/programs/programs_screen.dart
  • workout-logger/lib/screens/routine_optimizer_screen.dart
  • workout-logger/lib/screens/routines_screen.dart
  • workout-logger/lib/screens/widgets/activity_heatmap.dart
  • workout-logger/lib/screens/widgets/analytics_overview.dart
  • workout-logger/lib/screens/widgets/body_heatmap.dart
  • workout-logger/lib/screens/widgets/calendar_grid.dart
  • workout-logger/lib/screens/widgets/dashboard_widgets.dart
  • workout-logger/lib/screens/widgets/editable_exercise_card.dart
  • workout-logger/lib/screens/widgets/exercise_details_sheet.dart
  • workout-logger/lib/screens/widgets/exercise_input_section.dart
  • workout-logger/lib/screens/widgets/exercise_progress_view.dart
  • workout-logger/lib/screens/widgets/muscle_detail_sheet.dart
  • workout-logger/lib/screens/widgets/profile_sections.dart
  • workout-logger/lib/screens/widgets/program_week_editor.dart
  • workout-logger/lib/screens/widgets/program_week_tile.dart
  • workout-logger/lib/screens/widgets/readiness_card.dart
  • workout-logger/lib/screens/widgets/rest_timer_view.dart
  • workout-logger/lib/screens/widgets/rf_cards.dart
  • workout-logger/lib/screens/widgets/rf_inputs.dart
  • workout-logger/lib/screens/widgets/rf_question_card.dart
  • workout-logger/lib/screens/widgets/rf_widgets.dart
  • workout-logger/lib/screens/widgets/routine_creator.dart
  • workout-logger/lib/screens/widgets/session_details_sheet.dart
  • workout-logger/lib/screens/widgets/sparkline_painter.dart
  • workout-logger/lib/screens/widgets/targets_tab.dart
  • workout-logger/lib/screens/widgets/volume_chart.dart
  • workout-logger/lib/screens/widgets/wheel_picker.dart
  • workout-logger/lib/screens/widgets/workout_header.dart
  • workout-logger/lib/screens/workout_flow_screen.dart
  • workout-logger/lib/screens/workout_summary_screen.dart
  • workout-logger/lib/services/ai/coach_tool_service.dart
  • workout-logger/lib/services/ai/gemini_ai_service.dart
  • workout-logger/lib/services/gemini_context_builder.dart
  • workout-logger/lib/services/health_connect_service.dart
  • workout-logger/lib/services/interfaces/ai_service_interface.dart
  • workout-logger/lib/services/interfaces/health_connect_service_interface.dart
  • workout-logger/lib/services/interfaces/interfaces.dart
  • workout-logger/lib/services/interfaces/ml_service_interface.dart
  • workout-logger/lib/services/interfaces/readiness_manager_interface.dart
  • workout-logger/lib/services/interfaces/storage_service_interface.dart
  • workout-logger/lib/services/managers/conversation_manager.dart
  • workout-logger/lib/services/managers/managers.dart
  • workout-logger/lib/services/managers/pr_manager.dart
  • workout-logger/lib/services/managers/readiness_manager.dart
  • workout-logger/lib/services/ml_service.dart
  • workout-logger/lib/services/settings_provider.dart
  • workout-logger/lib/services/storage_service.dart
  • workout-logger/lib/services/utils/readiness_calculator.dart
  • workout-logger/lib/services/workout_provider.dart
  • workout-logger/lib/theme/app_theme.dart
  • workout-logger/lib/viewmodels/ai_coach_view_model.dart
  • workout-logger/lib/viewmodels/routine_optimizer_view_model.dart
  • workout-logger/pubspec.yaml
  • workout-logger/test/add_custom_exercise_screen_test.dart
  • workout-logger/test/ai_coach_view_model_test.dart
  • workout-logger/test/analytics_manager_test.dart
  • workout-logger/test/analytics_queries_test.dart
  • workout-logger/test/analytics_screen_test.dart
  • workout-logger/test/coach_tool_service_test.dart
  • workout-logger/test/conversation_manager_test.dart
  • workout-logger/test/exercise_library_screen_test.dart
  • workout-logger/test/exercise_progress_view_test.dart
  • workout-logger/test/gemini_ai_service_usage_test.dart
  • workout-logger/test/health_sync_manager_test.dart
  • workout-logger/test/history_manager_test.dart
  • workout-logger/test/ml_service_test.dart
  • workout-logger/test/model_serialization_test.dart
  • workout-logger/test/pr_manager_test.dart
  • workout-logger/test/readiness_calculator_test.dart
  • workout-logger/test/readiness_manager_test.dart
  • workout-logger/test/rf_question_card_test.dart
  • workout-logger/test/routine_optimizer_screen_test.dart
  • workout-logger/test/routine_optimizer_view_model_test.dart
  • workout-logger/test/test_utils/mock_ml_service.dart
  • workout-logger/test/test_utils/mock_storage_service.dart
  • workout-logger/test/workout_provider_test.dart
💤 Files with no reviewable changes (1)
  • workout-logger/android/app/src/main/kotlin/com/workoutlogger/workout_logger/MainActivity.kt

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

🛑 Comments failed to post (28)
CLAUDE.md (1)

298-301: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the graphify prerequisite conditional to avoid dead-end instructions.

Line 298 says to always read graphify-out/GRAPH_REPORT.md before any source inspection, but .gitignore (Line 83 in .gitignore) excludes graphify-out/, so this file may not exist in clean checkouts. Please make this rule conditional with a fallback path.

Proposed doc fix
-- ALWAYS read graphify-out/GRAPH_REPORT.md before reading any source files, running grep/glob searches, or answering codebase questions. The graph is your primary map of the codebase.
+- IF graphify-out/GRAPH_REPORT.md exists, read it first and use it as the primary map. If it does not exist, proceed with normal source inspection.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

- IF graphify-out/GRAPH_REPORT.md exists, read it first and use it as the primary map. If it does not exist, proceed with normal source inspection.
- IF graphify-out/wiki/index.md EXISTS, navigate it instead of reading raw files
- For cross-module "how does X relate to Y" questions, prefer `graphify query "<question>"`, `graphify path "<A>" "<B>"`, or `graphify explain "<concept>"` over grep — these traverse the graph's EXTRACTED + INFERRED edges instead of scanning files
- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).
🤖 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 `@CLAUDE.md` around lines 298 - 301, Update the CLAUDE.md prerequisite so the
instruction about reading graphify-out/GRAPH_REPORT.md is conditional: change
the absolute "ALWAYS read graphify-out/GRAPH_REPORT.md" statement to check for
the file's existence and provide a fallback (e.g., "If
graphify-out/GRAPH_REPORT.md exists, read it first; otherwise use the project's
README or run `graphify update .` and/or `graphify init` to generate the graph")
and adjust the subsequent guidance (the wiki/index.md note and graphify
commands) to reference the same conditional flow; modify the sentence containing
"ALWAYS read graphify-out/GRAPH_REPORT.md" and related lines in CLAUDE.md to
implement this fallback wording.
workout-logger/lib/main.dart (1)

180-184: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat a missing lastSeenVersion as unseen.

Any existing install that already has a name but has never persisted lastSeenVersion will evaluate versionChanged to false here, so the new update sheet never appears for that user on this rollout.

Suggested fix
-      final versionChanged = !needsName &&
-          settings.lastSeenVersion != null &&
-          settings.lastSeenVersion != version;
+      final versionChanged =
+          !needsName && settings.lastSeenVersion != version;
🤖 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 `@workout-logger/lib/main.dart` around lines 180 - 184, The check that computes
versionChanged incorrectly treats a null settings.lastSeenVersion as "seen";
update the logic that sets versionChanged (near the use of
settings.getCurrentVersion(), settings.userName, and settings.lastSeenVersion)
so that if lastSeenVersion is null it counts as a change/unseen—i.e.,
versionChanged should be true when the user has a name (needsName is false) and
(settings.lastSeenVersion is null OR settings.lastSeenVersion != version);
update the versionChanged expression accordingly.
workout-logger/lib/models/models.dart (1)

327-330: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Defensively copy list-backed model fields.

These paths keep mutable list references alive across old/new model instances. A later add/remove on the copied object will mutate state outside the intended copyWith() flow.

Suggested fix
   Routine copyWith({String? name, List<String>? exerciseIds}) => Routine(
     id: id,
     name: name ?? this.name,
-    exerciseIds: exerciseIds ?? this.exerciseIds,
+    exerciseIds: List<String>.from(exerciseIds ?? this.exerciseIds),
     createdAt: createdAt,
   );
@@
   Conversation({
     String? id,
     required this.title,
     this.kind = 'coach',
     DateTime? createdAt,
     DateTime? updatedAt,
     List<ChatMessage>? messages,
   })  : id = id ?? _uuid.v4(),
         createdAt = createdAt ?? DateTime.now(),
         updatedAt = updatedAt ?? createdAt ?? DateTime.now(),
-        messages = messages ?? const [];
+        messages = List<ChatMessage>.unmodifiable(messages ?? const []);
@@
     updatedAt: updatedAt == _sentinel ? this.updatedAt : updatedAt as DateTime,
-    messages: messages == _sentinel
-        ? this.messages
-        : messages as List<ChatMessage>,
+    messages: List<ChatMessage>.unmodifiable(
+      messages == _sentinel ? this.messages : messages as List<ChatMessage>,
+    ),
   );

As per coding guidelines, "All model mutations must go through copyWith() — never mutate state directly".

Also applies to: 875-885, 915-923

🤖 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 `@workout-logger/lib/models/models.dart` around lines 327 - 330,
Routine.copyWith currently preserves the original List reference for exerciseIds
causing shared mutable state; update Routine.copyWith to defensively clone
list-backed fields (e.g., use exerciseIds == null ? List.from(this.exerciseIds)
: List.from(exerciseIds) or .toList()) when constructing the new Routine so the
new instance gets its own list. Apply the same defensive copying pattern to the
other model copyWith methods mentioned (the copyWith implementations around the
other model blocks referenced in the comment) so no list-backed field retains
the original mutable reference.

Source: Coding guidelines

workout-logger/lib/screens/ai_coach_screen.dart (2)

382-382: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use the correct withValues named parameter.

Line 382 uses .withValues(alpha: 0.9), but the correct Dart 3 API is .withValues(alpha: 0.9) with explicitly named parameters. However, the withValues method signature expects an alpha value between 0.0 and 1.0. Verify this is correctly expressing "90% opacity" - if you meant 90% opacity, the value should remain 0.9. If this should be fully opaque with a 10% reduction, clarify the intent.

🤖 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 `@workout-logger/lib/screens/ai_coach_screen.dart` at line 382, The call to
AppColors.surface.withValues uses the withValues API; update the call at
AppColors.surface.withValues so it uses the correct named parameter syntax and a
valid alpha in the 0.0–1.0 range (e.g., alpha: 0.9 for 90% opacity); if your
intention was a different opacity (e.g., 10% transparent), adjust the numeric
value accordingly and keep the named parameter alpha to match the method
signature.

Source: Coding guidelines


817-840: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add const constructor to _AiAvatar.

The _AiAvatar widget at line 816 is missing a const constructor. As per coding guidelines, use const constructors wherever possible in Dart/Flutter code.

🔧 Proposed fix
 class _AiAvatar extends StatelessWidget {
+  const _AiAvatar({super.key});
+
   `@override`
   Widget build(BuildContext context) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

class _AiAvatar extends StatelessWidget {
  const _AiAvatar({super.key});

  `@override`
  Widget build(BuildContext context) {
    return Container(
      width: 28,
      height: 28,
      decoration: BoxDecoration(
        gradient: const LinearGradient(
          colors: [AppColors.primary, Color(0xFF5B21B6)],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(AppRadius.sm),
        boxShadow: [
          BoxShadow(
            color: AppColors.primaryGlow(0.35),
            blurRadius: 8,
            spreadRadius: -2,
          ),
        ],
      ),
      child: const Icon(Icons.auto_awesome_rounded, color: Colors.white, size: 14),
    );
  }
}
🤖 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 `@workout-logger/lib/screens/ai_coach_screen.dart` around lines 817 - 840, The
_AiAvatar StatelessWidget lacks a const constructor; add a const constructor
like "const _AiAvatar({Key? key}) : super(key: key);" to the _AiAvatar class and
ensure its fields (if any) are final/immutable, then update instantiations of
_AiAvatar to use const where possible so the widget can be created as a
compile-time constant.

Source: Coding guidelines

workout-logger/lib/screens/ai_program_generator_screen.dart (1)

50-50: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Avoid inline setState with complex expressions.

Line 50 contains a complex inline setState with a compound assignment. This reduces readability. Consider extracting the assignment to a local variable first.

♻️ Optional refactor
     if (!gemini.isConfigured) {
-      setState(() { _error = 'Add your Gemini API key in Profile → AI Features first.'; });
+      final errorMsg = 'Add your Gemini API key in Profile → AI Features first.';
+      setState(() => _error = errorMsg);
       return;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

     if (!gemini.isConfigured) {
       final errorMsg = 'Add your Gemini API key in Profile → AI Features first.';
       setState(() => _error = errorMsg);
       return;
     }
🤖 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 `@workout-logger/lib/screens/ai_program_generator_screen.dart` at line 50,
Extract the error message into a local variable (e.g., final String msg = 'Add
your Gemini API key in Profile → AI Features first.') and then call setState
with a simple, single-statement assignment that uses that variable (reference
_error and setState) instead of embedding the full string expression inline;
update the code around the setState call that assigns to _error so it becomes
more readable and easier to refactor later.
workout-logger/lib/screens/analytics_screen.dart (1)

86-134: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use design system tokens for border radius.

The pill tab bar uses hardcoded border radius values (14 and 11) instead of AppRadius tokens. As per coding guidelines, always use design system tokens rather than hardcoded values.

♻️ Proposed fix
       child: Container(
         padding: const EdgeInsets.all(3),
         decoration: BoxDecoration(
           color: AppColors.glass2,
-          borderRadius: BorderRadius.circular(14),
+          borderRadius: BorderRadius.circular(AppRadius.lg),
           border: Border.all(color: AppColors.glassBorder),
         ),
         child: Row(
           children: List.generate(_tabs.length, (i) {
             final active = i == _tab;
             return Expanded(
               child: GestureDetector(
                 onTap: () => setState(() => _tab = i),
                 child: AnimatedContainer(
                   duration: const Duration(milliseconds: 200),
                   curve: Curves.easeOut,
                   padding: const EdgeInsets.symmetric(vertical: 8),
                   decoration: BoxDecoration(
                     color: active ? AppColors.primary : Colors.transparent,
-                    borderRadius: BorderRadius.circular(11),
+                    borderRadius: BorderRadius.circular(AppRadius.md),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  Widget _buildPillTabBar() {
    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
      child: Container(
        padding: const EdgeInsets.all(3),
        decoration: BoxDecoration(
          color: AppColors.glass2,
          borderRadius: BorderRadius.circular(AppRadius.lg),
          border: Border.all(color: AppColors.glassBorder),
        ),
        child: Row(
          children: List.generate(_tabs.length, (i) {
            final active = i == _tab;
            return Expanded(
              child: GestureDetector(
                onTap: () => setState(() => _tab = i),
                child: AnimatedContainer(
                  duration: const Duration(milliseconds: 200),
                  curve: Curves.easeOut,
                  padding: const EdgeInsets.symmetric(vertical: 8),
                  decoration: BoxDecoration(
                    color: active ? AppColors.primary : Colors.transparent,
                    borderRadius: BorderRadius.circular(AppRadius.md),
                    boxShadow: active
                        ? [
                            BoxShadow(
                              color: AppColors.primary.withValues(alpha: 0.35),
                              blurRadius: 12,
                            ),
                          ]
                        : null,
                  ),
                  child: Text(
                    _tabs[i],
                    textAlign: TextAlign.center,
                    style: GoogleFonts.geist(
                      fontSize: 13,
                      fontWeight: FontWeight.w600,
                      color: active ? Colors.white : AppColors.textMuted,
                    ),
                  ),
                ),
              ),
            );
          }),
        ),
      ),
    );
  }
🤖 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 `@workout-logger/lib/screens/analytics_screen.dart` around lines 86 - 134,
Replace the hardcoded BorderRadius.circular(14) and BorderRadius.circular(11) in
_buildPillTabBar with the design-system radius tokens from AppRadius (e.g.,
AppRadius.large for the container and AppRadius.small for the pill) so the
Container decoration and the AnimatedContainer decoration use
BorderRadius.circular(AppRadius.<token>) instead of numeric literals; update
both occurrences and ensure AppRadius is imported/available.

Source: Coding guidelines

workout-logger/lib/screens/edit_workout_session_screen.dart (1)

390-395: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear hidden drop entries when a set stops being a dropset.

This handler never nulls log.sets[setIndex].drops when isDropset flips to false. _save() then serializes those stale drop entries into WorkoutSet.drops, even though the user turned the dropset off.

Suggested fix
                 onSetChanged: ({required int setIndex, required double weight, required int reps, required bool isDropset, List<DropsetEntry>? drops}) {
                   setState(() {
-                    log.sets[setIndex].weight = weight;
-                    log.sets[setIndex].reps = reps;
-                    log.sets[setIndex].isDropset = isDropset;
-                    if (drops != null) log.sets[setIndex].drops = drops;
+                    final set = log.sets[setIndex];
+                    set.weight = weight;
+                    set.reps = reps;
+                    set.isDropset = isDropset;
+                    set.drops = isDropset ? (drops ?? set.drops ?? []) : null;
                   });
                   _markChanged();
                 },
🤖 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 `@workout-logger/lib/screens/edit_workout_session_screen.dart` around lines 390
- 395, The onSetChanged handler (the callback updating log.sets in
EditWorkoutSessionScreen) fails to clear stale drop entries when isDropset flips
false; update the handler inside setState so that when isDropset is false you
explicitly set log.sets[setIndex].drops = null (or empty list per
WorkoutSet.drops contract) in addition to updating weight/reps/isDropset,
ensuring _save() will not serialize old drops for non-dropset rows.
workout-logger/lib/screens/exercise_library_screen.dart (2)

201-225: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear action does not clear the visible search text.

Tapping the suffix icon resets _query, but this TextField is uncontrolled, so the old text stays rendered after the filter is cleared. That leaves the UI out of sync with the actual search 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 `@workout-logger/lib/screens/exercise_library_screen.dart` around lines 201 -
225, The TextField is uncontrolled, so clearing the suffix icon only updates
_query but not the visible text; make the field controlled by creating a
TextEditingController in the State (e.g., _searchController), pass controller:
_searchController into the TextField, wire the controller to the existing
onChanged callback (keep onChanged: onChanged), and in the suffix
GestureDetector onTap call _searchController.clear() and then onChanged('') to
keep UI and state in sync; also ensure you initialize the controller (optionally
with initial text) and dispose it in dispose().

483-495: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't toggle selection when selectionMode is off.

This tap handler always mutates _selectedIds, but the count row and submit CTA only exist when selectionMode is true. With the default false, the screen can enter a hidden selection state and gives the user no way to complete or clear it.

🤖 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 `@workout-logger/lib/screens/exercise_library_screen.dart` around lines 483 -
495, The tap handler for ExerciseCard is mutating _selectedIds regardless of
selectionMode; change the onTap callback in the widget builder to first check
selectionMode and only call setState and toggle _selectedIds when selectionMode
is true (leave taps untouched when selectionMode is false). Update the code near
the ExerciseCard creation where sel is computed from _selectedIds and the onTap
lambda is defined so that the toggle logic is gated by the selectionMode boolean
to avoid entering a hidden selection state.
workout-logger/lib/screens/history_screen.dart (3)

236-260: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use design system tokens for border radius.

The search toggle button uses hardcoded borderRadius.circular(10) instead of AppRadius tokens. As per coding guidelines, always use design system tokens rather than hardcoded values.

♻️ Proposed fix
           child: Container(
             width: 36,
             height: 36,
             decoration: BoxDecoration(
               color: _showSearch ? AppColors.primary.withValues(alpha: 0.15) : AppColors.glass2,
-              borderRadius: BorderRadius.circular(10),
+              borderRadius: BorderRadius.circular(AppRadius.md),
               border: Border.all(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

          GestureDetector(
            onTap: () => setState(() {
              _showSearch = !_showSearch;
              if (!_showSearch) {
                _query = '';
                _searchController.clear();
              }
            }),
            child: Container(
              width: 36,
              height: 36,
              decoration: BoxDecoration(
                color: _showSearch ? AppColors.primary.withValues(alpha: 0.15) : AppColors.glass2,
                borderRadius: BorderRadius.circular(AppRadius.md),
                border: Border.all(
                  color: _showSearch ? AppColors.primary.withValues(alpha: 0.4) : AppColors.glassBorder,
                ),
              ),
              child: Icon(
                _showSearch ? Icons.close_rounded : Icons.search_rounded,
                size: 16,
                color: _showSearch ? AppColors.primary : AppColors.textMuted,
              ),
            ),
          ),
🤖 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 `@workout-logger/lib/screens/history_screen.dart` around lines 236 - 260, The
Container's decoration uses a hardcoded borderRadius.circular(10); replace that
literal with the design token from AppRadius (e.g., use AppRadius.small) so the
GestureDetector/Container decoration consistently uses the design system token
(update BorderRadius.circular(10) to BorderRadius.circular(AppRadius.small) or
equivalent AppRadius token and add any necessary import).

Source: Coding guidelines


307-320: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use design system tokens for border radius.

The calendar navigation buttons use hardcoded borderRadius.circular(8) instead of AppRadius tokens. As per coding guidelines, always use design system tokens rather than hardcoded values.

♻️ Proposed fix
                 child: Container(
                   padding: const EdgeInsets.all(6),
                   decoration: BoxDecoration(
                     color: AppColors.glass2,
-                    borderRadius: BorderRadius.circular(8),
+                    borderRadius: BorderRadius.circular(AppRadius.sm),
                   ),

Apply to both left and right navigation buttons (lines 307-320 and 341-357).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

                GestureDetector(
                  onTap: () => setState(() {
                    _calendarMonth = DateTime(_calendarMonth.year, _calendarMonth.month - 1);
                    _selectedDay = null;
                  }),
                  child: Container(
                    padding: const EdgeInsets.all(6),
                    decoration: BoxDecoration(
                      color: AppColors.glass2,
                      borderRadius: BorderRadius.circular(AppRadius.sm),
                    ),
                    child: const Icon(Icons.chevron_left_rounded, size: 18, color: AppColors.textMuted),
                  ),
                ),
🤖 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 `@workout-logger/lib/screens/history_screen.dart` around lines 307 - 320, The
calendar nav buttons use a hardcoded BorderRadius.circular(8); replace that with
the design token (e.g., AppRadius.someRadius) in the Container decoration for
the left and right navigation buttons so they use the design system. Locate the
GestureDetector/Container widgets that update _calendarMonth and _selectedDay
(the left chevron button with Icon(Icons.chevron_left_rounded) and the
corresponding right chevron) and swap BorderRadius.circular(8) for the
appropriate AppRadius token used elsewhere in the app.

Source: Coding guidelines


434-455: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use design system tokens for border radius.

The search bar uses hardcoded borderRadius.circular(12) instead of AppRadius tokens. As per coding guidelines, always use design system tokens rather than hardcoded values.

♻️ Proposed fix
     return Container(
       height: 44,
       decoration: BoxDecoration(
         color: AppColors.glass2,
-        borderRadius: BorderRadius.circular(12),
+        borderRadius: BorderRadius.circular(AppRadius.md),
         border: Border.all(color: AppColors.glassBorder),
       ),
🤖 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 `@workout-logger/lib/screens/history_screen.dart` around lines 434 - 455, The
Container decoration uses a hardcoded borderRadius: BorderRadius.circular(12);
replace this with the design token from your radius system (e.g. use
AppRadius.<appropriateToken>) so the code becomes borderRadius:
BorderRadius.circular(AppRadius.<token>) or
BorderRadius.all(Radius.circular(AppRadius.<token>)); update the instance in
history_screen.dart (the Container/TextField search bar decoration) to reference
the AppRadius token instead of 12.

Source: Coding guidelines

workout-logger/lib/screens/home_screen.dart (3)

278-306: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use design system tokens for border radius.

The AI coach button uses hardcoded borderRadius.circular(12) instead of AppRadius tokens. As per coding guidelines, always use design system tokens rather than hardcoded values.

♻️ Proposed fix
             child: Container(
               width: 40,
               height: 40,
               decoration: BoxDecoration(
-                borderRadius: BorderRadius.circular(12),
+                borderRadius: BorderRadius.circular(AppRadius.md),
                 gradient: const LinearGradient(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

              onTap: () => Navigator.push(
                context,
                MaterialPageRoute(builder: (_) => const AiCoachScreen()),
              ),
              child: Container(
                width: 40,
                height: 40,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(AppRadius.md),
                  gradient: const LinearGradient(
                    colors: [AppColors.primary, Color(0xFF5B21B6)],
                    begin: Alignment.topLeft,
                    end: Alignment.bottomRight,
                  ),
                  boxShadow: [
                    BoxShadow(
                      color: AppColors.primaryGlow(0.35),
                      blurRadius: 12,
                      spreadRadius: -3,
                    ),
                  ],
                ),
                child: const Icon(
                  Icons.auto_awesome_rounded,
                  size: 18,
                  color: Colors.white,
                ),
              ),
            ),
🤖 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 `@workout-logger/lib/screens/home_screen.dart` around lines 278 - 306, Replace
the hardcoded borderRadius.circular(12) in the AI coach button Container
decoration with the design token from the app's radius tokens (use
AppRadius.small instead of 12) so the snippet uses
BorderRadius.circular(AppRadius.small) (update the Container/BoxDecoration in
the HomeScreen build where borderRadius.circular(12) is used for the AI coach
button that navigates to AiCoachScreen).

Source: Coding guidelines


308-328: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use design system tokens for border radius.

The profile button uses hardcoded borderRadius.circular(12) instead of AppRadius tokens. As per coding guidelines, always use design system tokens rather than hardcoded values.

♻️ Proposed fix
             child: Container(
               width: 40,
               height: 40,
               decoration: BoxDecoration(
-                borderRadius: BorderRadius.circular(12),
+                borderRadius: BorderRadius.circular(AppRadius.md),
                 color: AppColors.glass2,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            GestureDetector(
              onTap: () => Navigator.push(
                context,
                MaterialPageRoute(builder: (_) => const ProfileScreen()),
              ),
              child: Container(
                width: 40,
                height: 40,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(AppRadius.md),
                  color: AppColors.glass2,
                  border: Border.all(color: AppColors.glassBorder),
                ),
                child: const Icon(
                  Icons.person_outline_rounded,
                  size: 18,
                  color: AppColors.textSoft,
                ),
              ),
            ),
          ],
🤖 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 `@workout-logger/lib/screens/home_screen.dart` around lines 308 - 328, Replace
the hardcoded BorderRadius.circular(12) used in the Container inside the
GestureDetector (the profile button) with the design-system radius token from
AppRadius (e.g., AppRadius.* appropriate token) so the widget uses the shared
radius values; update the Container's decoration to use
BorderRadius.circular(...) -> BorderRadius.all(AppRadius.<token>) or directly
the token shape as your design system expects, ensuring the rest of the
properties (color, border) remain unchanged and the ProfileScreen navigation and
Icon usage are not modified.

Source: Coding guidelines


464-507: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use design system tokens for border radius.

The Start/Resume button uses hardcoded borderRadius.circular(12) instead of AppRadius tokens. As per coding guidelines, always use design system tokens rather than hardcoded values.

♻️ Proposed fix
                   child: Container(
                     padding: const EdgeInsets.symmetric(
                         horizontal: 14, vertical: 10),
                     decoration: BoxDecoration(
                       color: isActive ? AppColors.warning : AppColors.primary,
-                      borderRadius: BorderRadius.circular(12),
+                      borderRadius: BorderRadius.circular(AppRadius.md),
                       border: Border.all(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

                  GestureDetector(
                    onTap: isActive
                        ? () => homeState?._resumeWorkout(context)
                        : () => homeState?._showRoutineSelector(context),
                    child: Container(
                      padding: const EdgeInsets.symmetric(
                          horizontal: 14, vertical: 10),
                      decoration: BoxDecoration(
                        color: isActive ? AppColors.warning : AppColors.primary,
                        borderRadius: BorderRadius.circular(AppRadius.md),
                        border: Border.all(
                          color: Colors.white.withValues(alpha: 0.18),
                        ),
                        boxShadow: [
                          BoxShadow(
                            color: (isActive ? AppColors.warning : AppColors.primary)
                                .withValues(alpha: 0.35),
                            blurRadius: 16,
                          ),
                        ],
                      ),
                      child: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Icon(
                            isActive
                                ? Icons.play_arrow_rounded
                                : Icons.flash_on_rounded,
                            size: 12,
                            color: Colors.white,
                          ),
                          const SizedBox(width: 4),
                          Text(
                            isActive ? 'Resume' : 'Start',
                            style: GoogleFonts.geist(
                              fontSize: 13,
                              fontWeight: FontWeight.w600,
                              color: Colors.white,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
🤖 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 `@workout-logger/lib/screens/home_screen.dart` around lines 464 - 507, Replace
the hardcoded BorderRadius.circular(12) in the Start/Resume button's Container
decoration with the design system token (e.g., AppRadius.medium or the
appropriate AppRadius constant) so the GestureDetector/Container uses the
AppRadius token for borderRadius; update the BoxDecoration in the widget where
borderRadius is set to reference AppRadius instead of BorderRadius.circular(12)
and ensure imports for the design tokens are present if needed.

Source: Coding guidelines

workout-logger/lib/screens/onboarding_screen.dart (1)

61-156: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use spacing tokens consistently in the welcome form.

This block mixes AppSpacing/AppRadius with raw layout literals (28, 12, 16, 14), so the new screen can drift from the design system this PR is trying to centralize.

Suggested direction
-              padding: const EdgeInsets.symmetric(horizontal: 28),
+              padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg),
@@
-                  const SizedBox(height: 12),
+                  const SizedBox(height: AppSpacing.sm),
@@
-                      contentPadding: const EdgeInsets.symmetric(
-                        horizontal: 16,
-                        vertical: 14,
-                      ),
+                      contentPadding: const EdgeInsets.symmetric(
+                        horizontal: AppSpacing.md,
+                        vertical: AppSpacing.sm,
+                      ),

As per coding guidelines, "Always use design system tokens (AppColors, AppSpacing, AppRadius) rather than hardcoded values".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Spacer(flex: 2),
                  // Logo mark
                  Container(
                    width: 72,
                    height: 72,
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(AppRadius.lg),
                      gradient: LinearGradient(
                        colors: [
                          AppColors.primary,
                          AppColors.secondary.withValues(alpha: 0.8),
                        ],
                        begin: Alignment.topLeft,
                        end: Alignment.bottomRight,
                      ),
                      boxShadow: [
                        BoxShadow(
                          color: AppColors.primary.withValues(alpha: 0.5),
                          blurRadius: 32,
                          spreadRadius: 4,
                        ),
                      ],
                    ),
                    child: const Icon(
                      Icons.fitness_center_rounded,
                      color: Colors.white,
                      size: 36,
                    ),
                  ),
                  const SizedBox(height: AppSpacing.xl),
                  Text(
                    'Welcome to\nRepForge',
                    style: GoogleFonts.geist(
                      fontSize: 36,
                      fontWeight: FontWeight.w800,
                      color: AppColors.textPrimary,
                      height: 1.1,
                      letterSpacing: -1,
                    ),
                  ),
                  const SizedBox(height: AppSpacing.sm),
                  Text(
                    'Track every rep. Beat every record.\nForge your best self.',
                    style: GoogleFonts.geist(
                      fontSize: 15,
                      color: AppColors.textMuted,
                      height: 1.5,
                    ),
                  ),
                  const Spacer(flex: 2),
                  Text(
                    'WHAT SHOULD WE CALL YOU?',
                    style: GoogleFonts.geist(
                      fontSize: 11,
                      fontWeight: FontWeight.w600,
                      color: AppColors.textFaint,
                      letterSpacing: 1.2,
                    ),
                  ),
                  const SizedBox(height: AppSpacing.sm),
                  TextField(
                    controller: _controller,
                    autofocus: true,
                    textCapitalization: TextCapitalization.words,
                    style: GoogleFonts.geist(
                      color: AppColors.textPrimary,
                      fontSize: 16,
                      fontWeight: FontWeight.w500,
                    ),
                    decoration: InputDecoration(
                      hintText: 'Your name',
                      hintStyle: GoogleFonts.geist(color: AppColors.textFaint),
                      filled: true,
                      fillColor: AppColors.glass2,
                      border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(AppRadius.md),
                        borderSide: BorderSide(color: AppColors.glassBorder),
                      ),
                      enabledBorder: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(AppRadius.md),
                        borderSide: BorderSide(color: AppColors.glassBorder),
                      ),
                      focusedBorder: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(AppRadius.md),
                        borderSide: BorderSide(color: AppColors.primary, width: 1.5),
                      ),
                      contentPadding: const EdgeInsets.symmetric(
                        horizontal: AppSpacing.md,
                        vertical: AppSpacing.sm,
                      ),
🤖 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 `@workout-logger/lib/screens/onboarding_screen.dart` around lines 61 - 156, The
UI uses hardcoded spacing and padding values (28, 12, 16, 14) inside the Column
and TextField decorations instead of the design tokens; update those literals to
use the appropriate tokens (e.g., replace horizontal padding 28 with
AppSpacing.[sm/md/lg] or a consistent token, replace SizedBox height 12 with
AppSpacing.[xs/sm], and replace contentPadding horizontal/vertical 16/14 with
AppSpacing tokens) to match the design system and keep existing usage of
AppRadius and AppSpacing elsewhere; ensure changes are applied around the
Container/Padding/ SizedBox/TextField (referencing _controller, TextField,
AppRadius, AppSpacing) so spacing is consistent across the
onboarding_screen.dart.

Source: Coding guidelines

workout-logger/lib/screens/profile_screen.dart (1)

205-207: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't report a canceled share as a successful export.

ShareResultStatus.dismissed means the user closed the share sheet without choosing a destination. Showing the success snackbar here can falsely reassure them that a backup was saved even though the file never left the app's temp directory.

Suggested fix
-      if (result.status == ShareResultStatus.success ||
-          result.status == ShareResultStatus.dismissed) {
+      if (result.status == ShareResultStatus.success) {
         _showSnack('Backup exported successfully!', AppColors.success);
+      } else if (result.status == ShareResultStatus.dismissed) {
+        _showSnack('Backup export canceled.', AppColors.warning);
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      if (result.status == ShareResultStatus.success) {
        _showSnack('Backup exported successfully!', AppColors.success);
      } else if (result.status == ShareResultStatus.dismissed) {
        _showSnack('Backup export canceled.', AppColors.warning);
      }
🤖 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 `@workout-logger/lib/screens/profile_screen.dart` around lines 205 - 207, The
code treats ShareResultStatus.dismissed as a successful export; change the
condition so only ShareResultStatus.success triggers _showSnack('Backup exported
successfully!', AppColors.success) and handle ShareResultStatus.dismissed
separately (e.g., call _showSnack with a canceled/neutral message and
AppColors.warning or do nothing) so users aren't misled that a backup left the
app; update the check of result.status (and any surrounding export/share
function) to only consider ShareResultStatus.success as success and add an
else-if for ShareResultStatus.dismissed to show an appropriate cancellation
message.
workout-logger/lib/screens/routine_optimizer_screen.dart (1)

473-497: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add const constructor to _OptimizerAvatar.

The _OptimizerAvatar widget at line 473 is missing a const constructor. As per coding guidelines, use const constructors wherever possible.

🔧 Proposed fix
 class _OptimizerAvatar extends StatelessWidget {
+  const _OptimizerAvatar({super.key});
+
   `@override`
   Widget build(BuildContext context) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

class _OptimizerAvatar extends StatelessWidget {
  const _OptimizerAvatar({super.key});

  `@override`
  Widget build(BuildContext context) {
    return Container(
      width: 28,
      height: 28,
      decoration: BoxDecoration(
        gradient: const LinearGradient(
          colors: [AppColors.secondary, Color(0xFF0097A7)],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(AppRadius.sm),
        boxShadow: [
          BoxShadow(
            color: AppColors.secondaryGlow(0.35),
            blurRadius: 8,
            spreadRadius: -2,
          ),
        ],
      ),
      child: const Icon(Icons.auto_fix_high_rounded, color: Colors.white, size: 14),
    );
  }
}
🤖 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 `@workout-logger/lib/screens/routine_optimizer_screen.dart` around lines 473 -
497, The _OptimizerAvatar StatelessWidget lacks a const constructor; add a const
constructor (const _OptimizerAvatar({Key? key}) : super(key: key);) to the
_OptimizerAvatar class and update all places that instantiate _OptimizerAvatar
to use const where applicable so the widget can be const-constructed and
leverage compile-time optimizations.

Source: Coding guidelines

workout-logger/lib/screens/routines_screen.dart (1)

494-518: 🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff

Verify the routine session count threshold.

The code checks if sessionCount < 3 before allowing optimization (lines 497-510). This threshold appears arbitrary and might be too restrictive for users who want to optimize after 1-2 sessions. Consider if this threshold should be configurable or if a warning (rather than blocking) would be more appropriate.

🤖 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 `@workout-logger/lib/screens/routines_screen.dart` around lines 494 - 518, The
current onTap handler in the GestureDetector blocks navigation to
RoutineOptimizerScreen when sessionCount < 3; make the threshold configurable
and/or change the behavior to warn instead of blocking: extract the hardcoded
"3" into a configurable constant or a user setting (e.g., optimizerMinSessions)
and reference it where sessionCount is computed, and if you prefer a soft
warning, replace the early return with a SnackBar warning that still calls
Navigator.push to RoutineOptimizerScreen(routine: routine) (or add an override
confirmation dialog) so users can proceed after 1–2 sessions if they choose.
workout-logger/lib/screens/widgets/activity_heatmap.dart (1)

23-41: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use design system tokens for spacing.

The GridView uses hardcoded spacing values (crossAxisSpacing: 3, mainAxisSpacing: 3) instead of AppSpacing tokens. As per coding guidelines, always use design system tokens rather than hardcoded values.

🤖 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 `@workout-logger/lib/screens/widgets/activity_heatmap.dart` around lines 23 -
41, Replace the hardcoded spacing numbers in GridView.builder's
SliverGridDelegateWithFixedCrossAxisCount with the design system tokens (use
AppSpacing.small for crossAxisSpacing and mainAxisSpacing) so spacing comes from
the design system; update the SliverGridDelegateWithFixedCrossAxisCount
constructor in the activity_heatmap.dart GridView.builder (references:
GridView.builder, SliverGridDelegateWithFixedCrossAxisCount, crossAxisSpacing,
mainAxisSpacing) to use AppSpacing tokens instead of the literal 3.

Source: Coding guidelines

workout-logger/lib/screens/widgets/analytics_overview.dart (1)

361-403: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use design system tokens for border radius.

The range toggle component uses hardcoded border radius values (10 and 7) instead of AppRadius tokens. As per coding guidelines, always use design system tokens rather than hardcoded values.

♻️ Proposed fix
     return Container(
       padding: const EdgeInsets.all(3),
       decoration: BoxDecoration(
         color: AppColors.glass2,
-        borderRadius: BorderRadius.circular(10),
+        borderRadius: BorderRadius.circular(AppRadius.md),
         border: Border.all(color: AppColors.glassBorder),
       ),
       child: Row(
         mainAxisSize: MainAxisSize.min,
         children: _Range.values.map((r) {
           final active = r == value;
           return GestureDetector(
             onTap: () => onChanged(r),
             child: AnimatedContainer(
               duration: const Duration(milliseconds: 180),
               curve: Curves.easeOut,
               padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
               decoration: BoxDecoration(
                 color: active ? AppColors.primary : Colors.transparent,
-                borderRadius: BorderRadius.circular(7),
+                borderRadius: BorderRadius.circular(AppRadius.sm),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

class _RangeToggle extends StatelessWidget {
  const _RangeToggle({required this.value, required this.onChanged});
  final _Range value;
  final ValueChanged<_Range> onChanged;

  `@override`
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(3),
      decoration: BoxDecoration(
        color: AppColors.glass2,
        borderRadius: BorderRadius.circular(AppRadius.md),
        border: Border.all(color: AppColors.glassBorder),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: _Range.values.map((r) {
          final active = r == value;
          return GestureDetector(
            onTap: () => onChanged(r),
            child: AnimatedContainer(
              duration: const Duration(milliseconds: 180),
              curve: Curves.easeOut,
              padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
              decoration: BoxDecoration(
                color: active ? AppColors.primary : Colors.transparent,
                borderRadius: BorderRadius.circular(AppRadius.sm),
              ),
              child: Text(
                r.label,
                style: GoogleFonts.geistMono(
                  fontSize: 11,
                  fontWeight: FontWeight.w700,
                  color: active ? Colors.white : AppColors.textMuted,
                ),
              ),
            ),
          );
        }).toList(),
      ),
    );
  }
}
🤖 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 `@workout-logger/lib/screens/widgets/analytics_overview.dart` around lines 361
- 403, Replace the hardcoded BorderRadius.circular(10) and
BorderRadius.circular(7) in the _RangeToggle widget with the design-system
radius tokens (use the appropriate AppRadius token(s) from your design system,
e.g. AppRadius.medium/AppRadius.small) so both the container decoration and the
inner AnimatedContainer use AppRadius instead of numeric literals; update the
BoxDecoration.borderRadius in _RangeToggle and the borderRadius on the inner
AnimatedContainer to reference the AppRadius token(s).

Source: Coding guidelines

workout-logger/lib/screens/widgets/calendar_grid.dart (2)

58-67: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use design system tokens for spacing.

The calendar grid uses hardcoded spacing values (crossAxisSpacing: 4, mainAxisSpacing: 4) instead of AppSpacing tokens. As per coding guidelines, always use design system tokens rather than hardcoded values.

🤖 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 `@workout-logger/lib/screens/widgets/calendar_grid.dart` around lines 58 - 67,
The SliverGridDelegateWithFixedCrossAxisCount in GridView.builder currently uses
hardcoded spacing (crossAxisSpacing: 4, mainAxisSpacing: 4); replace those
literals with the design system tokens (e.g., AppSpacing.small or the
appropriate AppSpacing constant) so both crossAxisSpacing and mainAxisSpacing
reference AppSpacing instead of numeric values; update the
GridView.builder/SliverGridDelegateWithFixedCrossAxisCount instantiation
accordingly to use the token names.

Source: Coding guidelines


188-203: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use design system tokens for border radius.

The day cell uses hardcoded borderRadius.circular(8) instead of AppRadius tokens. As per coding guidelines, always use design system tokens rather than hardcoded values.

♻️ Proposed fix
     return GestureDetector(
       onTap: onTap,
       child: Container(
         decoration: BoxDecoration(
-          borderRadius: BorderRadius.circular(8),
+          borderRadius: BorderRadius.circular(AppRadius.sm),
           color: bg,
           border: border,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    return GestureDetector(
      onTap: onTap,
      child: Container(
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(AppRadius.sm),
          color: bg,
          border: border,
          boxShadow: isSelected
              ? [
                  BoxShadow(
                    color: AppColors.primary.withValues(alpha: 0.25),
                    blurRadius: 8,
                  ),
                ]
              : 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 `@workout-logger/lib/screens/widgets/calendar_grid.dart` around lines 188 -
203, Replace the hardcoded borderRadius.circular(8) inside the Container's
BoxDecoration (in the day cell widget returned by GestureDetector) with the
design-system token (e.g., AppRadius.small) so it uses the AppRadius value
instead of a magic number; update the borderRadius property to
BorderRadius.circular(AppRadius.small) or the appropriate AppRadius token to
match the design system.

Source: Coding guidelines

workout-logger/lib/screens/widgets/editable_exercise_card.dart (2)

18-45: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Make the editable models immutable and update them via copyWith().

These new model types are mutable state holders, and the edit flow now depends on mutating nested fields in place. That conflicts with the repo rule and makes future state updates much easier to miss once this UI starts relying on identity changes.

As per coding guidelines, "All model mutations must go through copyWith() — never mutate state directly."

🤖 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 `@workout-logger/lib/screens/widgets/editable_exercise_card.dart` around lines
18 - 45, EditableExerciseLog and EditableSet are mutable but must be immutable
and updated via copyWith; change all fields on EditableExerciseLog and
EditableSet to final, make constructors constant/positional as appropriate,
ensure lists (sets and drops) are stored as unmodifiable (e.g.,
List.unmodifiable) and then add copyWith methods on both classes
(EditableExerciseLog.copyWith and EditableSet.copyWith) that let callers replace
fields or produce modified copies of nested lists (e.g., return a new
EditableExerciseLog with a modified sets list where an EditableSet is replaced
via its copyWith, and EditableSet.copyWith should allow updating weight, reps,
isDropset, drops, timeTaken, and timestamp). Update any code that previously
mutated fields directly to use these copyWith helpers and to replace lists
atomically rather than mutating in place.

Source: Coding guidelines


331-352: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't hardcode kg in the workout editor rows.

These inputs always label weights in kilograms, while the rest of this slice already formats weights through SettingsProvider. In lb mode, this editor will still show raw stored kg values, which makes it easy to edit and save the wrong weight.

Also applies to: 531-554

🤖 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 `@workout-logger/lib/screens/widgets/editable_exercise_card.dart` around lines
331 - 352, The weight input is hardcoded to 'kg' in the _NumField (using
_weightCtrl/_weightFocus and onWeightChanged), causing wrong display/edits in lb
mode; update the suffix to read the unit label from SettingsProvider (or
equivalent settings getter) and ensure displayed value and parsing honor the
current unit by converting stored kg to the user unit when initializing
_weightCtrl.text and converting back to kg before calling
widget.onWeightChanged; apply the same change for the other occurrence around
the 531-554 block so both editor rows use the settings-based unit and proper
conversions.
workout-logger/lib/screens/widgets/exercise_details_sheet.dart (1)

233-244: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't hardcode a positive sign for the growth slope.

This section only checks r2, not whether growthModel.slope is positive. A negative trend will currently render as +-… volume/session, which is misleading.

Suggested fix
-                  Text(
-                    '+${settings.toDisplay(growthModel.slope).toStringAsFixed(1)} ${settings.unitLabel} volume/session',
+                  Text(
+                    '${settings.toDisplay(growthModel.slope) > 0 ? '+' : ''}${settings.toDisplay(growthModel.slope).toStringAsFixed(1)} ${settings.unitLabel} volume/session',
                     style: const TextStyle(
                       color: AppColors.success,
                       fontSize: 13,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

                    child: Text(
                      '${settings.toDisplay(growthModel.slope) > 0 ? '+' : ''}${settings.toDisplay(growthModel.slope).toStringAsFixed(1)} ${settings.unitLabel} volume/session',
                      style: const TextStyle(
                        color: AppColors.success,
                        fontSize: 13,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ),
                  Text(
                    'R² ${(growthModel.r2 * 100).toStringAsFixed(0)}%',
                    style: const TextStyle(
🤖 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 `@workout-logger/lib/screens/widgets/exercise_details_sheet.dart` around lines
233 - 244, The Text currently hardcodes a '+' before the slope which causes
"+-..." when growthModel.slope is negative; update the widget that builds the
label (the Text containing
'+${settings.toDisplay(growthModel.slope).toStringAsFixed(1)}
${settings.unitLabel} volume/session') to prepend '+' only when
growthModel.slope > 0 (e.g., compute a sign = growthModel.slope > 0 ? '+' : ''
and render "$sign${settings.toDisplay(growthModel.slope).toStringAsFixed(1)}
${settings.unitLabel} volume/session"), keeping the existing settings.toDisplay
and unitLabel usage so negative values retain their '-' sign.
workout-logger/lib/screens/widgets/exercise_input_section.dart (1)

877-892: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Replace the hardcoded deload color with an AppColors token.

This is the one state in the new banner that still bypasses the centralized theme palette, so it will drift from the color refactor in this PR.

Suggested fix
         border: Border.all(
           color: week.isDeload
-              ? Colors.amber.withValues(alpha: 0.4)
+              ? AppColors.warning.withValues(alpha: 0.4)
               : AppColors.primary.withValues(alpha: 0.3),
         ),
@@
                   child: Icon(Icons.battery_charging_full_rounded,
-                      size: 14, color: Colors.amber),
+                      size: 14, color: AppColors.warning),
                 ),

As per coding guidelines, "Always use design system tokens (AppColors, AppSpacing, AppRadius) rather than hardcoded values."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        border: Border.all(
          color: week.isDeload
              ? AppColors.warning.withValues(alpha: 0.4)
              : AppColors.primary.withValues(alpha: 0.3),
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              if (week.isDeload)
                const Padding(
                  padding: EdgeInsets.only(right: 4),
                  child: Icon(Icons.battery_charging_full_rounded,
                      size: 14, color: AppColors.warning),
🤖 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 `@workout-logger/lib/screens/widgets/exercise_input_section.dart` around lines
877 - 892, The deload banner is using hardcoded Colors.amber in two places;
replace those with the design token from AppColors (e.g., AppColors.amber or the
project's deload/warning token) so the theme system is used consistently. Update
the Border.all color branch (where it currently uses week.isDeload ?
Colors.amber.withValues(alpha: 0.4) ...) and the Icon color (Icon(..., color:
Colors.amber)) to use the matching AppColors token and its withValues(alpha:
...) variant; keep the week.isDeload condition and existing sizing/padding
intact.

Source: Coding guidelines

claude and others added 6 commits June 10, 2026 12:53
…tection

Replaces the single exponentially-weighted linear regression with a
two-candidate fit chosen by weighted residual error:

- Linear and logarithmic (y = a + b·ln(1+x)) candidates — the log curve
  captures the diminishing returns muscle growth actually follows instead
  of promising linear gains forever; only eligible with >=6 points over
  >=14 days and a 2% RSS margin to prevent flip-flopping
- One Tukey-bisquare robust re-weighting pass per candidate so a single
  deload or cut-short session no longer tilts the trend
- GrowthModel now carries curve type, coefficient, lastX and a weighted
  residual stdError; slope is the instantaneous per-day rate at the
  newest point, so all existing consumers stay semantically correct
- New weeklyGrowthPercent (growth relative to current level) drives
  scale-independent plateau/decline detection: same thresholds work for
  a novice bench and a 10t weekly squat volume
- recommendSets adds a deload branch (~10% back-off, plate-rounded) when
  volume is genuinely regressing; trend signals require r2 > 0.2
- predictTargetCompletion inverts the fitted curve (log-aware), caps
  predictions at 2 years; confidence interval now derived from stdError
  converted to days instead of an ad-hoc R² heuristic
- Volume chart trend line now evaluates the model at day offsets instead
  of session indices (pre-existing mismatch); per-muscle trend arrows use
  relative weekly growth; mislabeled kg/session displays fixed to kg/week
- AI coach growth payload exposes curve, weekly_growth_percent and a
  plateauing/declining/improving trend

https://claude.ai/code/session_01FTgsHTfbvXsxwTUe74UrYe
Covers SleepHrSnapshot data model, 10-min bar chart with stage colors,
moving-average trend line, and the per-stage HR distribution (range bar)
chart replacing the two REM/Deep comparison cards.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added DebugLogBuffer class to capture and store debugPrint calls in a circular buffer.
- Integrated DebugLogBuffer into the main application via the attach method.

refactor: Update GeminiContextBuilder instructions and workflow

- Revised instructions for modifying user data and clarified the workflow steps.
- Enhanced clarity on data fetching and analysis processes.

fix: Improve HealthConnectService error handling and logging

- Added detailed debug prints for HealthConnectService methods.
- Adjusted permissions handling for heart rate data to use heartRateSeries.
- Enhanced error handling for read permissions and data fetching.

feat: Enhance ReadinessManager with sleep HR snapshot functionality

- Integrated SleepHrSnapshot to track heart rate during sleep.
- Improved refresh logic to build and display sleep HR data.
- Added debug tracing for readiness calculations and data fetching.

refactor: Update ReadinessCalculator for accurate sleep duration calculations

- Modified lastNightSleep method to return total sleep minutes instead of the longest period.
- Maintained backward compatibility with a synthetic SleepPeriod return.
…nagement

- Added HealthHistoryManager to handle sleep and heart rate data aggregation and caching.
- Introduced utility functions for date range calculations and stepping through time periods.
- Implemented methods to fetch and cache daily heart rate snapshots and aggregated sleep data.
- Created a new sleep_hr_builder utility for building sleep HR snapshots for any night.
- Updated ReadinessManager to utilize the new HealthHistoryManager for daily HR snapshots.
- Added unit tests for HealthHistoryManager covering various scenarios for sleep and heart rate data.
@gitguardian

gitguardian Bot commented Jun 16, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
34035904 Triggered Generic Password 57ee362 workout-logger/android/app/build.gradle.kts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@Devasy
Devasy merged commit ff1761c into Revamp-of-UI Jun 16, 2026
2 of 3 checks passed
@Devasy
Devasy deleted the claude/fitness-app-pain-points-sclt63 branch June 16, 2026 16:48
Devasy added a commit that referenced this pull request Jun 16, 2026
* feat: add workout summary screen and enhance app theme colors

- Implemented WorkoutSummaryScreen to display post-workout details including duration, volume, sets, and exercises.
- Added visual elements such as trophy header and muscle groups trained section.
- Refactored AppTheme to centralize color management through AppColors, improving maintainability and consistency across the app.
- Updated theme properties for better visual coherence and modern styling.

* feat: add predictive back page transitions for Android in AppTheme

* Refactor Programs Screen and add Week Structure Editor

- Updated ProgramsScreen to improve UI elements and replace AppTheme with AppColors.
- Enhanced FloatingActionButton styles and added new RFWidgets for better consistency.
- Implemented a new ProgramWeekEditorStep widget for editing week structures in training programs.
- Introduced ProgramWeekTile for displaying collapsible week cards in ProgramDetailScreen.
- Added functionality for managing deload weeks and intensity factors within the week editor.
- Improved overall code organization and readability across the modified files.

* feat: Enhance UI with new glassmorphic components and charts

- Introduced `GlassCard` with gradient background and accent border option.
- Added `AmbientGlow` for decorative ambient effects.
- Implemented `RFNavBar` for a custom bottom navigation bar with glassmorphism.
- Created `Sparkline` and `VolumeChart` widgets for data visualization.
- Developed `WheelPickerField` for weight and reps input with haptic feedback.
- Updated `WorkoutHeader` with gradient background and improved text styles.
- Refined `AppTheme` with new color definitions and text styles using Google Fonts.
- Added Google Fonts dependency for enhanced typography.

* feat: add personal records feature

- Introduced PersonalRecord model to track best weight, reps, and volume for exercises.
- Implemented PRManager to manage personal records, including checking and updating records after workouts.
- Added methods in storage service for saving and retrieving personal records.
- Updated UI to display personal records in the Analytics screen and Workout Summary screen.
- Enhanced onboarding process to prompt for user name and handle version updates.
- Refactored various screens to improve layout and user experience.

* feat: update exercise library screen tests with new icon and text changes

* feat: update .gitignore and CLAUDE.md for new graphify output and project guidelines; remove MainActivity.kt

* analytics_screen.dart

_FrequencyGrid: added w >= 0 && guard so future-dated sessions can't insert negative map keys
_MuscleVolumeChart: early-return isEmpty when maxVol == 0 (prevents NaN); converted volume display via settings.toDisplay + settings.unitLabel
dashboard_widgets.dart

startOfWeek now truncated to midnight (DateTime(y,m,d)) before subtracting weekdays — fixes the bug where sessions earlier in the day than "now" were excluded
Volume StatGridCard now uses settings.toDisplay(weeklyVolume) and 'Volume (${settings.unitLabel})'
edit_workout_session_screen.dart

drops: s.drops?.toList() — defensive copy prevents shared-mutation with the original WorkoutSession
exercise_details_sheet.dart

Added else branch on delete failure — shows error SnackBar instead of silently doing nothing
Set chips now use settings.toDisplay(s.weight) + settings.unitLabel
Growth trend label uses settings.toDisplay(growthModel.slope) + settings.unitLabel
exercise_input_section.dart

Icon(Icons.auto_awesome_rounded, …) → const Icon(…)
exercise_progress_view.dart

DropdownButton.value guarded with ids.contains(selected) ? selected : null — prevents assert/crash when the selected exercise id is no longer in the performed set
profile_sections.dart

const _SectionDivider() and const _ComingSoonBadge() constructors + all call sites updated
rf_inputs.dart

Added onLongPressEnd to _StepButton (wired to GestureDetector.onLongPressEnd)
_NumberPickerSheetState now holds Timer? _holdTimer; onLongPress assigns _holdTimer = Timer.periodic(…), onLongPressEnd cancels it, dispose() also cancels it — no more leaked timers
session_details_sheet.dart

Volume banner: settings.toDisplay(session.totalVolume) + 'Volume ${settings.unitLabel}'
Per-exercise total: settings.toDisplay(log.totalVolume) + settings.unitLabel
Per-set row: settings.toDisplay(set.weight) formatted + settings.unitLabel
targets_tab.dart

Added _isSubmitting bool; _submit guards against re-entry and wraps provider call in try/finally; GlowButton.onPressed is null while submitting
workout_flow_screen.dart

_toggleDropset: weight controller text now uses settings.toDisplay(_currentWeight) with proper decimal formatting (matching _loadLastSessionData)
_addDrop: new drop controller text also uses settings.toDisplay(newWeight)
WorkoutHeader now receives restSeconds: _restSeconds
workout_header.dart

Added optional restSeconds field (defaults to 90 for backwards compatibility); _OptionsMenu receives widget.restSeconds instead of the hardcoded literal
workout_summary_screen.dart

volStr built from settings.toDisplay(session.totalVolume); label updated to 'Volume (${settings.unitLabel})'

* feat: enhance UI components and improve data handling across screens

* feat: update ExerciseLibraryScreen tests to include SettingsProvider in widget setup

* feat: enhance workout session data collection by including weight in HealthConnectService

* feat: update Android build configuration, enhance analytics screen, and improve UI components

* feat: add muscle recovery and growth tracking features in WorkoutProvider and MLService

* feat: refactor NumberInputCard to StatefulWidget and enhance RFNavBar design

* feat: enhance ProfileScreen and ProfileSections with Google Fonts for improved typography

* feat: update Create button in ProgramsScreen to allow for non-full width display

* feat: Integrate Gemini AI features for personalized coaching and insights

- Added GeminiService for AI integration, including chat and program generation capabilities.
- Implemented GeminiContextBuilder to create context strings for AI prompts.
- Introduced _WeeklyInsightsCard to display weekly insights based on user workouts.
- Added AiSettingsSection in profile settings for managing Gemini API key and model selection.
- Updated home screen to include weekly insights and AI coach button.
- Enhanced ProgramsScreen with AI program generator button.
- Updated SettingsProvider to manage Gemini API key and insights storage.
- Added necessary dependencies for Google Generative AI.

* feat: enhance various screens and models with improved error handling, UI adjustments, and new features

* feat: improve error handling in program saving and adjust opacity calculation in body heatmap

* Add comprehensive tests for MLService, model serialization, PRManager, and WorkoutProvider

- Implement tests for MLService covering growth model training, set recommendations, default recommendations, target completion prediction, and muscle recovery score computation.
- Create model serialization tests for WorkoutSet, ExerciseLog, WorkoutSession, Exercise, MuscleGroup, Target, GrowthModel, Routine, PersonalRecord, TrainingProgram, and ProgramExerciseSlot to ensure data integrity during JSON serialization/deserialization.
- Introduce tests for PRManager to validate loading records, backfilling from sessions, checking and updating personal records, and retrieving records.
- Enhance WorkoutProvider tests to verify initialization, session and routine loading, active workout management, and exercise name retrieval.

* fix: ensure opacity calculation in heatmap drawing is explicitly a double

* chore: update version to 2.0.0+21 in pubspec.yaml

* feat: enhance UI responsiveness with layout adjustments and breakpoints across multiple screens

* feat: enhance UI layout and responsiveness with padding adjustments for floating action buttons and improved data representation

* feat: add advanced metrics toggle and display estimated 1RM badge in workout input section

* Add widget tests for AnalyticsScreen and ExerciseProgressView

- Implement comprehensive widget tests for the AnalyticsScreen, covering the Overview, Targets, and Records tabs.
- Validate functionality such as tab switching, data display, and interaction with UI elements.
- Add widget tests for ExerciseProgressView, including exercise picker, chart mode toggle, and set progression chart.
- Ensure tests cover empty states, search functionality, and interaction with various UI components.

* feat: update app label handling and improve navigation in home screen

* feat: Implement Gemini AI service for AI coach functionality (#49)

* feat: Implement Gemini AI service for AI coach functionality

- Added GeminiAiService to handle AI coach chat, program generation, and insights using Google Generative AI.
- Created IAiService interface to define the contract for AI services.
- Developed GeminiContextBuilder to construct context for AI interactions.
- Introduced ConversationManager to manage AI conversations, including persistence and active conversation logic.
- Implemented storage service methods for saving and retrieving AI conversations.
- Built AiCoachViewModel to orchestrate AI interactions and manage conversation state.
- Added unit tests for AiCoachViewModel, CoachToolService, and ConversationManager to ensure functionality and persistence.
- Updated analytics and exercise progress views to use the new GeminiAiService.

* feat: Enhance Gemini AI service with token usage tracking and UI updates for AI coach

* feat: Refactor AiCoachViewModel and related services for improved readability and error handling

* feat: Improve error handling and async behavior in Gemini AI service usage tracking

---------

Co-authored-by: Devasy Patel <110348311+Devasy23@users.noreply.github.com>

* feat: Add muscle volume trend chart and recent sessions section in MuscleDetailSheet

* docs: add conversational routine optimizer design spec

Replaces the one-shot optimizer sheet with a dedicated conversational
optimizer screen that reuses the coach streaming tool-loop, adds an
interactive ask_user_questions tool, gates on insufficient data, and
persists sessions to a separate optimizer inbox.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor: remove one-shot routine optimizer (replaced by conversational flow)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add Conversation.kind and AI question models (QuestionSpec, AnswerSpec, PendingQuestions)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: scope ConversationManager by kind ('coach' | 'optimizer')

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add ask_user_questions tool declaration and optimizer system prompt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add RFQuestionCard reusable widget (option chips + custom input)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: rewrite RoutineOptimizerViewModel as conversational streaming VM with ask_user_questions pausing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: handle aborted completer and await appendMessage in submitAnswers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add RoutineOptimizerScreen (conversational UI with question card + history inbox)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style: fix magic padding values and header button consistency in RoutineOptimizerScreen

* feat: add data gate (<3 sessions) and navigate to RoutineOptimizerScreen from routine card

* feat: add testBody method for RoutineOptimizerScreen to facilitate widget testing

* feat: add workout summary screen and enhance app theme colors (#50)

feat: add workout summary screen, health analytics, and production APK signing

- Workout summary screen with session details
- Sleep HR chart and debug log buffer  
- HealthHistoryManager for sleep/heart rate data
- Workout heart rate analysis and recovery metrics
- EC P-256 production keystore signing via GitHub Secrets
- Split-per-ABI APK builds for arm64, armeabi-v7a, x86_64
- FUTURE_IMPROVEMENTS.md documenting Options B & C (F-Droid, fastlane)

* test: update lastNightSleep test to sum all periods in the night window

* feat: bundle Geist fonts locally and add F-Droid metadata (Option B) (#51)

- Remove google_fonts dependency; replace with bundled variable font files
  (Geist-Variable.ttf + GeistMono-Variable.ttf from Vercel v1.7.2, MIT licensed)
- Replace all 377 GoogleFonts.geist/geistMono() calls with TextStyle(fontFamily:)
  across 31 dart files — no runtime Google CDN fetch, F-Droid build-compatible
- Declare fonts in pubspec.yaml flutter.fonts section
- Add fdroid/metadata/com.devasy.repforge.yml with anti-features (NonFreeNet)
  and auto-update config for F-Droid submission

Co-authored-by: Devasy Patel <110348311+Devasy23@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Devasy Patel <110348311+Devasy23@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants