Skip to content

feat: enhance exercise tracking with unilateral and double weight support#149

Merged
isotronic merged 15 commits into
masterfrom
exercise-flags
May 17, 2026
Merged

feat: enhance exercise tracking with unilateral and double weight support#149
isotronic merged 15 commits into
masterfrom
exercise-flags

Conversation

@isotronic
Copy link
Copy Markdown
Owner

@isotronic isotronic commented May 17, 2026

Summary by CodeRabbit

Release Notes

  • New Features
    • Three new stats settings now customise volume calculations: exclude warm-up sets, automatically double dumbbell weights for paired exercises, and double repetitions for single-arm/leg exercises.
    • Custom exercises can now be marked as unilateral or paired/double-weight to accurately reflect in stats.
    • Stats, progress metrics, and volume displays across the app now reflect these personalised calculation settings.

Review Change Stack

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Sorry @isotronic, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

Warning

Rate limit exceeded

@isotronic has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 58 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3bb295bd-54ef-4934-9c61-63dad6cee09e

📥 Commits

Reviewing files that changed from the base of the PR and between b8faf97 and 0f6da5b.

📒 Files selected for processing (3)
  • app/(app)/custom-exercise.tsx
  • constants/WhatsNew.ts
  • utils/database.ts
📝 Walkthrough

Walkthrough

This PR introduces two new per-exercise configuration flags—is_unilateral (for single-arm/leg exercises) and double_weight (for paired implements)—that users configure via the custom-exercise screen and toggle globally in settings. These flags control how volume and progression metrics are multiplied across stats displays, charts, and workout summaries.

Changes

Exercise Stats Doubling Flags and Volume Multipliers

Layer / File(s) Summary
Database schema, migration, and data syncing
utils/initUserDataDB.ts, utils/initAppDataDB.ts, utils/database.ts, app/_layout.tsx
Exercise table gains is_unilateral and double_weight columns via idempotent migration; default settings are bootstrapped with two new keys; app-to-user database synchronisation copies and reconciles these fields; new syncExerciseFlagsFromAppData() function backfills from bundled data when dataVersion is below 1.9.
Completed-workout and session queries extended with exercise flags
hooks/useCompletedWorkoutsQuery.ts
WorkoutResult, CompletedWorkout, and CompletedWorkoutRow shapes now carry is_unilateral and double_weight; SQL queries for fetching completed workouts, session history, and workout details select and populate these fields, defaulting nullish values to 0.
Custom-exercise screen UI and persistence
app/(app)/custom-exercise.tsx
Screen adds Switch component and state for the two flags; existing-exercise edits load them from the database; save logic extends both INSERT and UPDATE SQL to persist them as 1/0 values; new "Stats Options" UI section with toggles and layout styles.
Settings screen UI and state management
app/(app)/(tabs)/settings.tsx
Settings screen initialises, syncs, and toggles local state for countUnilateralDouble and doubleWeightForPaired; persists them via updateSetting; new "Stats" UI rows with explanatory switches allow users to enable/disable multiplier behaviours globally.
Parameterised progression-metric SQL builders
hooks/useTrackedExercisesQuery.ts, hooks/useExerciseDetailQuery.ts
New buildTrackedProgressionCase and progression builders parameterise SQL CASE expressions to scale weight and reps based on flags and exercise attributes; both hooks accept these flags in their signatures, include them in queryKeys, and pass them through to fetch functions.
Stats screen: volume aggregation and query wiring
app/(app)/(tabs)/(stats)/index.tsx, app/(app)/(tabs)/(stats)/exercise-detail.tsx
Stats screens read the doubling flags from settings and pass them into both tracked-exercise queries and computeStats; volume calculations apply multipliers for current and previous-period stats; VolumeBarChart receives the flags as props for per-range bucket aggregation.
WeeklySummaryCard: volume and progression metrics
components/WeeklySummaryCard.tsx, app/(app)/(tabs)/index.tsx
Props interface gains the two optional flags; getProgressionMetric accepts weight/rep multipliers; computeBestAchievement derives multipliers from flags and exercise attributes in both baseline and comparison paths; volume aggregation and memo dependencies are updated; HomeScreen threads the flags through.
VolumeBarChart: per-range volume aggregation
components/charts/VolumeBarChart.tsx
Props interface gains the two flags; groupVolumeByTime applies weight-doubling for paired exercises and rep-doubling for unilateral exercises during bucket aggregation; component destructuring and useMemo dependencies are updated.
Workout summary: volume calculation
app/(app)/(workout)/workout-summary.tsx
computeVolume gains two options and applies conditional weight/rep multipliers during accumulation; screen reads the flags and passes them into volume useMemo computations; countSets is reformatted without behavioural change.
Release notes
constants/WhatsNew.ts
New version 2620 entry describes the three new stats settings: warm-up exclusion, dumbbell weight doubling, and unilateral reps doubling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • isotronic/MuscleQuest#142: Both PRs extend components/WeeklySummaryCard.tsx volume and achievement calculations, with this PR adding unilateral/paired weight multipliers to the existing weekly summary card logic.
  • isotronic/MuscleQuest#139: Both PRs modify app/(app)/(workout)/workout-summary.tsx volume computation, with this PR extending the same screen's computeVolume function to support the new doubling flags.
  • isotronic/MuscleQuest#148: Both PRs modify stats and volume aggregation across overlapping components (VolumeBarChart, stats screens, queries), introducing similar multiplier-based customisation patterns.

