Add CLAUDE.md with comprehensive codebase documentation - #32
Conversation
Documents project structure, architecture patterns (SOLID), data models, development commands, testing conventions, CI/CD pipeline, theme system, and key conventions for AI assistants working on the codebase. https://claude.ai/code/session_01NytaxeQoLadhKcESsKqjmT
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Adds a complete multi-week training program feature inspired by the 12-week periodisation reference plan (phases, deload weeks, tempo notation, rep ranges, rest times, superset grouping). Models (models.dart): - ProgramExerciseSlot — per-exercise parameters (sets, rep range, rest seconds, tempo, weight %, notes, superset group) - ProgramDay — named training day with ordered exercise slots - ProgramWeek — week with deload flag, intensity factor, set reduction - TrainingPhase — named phase spanning a range of weeks - TrainingProgram — top-level entity; full JSON serialization Storage: - New 'training_programs' Hive box in StorageService - saveTrainingProgram / getAllTrainingPrograms / getTrainingProgram / deleteTrainingProgram added to IStorageService interface and implemented in StorageService + MockStorageService ProgramManager (SRP manager): - loadPrograms, saveProgram, createProgram, deleteProgram - importFromJson (assigns new UUID, marks isImported=true) - exportToJson (pretty-printed) WorkoutProvider: - Exposes programManager; loadPrograms() called during init UI (screens/programs/): - ProgramsScreen — list with mini phase timeline, import FAB - ProgramDetailScreen — phase timeline bar, expandable week list with deload badges, per-day exercise breakdown (sets×reps, rest, tempo chip, weight %, superset bracket, notes) - ProgramDesignerScreen — 3-step wizard: metadata/phases → week structure (deload toggle, intensity/set-reduction steppers, add days) → exercise slots per day with full parameter editing RoutinesScreen: - Added Programs tab (TabBar + TabBarView) alongside existing Routines tab; no existing functionality changed https://claude.ai/code/session_01E15CGTZZDU8rz7PX1nYXA3
example_12_week_program.json (docs/):
- 12 weeks · 3 phases (Foundation W1–4, Intensify W5–8, Peak W9–12)
- 5 training days/week: Push (Mon), Core (Wed), Pull (Thu),
Shoulders & Arms (Fri), Legs + Rehab (Sat)
- 344 exercise slots across 60 days (~98 KB)
- Deload weeks 5 (85% / −1 set) and 9 (90% / −1 set)
- Supersets encoded with supersetGroupId per phase:
ss-push-a (tricep pushdown + lateral raise, W5+)
ss-pull-a (preacher curl + hammer curl, W5+)
ss-shld-a (lateral raise + rear delt fly, W5+)
ss-arm-a (bicep curl + tricep pushdown, W5+)
- All exercise IDs match built-in ExerciseDatabase entries
- Tempo notation on all compound lifts (e.g. "3-1-1")
ImportProgramScreen (screens/programs/import_program_screen.dart):
- Full-screen page replaces the cramped 8-line bottom sheet
- Expands text area fills available viewport height (maxLines: null)
- Live char / line / KB counter in status bar
- Two-step flow: Validate → Import (button disabled until valid)
- Inline field-level error messages (missing key, wrong type)
- Green border + "Valid JSON" indicator on success
- Red border + inline error on failure
- Clear (×) button in AppBar
ProgramsScreen:
- _showImportDialog / _doImport / _friendlyError removed
- Replaced with _openImport → Navigator.push to ImportProgramScreen
- Success snackbar shown on return with result == true
https://claude.ai/code/session_01E15CGTZZDU8rz7PX1nYXA3
file_picker (already in pubspec) is now wired into the import screen: - Folder icon in AppBar opens native file picker filtered to .json - Selected file content is read and loaded into the text field - Validation / Import flow unchanged after loading Instructions banner updated to mention both input methods. https://claude.ai/code/session_01E15CGTZZDU8rz7PX1nYXA3
- WorkoutFlowScreen: add programDay/programWeek optional params so any day in a program can be started directly from the detail screen - Slot-based rest time: rest seconds are pulled from ProgramExerciseSlot and applied automatically when completing each set - Superset auto-advance: completing a set advances immediately to the next exercise (no rest timer) when both share the same supersetGroupId - Program meta banner: shown above the SET DONE button during program-mode workouts; displays target sets×rep-range, rest duration, tempo, 1RM %, deload indicator, superset label, and slot notes - Pull-up/chin-up assist mode: weight input label changes to 'Assist kg (0=BW)' for pull_ups and chin_ups exercise IDs - ProgramDetailScreen: add 'Start <DayName>' ElevatedButton at the bottom of each expanded day section; navigates to WorkoutFlowScreen with the correct programDay and programWeek context https://claude.ai/code/session_01E15CGTZZDU8rz7PX1nYXA3
Supersets now follow the correct E1→E2 (no rest)→rest→E1→E2 cycle:
- Add WorkoutProvider.goToExercise(index) for direct index navigation
- Track _supersetReturnIndex: set when completing the last exercise in a
superset group if the group still has unfinished sets
- _skipRest() now navigates back to the superset group start when
_supersetReturnIndex is set, restoring the correct exercise and rest time
- Add _supersetGroupStart() to scan backward and find the group's first index
- Add _supersetNeedsMoreSets() to check deload-adjusted target sets vs logged
Flow: set1(E1) → advance to E2 → set1(E2) → rest → return to E1 →
set2(E1) → advance to E2 → set2(E2) → rest → continue to E3
https://claude.ai/code/session_01E15CGTZZDU8rz7PX1nYXA3
… dropsets, alongside program design and detail screens.
…xerciseScreen, along with project configuration in pubspec.yaml.
…ercises, and workout flow, supported by new screens and tests.
- Add SettingsProvider for weight unit (kg/lbs) and configurable weight increments (1.25/2.5/5/10 kg or 2.5/5/10/25 lbs), persisted via IStorageService settings key-value store - Add Preferences section to Settings screen with unit toggle and increment chip selector - Show estimated 1RM (Epley formula) per set in workout flow screen and as a "Best 1RM" card in analytics exercise tab - Apply selected unit/increment throughout workout flow (labels, step values, display conversion) and analytics session history - Fix numeric keyboard in weight/reps fields: use numberWithOptions instead of TextInputType.number; add isScrollControlled + FocusNode + viewInsets padding to the number picker bottom sheet so keyboard doesn't overlap content https://claude.ai/code/session_01GhMv58Dda7sCxNJYznGxn9
Consolidates all user-facing settings into a dedicated Profile tab: - Profile header with RepForge logo, app name, and version (v1.0.12) - Preferences section: kg/lbs unit toggle + configurable weight increment chips (persisted via SettingsProvider) - Data Management section: local export/import and cloud backup actions, migrated from standalone SettingsScreen - Cloud Sync section: MongoDB connection string placeholder with "Coming Soon" badge — ready to wire up in a future update - About section: version, created by Devasy Patel, platform, package - Settings gear icon in home dashboard header now navigates directly to the Profile tab instead of pushing a separate route https://claude.ai/code/session_01GhMv58Dda7sCxNJYznGxn9
WalkthroughAdds a comprehensive training program management system including new domain models (TrainingProgram, TrainingPhase, ProgramWeek, ProgramDay, ProgramExerciseSlot), a ProgramManager service for CRUD operations, UI screens for designing/importing/viewing programs, a SettingsProvider for weight unit preferences, a ProfileScreen for user preferences and data management, GitHub Actions CI workflow, and Flutter expertise documentation. Changes
Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 27
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/screens/workout_flow_screen.dart (2)
589-591:⚠️ Potential issue | 🟠 MajorSeed the dropset text fields in the display unit, not storage kg.
_currentWeightandnewWeightare stored in kg, but these controller values are written straight into fields labeled withsettings.unitLabel. In lbs mode the user sees the wrong number, and any edit then gets converted again viatoStorage(). Initialize the controllers withsettings.toDisplay(...)here.Also applies to: 800-802
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@workout-logger/lib/screens/workout_flow_screen.dart` around lines 589 - 591, The controllers are being seeded with storage-kg values so users in lbs see incorrect numbers; update the seeding to use the display-converted values by calling settings.toDisplay(...) for weight before assigning to _mainWeightController.text (and likewise use settings.toDisplay(...) wherever _currentWeight or newWeight are written into UI controllers, e.g., the other occurrences around lines 800-802); keep _mainRepsController.text as-is for reps, and ensure you convert only the weight value to display units rather than writing the raw storage kg.
403-405:⚠️ Potential issue | 🟡 MinorRecommendation text is still pinned to kg.
The rest of this screen is unit-aware now, but this card always renders
rec.weightaskg. In lbs mode the workout flow mixes units within the same exercise, which is easy to misread. UseSettingsProvider.formatWeight(rec.weight)for display and keep the stored value in kg internally.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@workout-logger/lib/screens/workout_flow_screen.dart` around lines 403 - 405, The Text widget rendering the weight uses a hardcoded "kg" string ('${rec.weight}kg × ${rec.reps} reps') which breaks unit-awareness; replace the literal with the unit-aware formatter (use SettingsProvider.formatWeight(rec.weight) × ${rec.reps} reps) so the UI shows the correct units while keeping rec.weight stored in kg internally, and remove the hardcoded "kg" suffix. Locate this in workout_flow_screen.dart where the Text is built for the record and swap to the formatter so lbs mode displays correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/skills/flutter-expert/SKILL.md:
- Around line 196-208: Add a blank line before the fenced code block that begins
with "```json" under the "Progress tracking:" heading so the JSON block is
separated by an empty line (per MD031); locate the "Progress tracking:"
paragraph and insert a single empty line immediately before the ```json fence to
surround the code block with blank lines.
- Around line 133-142: Add a blank line before the fenced code block that begins
with the JSON payload under the "Flutter context query:" heading so the code
block is separated from the preceding text (fix the MarkdownLint MD031
violation); locate the fenced block starting with ```json followed by the JSON
object containing "requesting_agent": "flutter-expert" and insert a single empty
line immediately above the opening ```json fence.
- Line 287: Add a single trailing newline character to the end of the SKILL.md
file (the file content that ends with "Always prioritize native performance,
beautiful UI, and consistent experience while building Flutter applications that
delight users across all platforms.") so the file terminates with a single
newline to satisfy markdownlint MD047.
In @.github/workflows/test.yml:
- Around line 35-45: The current Analyze step uses tail -n 1 on
analyze_output.txt which can miss the real summary if the last line is blank or
the summary isn’t the final line; change the summary extraction to search
analyze_output.txt for the actual flutter analyze summary pattern (e.g., lines
containing "issues" or "No issues found") instead of using tail, assign that
match to SUMMARY (falling back to the last non-empty line if no pattern match),
and keep writing SUMMARY to $GITHUB_STEP_SUMMARY so the Analyze job and the
flutter analyze invocation remain unchanged.
In `@workout-logger/lib/main.dart`:
- Around line 66-69: ProgramManager is being provided twice which creates
ambiguity: remove the duplicate or document intended usage—either delete the
direct ChangeNotifierProvider<ProgramManager>.value(value: _programManager) and
rely on WorkoutProvider.programManager as the single source of truth, or keep
the direct provider and add a comment clarifying that ProgramsScreen (and its
ListenableBuilder) should access ProgramManager via the direct
ChangeNotifierProvider rather than WorkoutProvider.programManager; update any
imports/usages (ProgramsScreen, ListenableBuilder) to match the chosen pattern
and ensure only one provider supplies ProgramManager at runtime.
In `@workout-logger/lib/screens/analytics_screen.dart`:
- Around line 788-818: The Builder is redundantly watching SettingsProvider
inside _buildSessionHistory; instead accept SettingsProvider as a parameter
(e.g., add a settings parameter to _buildSessionHistory and pass the existing
settings from _ExerciseProgressView) and remove the internal context.watch call
and Builder widget; update calls to _buildSessionHistory to pass the settings
instance so the widget tree uses the already-obtained SettingsProvider
consistently.
In `@workout-logger/lib/screens/home_screen.dart`:
- Around line 161-165: The code currently reaches into the parent using
findAncestorStateOfType<_HomeScreenState>() and sets homeState._currentIndex
directly which breaks encapsulation and is fragile; add a public method on
_HomeScreenState (e.g., navigateToTab(int index)) that wraps setState(() =>
_currentIndex = index) and then replace the ancestor lookup + direct mutation
with a safe callback or provider/inherited access that calls navigateToTab(4)
instead of touching _currentIndex or relying on findAncestorStateOfType.
In `@workout-logger/lib/screens/profile_screen.dart`:
- Around line 61-124: The _importFromFile method touches state and context after
multiple awaits; add mounted guards immediately after each async boundary (after
await showDialog(...), after await FilePicker.platform.pickFiles(...), and after
await provider.importData(jsonString)) to return early if (!mounted) before
calling setState, context.read/using provider, or _showSnack; ensure every
setState or context.read in _importFromFile is preceded by a mounted check so no
state/context is accessed on a disposed widget.
In `@workout-logger/lib/screens/programs/import_program_screen.dart`:
- Around line 267-285: In _pickFile(), avoid calling setState or mutating _ctrl
after async awaits when the State may be disposed: after each await (the
FilePicker.platform.pickFiles call and the File(...).readAsString call) check if
the State is still mounted and return early if not; specifically guard before
assigning to _ctrl.text and before calling setState to update
_validationState/_validationError/_parsed so you never call setState on a
disposed object.
- Around line 316-327: The current validation only checks top-level keys and
weeks[0] with _requireField, which can miss malformed later weeks/days; instead,
after the lightweight checks run the decoded Map through the same parser used
during import (e.g., TrainingProgram.fromMap or the ProgramManager.parse/import
routine) to fully validate structure before showing "Valid JSON" or calling
_import(); replace or augment the final validation step to call the real parser
(TrainingProgram / ProgramManager methods) with the decoded value and surface
any parser errors to the user so validation and _import() use the identical code
path.
In `@workout-logger/lib/screens/programs/program_designer_screen.dart`:
- Around line 506-780: The functions _buildDayExerciseEditor, _buildSlotEditor,
_showSlotDialog, and _updateDayExercises are using 3+ positional parameters
which makes calls error-prone; change their signatures to use named parameters
(e.g. replace int weekIdx, int dayIdx, int slotIdx, ProgramExerciseSlot slot
with {required int weekIdx, required int dayIdx, required int slotIdx, required
ProgramExerciseSlot slot}) and update every call site (_buildDayExerciseEditor
calls, places calling _buildSlotEditor, calls to _showSlotDialog from
_addExerciseSlot/_editSlot, and where _updateDayExercises is invoked) to pass
arguments by name; preserve existing defaults/nullable types (use required where
previously non-null) and keep logic unchanged.
- Around line 621-750: The dialog never exposes or mutates supersetGroupId so
new ProgramExerciseSlot instances are always null for supersetGroupId; add a
local state variable (e.g., int? supersetGroupId = existing?.supersetGroupId)
inside the dialog's builder and render a small control (toggle, dropdown, or
chip list) below the exercise picker to choose/create a superset group (source
options from the enclosing Program's superset groups or a simple "New group"
incrementer), update that state with setDlg when the user picks/creates a group,
and include supersetGroupId when constructing the ProgramExerciseSlot in the
ElevatedButton onPressed so edits and new slots persist superset membership;
ensure the control preserves existing?.supersetGroupId when editing and allows
clearing the group.
In `@workout-logger/lib/screens/programs/program_detail_screen.dart`:
- Around line 234-241: The fraction calculation can divide by zero when
_program.totalWeeks is 0; update the code in the mapping over _program.phases to
guard the denominator: compute a safeDenominator = (_program.totalWeeks == 0) ?
1 : _program.totalWeeks (or set fraction = 0.0 when totalWeeks == 0) and then
calculate fraction = (phase.endWeek - phase.startWeek + 1) / safeDenominator
before computing flex; keep the existing clamp to guarantee a minimum flex and
reference the variables _program.totalWeeks, _program.phases, fraction and the
Expanded(flex: ...) expression when making the change.
- Around line 600-605: The TextStyle instance used in program_detail_screen.dart
is not declared as const even though all its fields (fontSize, color,
fontWeight, letterSpacing) are compile-time constants; update the TextStyle(...)
expression to const TextStyle(...) so the style becomes immutable and allows
compile-time const folding (locate the TextStyle creation in the widget tree
where the style: property is set and change it to const TextStyle(...)).
In `@workout-logger/lib/screens/programs/programs_screen.dart`:
- Around line 278-279: The computation of fraction uses (phase.endWeek -
phase.startWeek + 1) / program.totalWeeks and can divide by zero if
program.totalWeeks == 0; update the code around fraction to guard against
zero/negative totalWeeks (e.g., if program.totalWeeks <= 0 set fraction = 0.0)
and optionally clamp the result to a valid range (0.0..1.0) so that fraction is
always a finite double; adjust any UI logic that consumes fraction accordingly.
- Around line 29-53: The ProgramsScreen currently returns a full Scaffold which
nests inside the parent screen’s Scaffold and causes FAB conflicts; remove the
inner Scaffold and return only the body content (use the result of
programs.isEmpty ? _buildEmptyState(context) : _buildList(context, programs)) so
_buildEmptyState and _buildList render the page content without a Scaffold, and
move the FAB widgets (those created with FloatingActionButton.small and
FloatingActionButton.extended that call _openImport and _openDesigner) up to the
parent screen so the parent composes and coordinates FABs for both tabs; ensure
any heroTag usage and callbacks (_openImport, _openDesigner) remain accessible
to the parent (e.g., expose callbacks or pass context) when refactoring.
- Around line 23-27: The code redundantly calls context.read<WorkoutProvider>()
twice inside the ListenableBuilder; cache the provider once instead: in the
builder, assign final workoutProvider = context.read<WorkoutProvider>() and then
use workoutProvider.programManager for the listenable and
workoutProvider.programManager.programs for the programs variable to improve
clarity and avoid duplicate reads (referencing ListenableBuilder,
WorkoutProvider, programManager, and programs).
In `@workout-logger/lib/screens/routines_screen.dart`:
- Around line 16-39: The children of the TabBarView (_RoutinesTab and
ProgramsScreen) currently contain their own Scaffold instances which causes
nested scaffolds and layout/FAB/back-button issues; remove the inner Scaffold
wrappers from _RoutinesTab and from ProgramsScreen so they return plain widgets
(e.g., Container, Column, ListView or CustomScrollView/SliverToBoxAdapter) and
move any floatingActionButton or top AppBar behavior into the parent
RoutinesScreen's Scaffold (or implement a Stack at RoutinesScreen to position a
shared FAB), ensuring AppBar/TabBar remain in RoutinesScreen and navigation/back
handling is provided by the single outer Scaffold.
In `@workout-logger/lib/screens/settings_screen.dart`:
- Around line 548-550: Remove the accidental blank line between the onPressed
and style properties in the ElevatedButton widget: collapse the empty line so
the onPressed: _isBackingUp ? null : _performBackup, and style:
ElevatedButton.styleFrom( appear consecutively; locate the ElevatedButton
construction that references _isBackingUp and _performBackup and delete the
stray newline to restore correct formatting.
In `@workout-logger/lib/screens/workout_flow_screen.dart`:
- Around line 147-154: The prefilled weight and number picker currently format
displayWeight with a hardcoded 1 decimal; instead derive the decimal precision
from the weight increment configured in SettingsProvider (use the increment used
by settings.toDisplay / settings.weightIncrement) and use that computed
decimalPlaces when formatting displayWeight and wiring the number picker so
values like 21.25 round-trip exactly; update the formatting in the block that
sets _currentWeight/_currentReps and sets _mainWeightController.text (and the
similar code around the 444-450 region) to compute decimalPlaces = max(0,
-log10(increment)) or otherwise derive digits from the increment and call
toStringAsFixed(decimalPlaces) and configure the number picker to use the same
decimalPlaces.
In `@workout-logger/lib/services/managers/program_manager.dart`:
- Around line 77-89: The importFromJson method currently casts
jsonDecode(jsonString) to Map<String, dynamic> which will throw a TypeError for
non-object JSON; instead check the decoded value returned from jsonDecode and if
it's not a Map<String, dynamic> throw a FormatException with a clear message;
after verifying the type, assign raw['id'], raw['isImported'], raw['createdAt']
as before, construct the TrainingProgram via TrainingProgram.fromJson(raw), and
then call saveProgram(program); reference the importFromJson function, the raw
variable, jsonDecode call, TrainingProgram.fromJson, saveProgram, and _uuid when
making this change.
- Around line 99-105: The getProgramById function currently uses a try/catch
around _programs.firstWhere(...) to return null on miss; replace this with the
collection package's firstWhereOrNull for clarity: import the collection
extension and change the body of getProgramById to return
_programs.firstWhereOrNull((p) => p.id == id) (removing the try/catch). If you
prefer to keep consistency with WorkoutProvider.getExercise, you may leave the
try/catch, but the preferred refactor is to use firstWhereOrNull in
getProgramById.
In `@workout-logger/lib/services/settings_provider.dart`:
- Around line 32-37: The setter setWeightUnit currently resets and persists
changes even when the selected unit equals the current _weightUnit; modify
setWeightUnit to return immediately if the incoming unit equals the existing
_weightUnit (i.e., add a guard if (unit == _weightUnit) return;) so it does not
reset _weightIncrement to _defaultIncrement, call _storage.saveSetting, or
notifyListeners on no-op taps; keep the rest of the logic (updating _weightUnit,
resetting _weightIncrement, persisting via _storage.saveSetting('weightUnit'...)
and 'weightIncrement', and notifyListeners()) unchanged for real changes.
- Around line 58-68: The current formatWeight(double kg) always uses one decimal
which rounds values like 21.25 to 21.3; change formatWeight to choose decimal
precision based on the current availableIncrements so supported increments are
represented exactly: compute d = toDisplay(kg), determine requiredDecimals by
inspecting availableIncrements (e.g., if any increment has hundredths (.25/.75)
use 2 decimals, else if any has tenths (.5) use 1, otherwise 0), then format d
with toStringAsFixed(requiredDecimals) and return '$str $unitLabel'; update
formatWeight (and use availableIncrements) so displayed values exactly match
supported increments.
In `@workout-logger/lib/services/storage_service.dart`:
- Around line 451-481: The export/import flow is missing training programs;
update exportAllData() to include a "trainingPrograms" key whose value is the
list returned by getAllTrainingPrograms() converted to JSON maps (use
program.toJson()), and update importData() to read the "trainingPrograms" array
from the imported payload and persist each entry by calling
saveTrainingProgram(TrainingProgram.fromJson(...)) (ensure you handle existing
IDs/overwrites consistently with other entities). Reference the TrainingProgram
CRUD helpers saveTrainingProgram, getAllTrainingPrograms, getTrainingProgram,
and deleteTrainingProgram when locating where to add serialization and
deserialization logic.
In `@workout-logger/pubspec.yaml`:
- Line 23: The Flutter SDK constraint is pinned exactly as "flutter: 3.41.5"
which blocks compatible upgrades; update the pubspec.yaml Flutter constraint
(the line with flutter: 3.41.5) to an inclusive version range such as "flutter:
^3.41.0" or "flutter: >=3.41.0 <4.0.0" so minor/patch updates are allowed while
preserving compatibility with the Dart SDK constraint.
In `@workout-logger/test/test_utils/mock_storage_service.dart`:
- Line 22: Add a public getter to expose the private list _trainingPrograms for
test assertions, matching the pattern used for
customExercises/sessions/routines/targets; implement a getter named
trainingPrograms that returns _trainingPrograms so tests can access the
collection and maintain consistency with the other getters in
mock_storage_service.dart.
---
Outside diff comments:
In `@workout-logger/lib/screens/workout_flow_screen.dart`:
- Around line 589-591: The controllers are being seeded with storage-kg values
so users in lbs see incorrect numbers; update the seeding to use the
display-converted values by calling settings.toDisplay(...) for weight before
assigning to _mainWeightController.text (and likewise use
settings.toDisplay(...) wherever _currentWeight or newWeight are written into UI
controllers, e.g., the other occurrences around lines 800-802); keep
_mainRepsController.text as-is for reps, and ensure you convert only the weight
value to display units rather than writing the raw storage kg.
- Around line 403-405: The Text widget rendering the weight uses a hardcoded
"kg" string ('${rec.weight}kg × ${rec.reps} reps') which breaks unit-awareness;
replace the literal with the unit-aware formatter (use
SettingsProvider.formatWeight(rec.weight) × ${rec.reps} reps) so the UI shows
the correct units while keeping rec.weight stored in kg internally, and remove
the hardcoded "kg" suffix. Locate this in workout_flow_screen.dart where the
Text is built for the record and swap to the formatter so lbs mode displays
correctly.
🪄 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: 893a3de5-c7e3-4369-bbdb-ec1bbb1e7169
📒 Files selected for processing (27)
.github/skills/flutter-expert/SKILL.md.github/workflows/test.ymldocs/example_12_week_program.jsonworkout-logger/lib/main.dartworkout-logger/lib/models/models.dartworkout-logger/lib/screens/analytics_screen.dartworkout-logger/lib/screens/home_screen.dartworkout-logger/lib/screens/profile_screen.dartworkout-logger/lib/screens/programs/import_program_screen.dartworkout-logger/lib/screens/programs/program_designer_screen.dartworkout-logger/lib/screens/programs/program_detail_screen.dartworkout-logger/lib/screens/programs/programs_screen.dartworkout-logger/lib/screens/routines_screen.dartworkout-logger/lib/screens/settings_screen.dartworkout-logger/lib/screens/workout_flow_screen.dartworkout-logger/lib/services/interfaces/storage_service_interface.dartworkout-logger/lib/services/managers/managers.dartworkout-logger/lib/services/managers/program_manager.dartworkout-logger/lib/services/settings_provider.dartworkout-logger/lib/services/storage_service.dartworkout-logger/lib/services/workout_provider.dartworkout-logger/pubspec.yamlworkout-logger/test/add_custom_exercise_screen_test.dartworkout-logger/test/exercise_library_screen_test.dartworkout-logger/test/program_manager_test.dartworkout-logger/test/test_utils/mock_storage_service.dartworkout-logger/test/workout_provider_test.dart
| Flutter context query: | ||
| ```json | ||
| { | ||
| "requesting_agent": "flutter-expert", | ||
| "request_type": "get_flutter_context", | ||
| "payload": { | ||
| "query": "Flutter context needed: target platforms, app type, state management preference, native features required, and deployment strategy." | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Add blank line before fenced code block.
Per markdownlint MD031, fenced code blocks should be surrounded by blank lines for proper rendering across all Markdown parsers.
📝 Proposed fix
Flutter context query:
+
```json
{
"requesting_agent": "flutter-expert",🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 134-134: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/skills/flutter-expert/SKILL.md around lines 133 - 142, Add a blank
line before the fenced code block that begins with the JSON payload under the
"Flutter context query:" heading so the code block is separated from the
preceding text (fix the MarkdownLint MD031 violation); locate the fenced block
starting with ```json followed by the JSON object containing "requesting_agent":
"flutter-expert" and insert a single empty line immediately above the opening
```json fence.
| Progress tracking: | ||
| ```json | ||
| { | ||
| "agent": "flutter-expert", | ||
| "status": "implementing", | ||
| "progress": { | ||
| "screens_completed": 32, | ||
| "custom_widgets": 45, | ||
| "test_coverage": "82%", | ||
| "performance_score": "60fps" | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Add blank line before fenced code block.
Per markdownlint MD031, fenced code blocks should be surrounded by blank lines.
📝 Proposed fix
Progress tracking:
+
```json
{
"agent": "flutter-expert",🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 197-197: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/skills/flutter-expert/SKILL.md around lines 196 - 208, Add a blank
line before the fenced code block that begins with "```json" under the "Progress
tracking:" heading so the JSON block is separated by an empty line (per MD031);
locate the "Progress tracking:" paragraph and insert a single empty line
immediately before the ```json fence to surround the code block with blank
lines.
| - Partner with backend-developer on API integration | ||
| - Coordinate with ios-developer on iOS specifics | ||
|
|
||
| Always prioritize native performance, beautiful UI, and consistent experience while building Flutter applications that delight users across all platforms. No newline at end of file |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Add trailing newline at end of file.
Per markdownlint MD047, files should end with a single newline character. This is a minor formatting fix.
📝 Proposed fix
Always prioritize native performance, beautiful UI, and consistent experience while building Flutter applications that delight users across all platforms.
+📝 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.
| Always prioritize native performance, beautiful UI, and consistent experience while building Flutter applications that delight users across all platforms. | |
| Always prioritize native performance, beautiful UI, and consistent experience while building Flutter applications that delight users across all platforms. | |
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 287-287: Files should end with a single newline character
(MD047, single-trailing-newline)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/skills/flutter-expert/SKILL.md at line 287, Add a single trailing
newline character to the end of the SKILL.md file (the file content that ends
with "Always prioritize native performance, beautiful UI, and consistent
experience while building Flutter applications that delight users across all
platforms.") so the file terminates with a single newline to satisfy
markdownlint MD047.
| - name: Analyze | ||
| working-directory: ./workout-logger | ||
| run: | | ||
| # Only fail on errors, ignore warnings and info messages | ||
| flutter analyze --no-fatal-infos --no-fatal-warnings | tee analyze_output.txt | ||
|
|
||
| # Extract and display the summary of issues | ||
| SUMMARY=$(tail -n 1 analyze_output.txt) | ||
| echo "Analysis Summary: $SUMMARY" | ||
| echo "### Analysis Summary" >> $GITHUB_STEP_SUMMARY | ||
| echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
tail -n 1 may not capture the actual summary line.
The flutter analyze output format may vary. If there are trailing blank lines or the summary isn't on the last line, tail -n 1 will capture incorrect content. Consider using grep to find the actual summary pattern.
🔧 More robust summary extraction
- name: Analyze
working-directory: ./workout-logger
run: |
# Only fail on errors, ignore warnings and info messages
flutter analyze --no-fatal-infos --no-fatal-warnings | tee analyze_output.txt
- # Extract and display the summary of issues
- SUMMARY=$(tail -n 1 analyze_output.txt)
+ # Extract the summary line (e.g., "No issues found!" or "X issues found")
+ SUMMARY=$(grep -E "(No issues found|[0-9]+ issues? found)" analyze_output.txt | tail -n 1 || echo "Analysis completed")
echo "Analysis Summary: $SUMMARY"
echo "### Analysis Summary" >> $GITHUB_STEP_SUMMARY
echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY📝 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.
| - name: Analyze | |
| working-directory: ./workout-logger | |
| run: | | |
| # Only fail on errors, ignore warnings and info messages | |
| flutter analyze --no-fatal-infos --no-fatal-warnings | tee analyze_output.txt | |
| # Extract and display the summary of issues | |
| SUMMARY=$(tail -n 1 analyze_output.txt) | |
| echo "Analysis Summary: $SUMMARY" | |
| echo "### Analysis Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY | |
| - name: Analyze | |
| working-directory: ./workout-logger | |
| run: | | |
| # Only fail on errors, ignore warnings and info messages | |
| flutter analyze --no-fatal-infos --no-fatal-warnings | tee analyze_output.txt | |
| # Extract the summary line (e.g., "No issues found!" or "X issues found") | |
| SUMMARY=$(grep -E "(No issues found|[0-9]+ issues? found)" analyze_output.txt | tail -n 1 || echo "Analysis completed") | |
| echo "Analysis Summary: $SUMMARY" | |
| echo "### Analysis Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/test.yml around lines 35 - 45, The current Analyze step
uses tail -n 1 on analyze_output.txt which can miss the real summary if the last
line is blank or the summary isn’t the final line; change the summary extraction
to search analyze_output.txt for the actual flutter analyze summary pattern
(e.g., lines containing "issues" or "No issues found") instead of using tail,
assign that match to SUMMARY (falling back to the last non-empty line if no
pattern match), and keep writing SUMMARY to $GITHUB_STEP_SUMMARY so the Analyze
job and the flutter analyze invocation remain unchanged.
| // ProgramManager passed to tree directly | ||
| ChangeNotifierProvider<ProgramManager>.value(value: _programManager), | ||
| // SettingsProvider for user preferences (weight unit, increments) | ||
| ChangeNotifierProvider<SettingsProvider>.value(value: _settingsProvider), |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
ProgramManager is provided twice—clarify intended access pattern.
ProgramManager is exposed both:
- Directly via
ChangeNotifierProvider<ProgramManager>.value(line 67) - Indirectly via
WorkoutProvider.programManager(line 75)
This could confuse developers about which to use. If direct access is intended (e.g., for ListenableBuilder in ProgramsScreen), document this. Otherwise, consider removing the direct provider if all access should go through WorkoutProvider.
Also applies to: 71-76
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@workout-logger/lib/main.dart` around lines 66 - 69, ProgramManager is being
provided twice which creates ambiguity: remove the duplicate or document
intended usage—either delete the direct
ChangeNotifierProvider<ProgramManager>.value(value: _programManager) and rely on
WorkoutProvider.programManager as the single source of truth, or keep the direct
provider and add a comment clarifying that ProgramsScreen (and its
ListenableBuilder) should access ProgramManager via the direct
ChangeNotifierProvider rather than WorkoutProvider.programManager; update any
imports/usages (ProgramsScreen, ListenableBuilder) to match the chosen pattern
and ensure only one provider supplies ProgramManager at runtime.
| Future<void> setWeightUnit(WeightUnit unit) async { | ||
| _weightUnit = unit; | ||
| _weightIncrement = _defaultIncrement; | ||
| await _storage.saveSetting('weightUnit', unit == WeightUnit.kg ? 'kg' : 'lbs'); | ||
| await _storage.saveSetting('weightIncrement', _weightIncrement.toString()); | ||
| notifyListeners(); |
There was a problem hiding this comment.
Ignore no-op unit selections.
workout-logger/lib/screens/profile_screen.dart calls this setter on every tap, including taps on the already-selected chip. Right now that silently resets a custom increment back to the default and overwrites storage, so a stray tap on kg will discard a 1.25 kg preference.
Suggested fix
Future<void> setWeightUnit(WeightUnit unit) async {
+ if (unit == _weightUnit) return;
_weightUnit = unit;
_weightIncrement = _defaultIncrement;
await _storage.saveSetting('weightUnit', unit == WeightUnit.kg ? 'kg' : 'lbs');
await _storage.saveSetting('weightIncrement', _weightIncrement.toString());
notifyListeners();
}📝 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.
| Future<void> setWeightUnit(WeightUnit unit) async { | |
| _weightUnit = unit; | |
| _weightIncrement = _defaultIncrement; | |
| await _storage.saveSetting('weightUnit', unit == WeightUnit.kg ? 'kg' : 'lbs'); | |
| await _storage.saveSetting('weightIncrement', _weightIncrement.toString()); | |
| notifyListeners(); | |
| Future<void> setWeightUnit(WeightUnit unit) async { | |
| if (unit == _weightUnit) return; | |
| _weightUnit = unit; | |
| _weightIncrement = _defaultIncrement; | |
| await _storage.saveSetting('weightUnit', unit == WeightUnit.kg ? 'kg' : 'lbs'); | |
| await _storage.saveSetting('weightIncrement', _weightIncrement.toString()); | |
| notifyListeners(); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@workout-logger/lib/services/settings_provider.dart` around lines 32 - 37, The
setter setWeightUnit currently resets and persists changes even when the
selected unit equals the current _weightUnit; modify setWeightUnit to return
immediately if the incoming unit equals the existing _weightUnit (i.e., add a
guard if (unit == _weightUnit) return;) so it does not reset _weightIncrement to
_defaultIncrement, call _storage.saveSetting, or notifyListeners on no-op taps;
keep the rest of the logic (updating _weightUnit, resetting _weightIncrement,
persisting via _storage.saveSetting('weightUnit'...) and 'weightIncrement', and
notifyListeners()) unchanged for real changes.
| /// Format a kg weight value with the correct unit label. | ||
| String formatWeight(double kg) { | ||
| final d = toDisplay(kg); | ||
| final str = d == d.truncateToDouble() ? d.toStringAsFixed(0) : d.toStringAsFixed(1); | ||
| return '$str $unitLabel'; | ||
| } | ||
|
|
||
| /// Available weight increments for the current unit. | ||
| List<double> get availableIncrements => _weightUnit == WeightUnit.kg | ||
| ? [1.25, 2.5, 5.0, 10.0] | ||
| : [2.5, 5.0, 10.0, 25.0]; |
There was a problem hiding this comment.
formatWeight() rounds away supported increments.
This provider exposes 1.25 kg increments, but every non-integer weight is formatted with a single decimal. Values like 21.25 therefore render as 21.3, which will leak into analytics and set history.
Suggested fix
String formatWeight(double kg) {
final d = toDisplay(kg);
- final str = d == d.truncateToDouble() ? d.toStringAsFixed(0) : d.toStringAsFixed(1);
+ final str = d
+ .toStringAsFixed(2)
+ .replaceFirst(RegExp(r'\.?0+$'), '');
return '$str $unitLabel';
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@workout-logger/lib/services/settings_provider.dart` around lines 58 - 68, The
current formatWeight(double kg) always uses one decimal which rounds values like
21.25 to 21.3; change formatWeight to choose decimal precision based on the
current availableIncrements so supported increments are represented exactly:
compute d = toDisplay(kg), determine requiredDecimals by inspecting
availableIncrements (e.g., if any increment has hundredths (.25/.75) use 2
decimals, else if any has tenths (.5) use 1, otherwise 0), then format d with
toStringAsFixed(requiredDecimals) and return '$str $unitLabel'; update
formatWeight (and use availableIncrements) so displayed values exactly match
supported increments.
| // ==================== TRAINING PROGRAMS ==================== | ||
|
|
||
| @override | ||
| Future<void> saveTrainingProgram(TrainingProgram program) async { | ||
| await _trainingProgramsBoxInstance.put( | ||
| program.id, | ||
| jsonEncode(program.toJson()), | ||
| ); | ||
| } | ||
|
|
||
| @override | ||
| Future<List<TrainingProgram>> getAllTrainingPrograms() async { | ||
| final programs = <TrainingProgram>[]; | ||
| for (final json in _trainingProgramsBoxInstance.values) { | ||
| programs.add(TrainingProgram.fromJson(jsonDecode(json))); | ||
| } | ||
| programs.sort((a, b) => b.createdAt.compareTo(a.createdAt)); | ||
| return programs; | ||
| } | ||
|
|
||
| @override | ||
| Future<TrainingProgram?> getTrainingProgram(String id) async { | ||
| final json = _trainingProgramsBoxInstance.get(id); | ||
| if (json == null) return null; | ||
| return TrainingProgram.fromJson(jsonDecode(json)); | ||
| } | ||
|
|
||
| @override | ||
| Future<void> deleteTrainingProgram(String id) async { | ||
| await _trainingProgramsBoxInstance.delete(id); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify if training programs are mentioned anywhere in export/import context
rg -n "trainingProgram|training_program" workout-logger/lib/services/storage_service.dart | grep -i "export\|import" || echo "No export/import references found for training programs"Repository: Devasy/Workout-logger
Length of output: 120
🏁 Script executed:
sed -n '332,363p' workout-logger/lib/services/storage_service.dartRepository: Devasy/Workout-logger
Length of output: 1159
🏁 Script executed:
sed -n '366,449p' workout-logger/lib/services/storage_service.dartRepository: Devasy/Workout-logger
Length of output: 2942
🏁 Script executed:
# Check if there are any TODOs or comments about training programs in the file
rg -n "TODO|FIXME|training.?program" workout-logger/lib/services/storage_service.dart -A 2 -B 2Repository: Devasy/Workout-logger
Length of output: 292
🏁 Script executed:
rg -n "TODO|FIXME|training.?program|trainingProgram" workout-logger/lib/services/storage_service.dart -B 3 -A 3Repository: Devasy/Workout-logger
Length of output: 1983
🏁 Script executed:
# Check if training programs are referenced/imported in other parts of the codebase
rg -n "trainingProgram|training_program" workout-logger/lib --type dart | head -20Repository: Devasy/Workout-logger
Length of output: 902
Add training programs to export/import methods.
Training programs have complete CRUD methods but are missing from exportAllData() and importData(). Users will lose their training programs when exporting and re-importing data. Include trainingPrograms in the export/import flow alongside the other entities (sessions, routines, targets, muscle groups, custom exercises).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@workout-logger/lib/services/storage_service.dart` around lines 451 - 481, The
export/import flow is missing training programs; update exportAllData() to
include a "trainingPrograms" key whose value is the list returned by
getAllTrainingPrograms() converted to JSON maps (use program.toJson()), and
update importData() to read the "trainingPrograms" array from the imported
payload and persist each entry by calling
saveTrainingProgram(TrainingProgram.fromJson(...)) (ensure you handle existing
IDs/overwrites consistently with other entities). Reference the TrainingProgram
CRUD helpers saveTrainingProgram, getAllTrainingPrograms, getTrainingProgram,
and deleteTrainingProgram when locating where to add serialization and
deserialization logic.
|
|
||
| environment: | ||
| sdk: ^3.9.2 | ||
| flutter: 3.41.5 |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the latest stable version of Flutter SDK as of April 2026?
💡 Result:
The latest stable version of the Flutter SDK as of April 3, 2026, is 3.41.5.
Citations:
- 1: https://docs.flutter.dev/install/archive
- 2: https://flutter.dev/docs/development/tools/sdk/releases
- 3: https://flutter.dev/docs/development/tools/sdk/release-notes
- 4: https://docs.flutter.dev/release/release-notes
- 5: https://docs.flutter.dev/development/tools/sdk/release-notes
- 6: http://docs.flutter.dev/release
- 7: https://docs.flutter.dev/install
- 8: https://docs.flutter.dev/development/tools/sdk/upgrading
- 9: https://docs.flutter.dev/development/tools/sdk/releases
- 10: https://flutter.googlesource.com/mirrors/flutter.git/+/0373dbee2e6afe42cbb100d252c65c1f9e6f4c19/CHANGELOG.md
- 11: [stable] [hotfix] Flutter Release Version 3.41.5 flutter/flutter#183740
Consider using a version range for Flutter SDK constraint instead of exact pinning.
The Flutter SDK is pinned to exact version 3.41.5, which prevents developers from using compatible newer versions. This is inconsistent with the Dart SDK constraint on line 22 (^3.9.2), which allows compatible versions.
Consider using flutter: ^3.41.0 or flutter: ">=3.41.0 <4.0.0" to allow patch and minor version updates while maintaining consistency with the Dart SDK constraint style.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@workout-logger/pubspec.yaml` at line 23, The Flutter SDK constraint is pinned
exactly as "flutter: 3.41.5" which blocks compatible upgrades; update the
pubspec.yaml Flutter constraint (the line with flutter: 3.41.5) to an inclusive
version range such as "flutter: ^3.41.0" or "flutter: >=3.41.0 <4.0.0" so
minor/patch updates are allowed while preserving compatibility with the Dart SDK
constraint.
| final List<Target> _targets = []; | ||
| final List<MuscleGroup> _muscleGroups = []; | ||
| final Map<String, String> _settings = {}; | ||
| final List<TrainingProgram> _trainingPrograms = []; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider adding a public getter for test assertions.
Other collections (customExercises, sessions, routines, targets) have public getters (lines 28-31) for test assertions. Adding one for _trainingPrograms would maintain consistency.
📝 Proposed addition after line 31
List<TrainingProgram> get trainingPrograms => _trainingPrograms;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@workout-logger/test/test_utils/mock_storage_service.dart` at line 22, Add a
public getter to expose the private list _trainingPrograms for test assertions,
matching the pattern used for customExercises/sessions/routines/targets;
implement a getter named trainingPrograms that returns _trainingPrograms so
tests can access the collection and maintain consistency with the other getters
in mock_storage_service.dart.
Documents project structure, architecture patterns (SOLID), data models,
development commands, testing conventions, CI/CD pipeline, theme system,
and key conventions for AI assistants working on the codebase.
https://claude.ai/code/session_01NytaxeQoLadhKcESsKqjmT
Summary by CodeRabbit
Release Notes
New Features
Tests