Poem

A rabbit hops through databases new,
Flags for arms and dumbbells too—
Volume flows where multipliers lead,
Double this, double that, we're agreed!
Stats now know what lifters need. 🐰📊

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically summarizes the main feature: adding support for unilateral and double weight exercise tracking across the application.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
app/(app)/custom-exercise.tsx (1)

592-599: 💤 Low value

Consider using Switch from react-native-paper for visual consistency.

The settings screen and other parts of the app use Switch from react-native-paper, which has different default styling (and uses color prop instead of thumbColor). Using the same component would ensure consistent appearance across the app.

♻️ Suggested change

The Switch is already exported from react-native-paper in this file (line 13 imports from react-native-paper). Remove the Switch from the react-native import and use the paper version:

 import {
   ScrollView,
   TextInput,
   Alert,
   StyleSheet,
   KeyboardAvoidingView,
   Platform,
   Image,
   View,
-  Switch,
 } from "react-native";
-import { Button, Divider } from "react-native-paper";
+import { Button, Divider, Switch } from "react-native-paper";

Then update the Switch usage to use color instead of thumbColor:

 <Switch
   value={isUnilateral}
   onValueChange={(v: boolean) => {
     setIsUnilateral(v);
     markDirty();
   }}
-  thumbColor={Colors.dark.tint}
+  color={Colors.dark.tint}
 />

Also applies to: 611-618

🤖 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 `@app/`(app)/custom-exercise.tsx around lines 592 - 599, The current Switch
usage imports the RN core Switch and passes thumbColor (Switch used with
isUnilateral, setIsUnilateral, markDirty and Colors.dark.tint), but the file
already exports Switch from react-native-paper; remove the Switch import from
react-native and use the paper Switch instead, replacing the thumbColor prop
with the paper prop name color (e.g., color={Colors.dark.tint}); update the
other Switch instance(s) (the one around lines 611–618) likewise so all Switches
in this component use react-native-paper's Switch for consistent styling.
utils/database.ts (1)

226-229: ⚡ Quick win

Extend ExerciseCheckResult interface instead of using type assertions.

The ExerciseCheckResult interface (lines 120-129) is missing is_unilateral and double_weight fields, requiring unsafe type assertions here. Adding these fields to the interface ensures type safety.

Suggested fix

Add to the ExerciseCheckResult interface around line 129:

 interface ExerciseCheckResult {
   app_exercise_id: number | null;
   name: string;
   image: Uint8Array | null;
   description: string | null;
   animated_url: string | null;
   equipment: string | null;
   tracking_type: string | null;
   is_deleted: number;
+  is_unilateral: number | null;
+  double_weight: number | null;
 }

Then update the case statements:

                   case "is_unilateral":
-                    return row[col] !== (existingEntry as any).is_unilateral;
+                    return row[col] !== existingEntry.is_unilateral;
                   case "double_weight":
-                    return row[col] !== (existingEntry as any).double_weight;
+                    return row[col] !== existingEntry.double_weight;
🤖 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 `@utils/database.ts` around lines 226 - 229, The ExerciseCheckResult interface
is missing the is_unilateral and double_weight properties, forcing unsafe casts
in the comparison switch; add these fields to the ExerciseCheckResult interface
(e.g., is_unilateral?: boolean; double_weight?: boolean;) so the case branches
for "is_unilateral" and "double_weight" can use (existingEntry as
ExerciseCheckResult).is_unilateral / .double_weight safely (and remove the any
type assertions).
🤖 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 `@constants/WhatsNew.ts`:
- Line 159: The release note entry for version 2619 in constants/WhatsNew.ts
contains a stray diff artefact ("159~" / backtick marker) that leaked into the
displayed text; locate the version 2619 message (the string/array element in
WhatsNew.ts that contains "2619") and remove the stray diff markers (`159~`,
stray backticks or punctuation) so the string contains only the intended release
text, then ensure the surrounding syntax (closing quotes/backticks and trailing
comma) remains valid.
- Line 166: Change the American spelling "Customize" to British English
"Customise" in the string "Customize how your volume and stats are calculated
with three new options in Settings:" so it reads "Customise how your volume and
stats are calculated with three new options in Settings:"; locate and update
this literal in the WhatsNew content (the string shown in the diff) to maintain
en-GB consistency.

---

Nitpick comments:
In `@app/`(app)/custom-exercise.tsx:
- Around line 592-599: The current Switch usage imports the RN core Switch and
passes thumbColor (Switch used with isUnilateral, setIsUnilateral, markDirty and
Colors.dark.tint), but the file already exports Switch from react-native-paper;
remove the Switch import from react-native and use the paper Switch instead,
replacing the thumbColor prop with the paper prop name color (e.g.,
color={Colors.dark.tint}); update the other Switch instance(s) (the one around
lines 611–618) likewise so all Switches in this component use
react-native-paper's Switch for consistent styling.

In `@utils/database.ts`:
- Around line 226-229: The ExerciseCheckResult interface is missing the
is_unilateral and double_weight properties, forcing unsafe casts in the
comparison switch; add these fields to the ExerciseCheckResult interface (e.g.,
is_unilateral?: boolean; double_weight?: boolean;) so the case branches for
"is_unilateral" and "double_weight" can use (existingEntry as
ExerciseCheckResult).is_unilateral / .double_weight safely (and remove the any
type assertions).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ca112a2d-7f55-4b99-84ff-277a83268280

📥 Commits

Reviewing files that changed from the base of the PR and between 7a97228 and b8faf97.

📒 Files selected for processing (16)
  • app/(app)/(tabs)/(stats)/exercise-detail.tsx
  • app/(app)/(tabs)/(stats)/index.tsx
  • app/(app)/(tabs)/index.tsx
  • app/(app)/(tabs)/settings.tsx
  • app/(app)/(workout)/workout-summary.tsx
  • app/(app)/custom-exercise.tsx
  • app/_layout.tsx
  • components/WeeklySummaryCard.tsx
  • components/charts/VolumeBarChart.tsx
  • constants/WhatsNew.ts
  • hooks/useCompletedWorkoutsQuery.ts
  • hooks/useExerciseDetailQuery.ts
  • hooks/useTrackedExercisesQuery.ts
  • utils/database.ts
  • utils/initAppDataDB.ts
  • utils/initUserDataDB.ts

Comment thread constants/WhatsNew.ts
Comment thread constants/WhatsNew.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant