feat: implement supersets#136
Conversation
- Added superset handling in WorkoutSessionScreen to manage exercise transitions without rest. - Enhanced WorkoutCard to allow creation and removal of supersets for exercises. - Updated WorkoutDetailsScreen to visually indicate superset status for exercises. - Modified usePlanQuery to include supersetGroupId in workout records. - Extended activeWorkoutStore to manage superset logic during workout progression. - Implemented superset management in workoutStore for adding and removing exercises. - Updated database schema to support supersetGroupId in user_workout_exercises. - Ensured user data initialization includes supersetGroupId column if not present.
Reviewer's GuideImplements superset functionality across workout planning, execution, and persistence: exercises can be paired as supersets with shared set structures, visually grouped in the UI, reordered safely, and stored/loaded via a new superset_group_id field in the database and plan APIs. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds superset pairing: exercises can be grouped in pairs with synchronized sets. Changes touch data types, DB migration and persistence, store actions and mirroring, selection and list UIs, session progression, utilities for classification, and release metadata. ChangesSuperset Feature Implementation
Sequence DiagramsequenceDiagram
participant User
participant ExercisesScreen
participant WorkoutStore
participant Database
participant WorkoutSessionScreen
User->>ExercisesScreen: Open in superset mode (supersetForIndex)
User->>ExercisesScreen: Select partner exercise
ExercisesScreen->>WorkoutStore: createSuperset(workoutIndex, exerciseIndex, newExercise)
WorkoutStore->>WorkoutStore: generate supersetGroupId and insert partner
WorkoutStore->>Database: insert/update exercises with superset_group_id
Database-->>WorkoutStore: confirm persistence
ExercisesScreen->>User: navigate back
User->>WorkoutSessionScreen: complete set on first superset member
WorkoutSessionScreen->>WorkoutStore: nextSet(currentIndex)
WorkoutStore->>WorkoutStore: detect supersetGroupId -> switch to partner (no rest)
WorkoutStore-->>WorkoutSessionScreen: updated indices/values
User->>WorkoutSessionScreen: complete set on second member
WorkoutSessionScreen->>WorkoutStore: nextSet(partnerIndex)
WorkoutStore->>WorkoutStore: advance both exercises, compute carry-over
WorkoutStore-->>WorkoutSessionScreen: updated indices or finish workout
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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.
Hey - I've found 1 issue, and left some high level feedback:
- The superset partner detection logic (finding
partnerIndex,isFirstInSuperset,isSecondInSuperset) is duplicated across multiple components (WorkoutCard, WorkoutOverviewScreen, WorkoutDetailsScreen, WorkoutSession, etc.); consider extracting a shared helper to keep the behavior consistent and reduce future maintenance overhead. - Superset-specific UI elements (headers, connector bars, left borders, etc.) have very similar style definitions in multiple screens; pulling these into a shared component or style module would reduce duplication and make it easier to tweak the visual design in one place.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The superset partner detection logic (finding `partnerIndex`, `isFirstInSuperset`, `isSecondInSuperset`) is duplicated across multiple components (WorkoutCard, WorkoutOverviewScreen, WorkoutDetailsScreen, WorkoutSession, etc.); consider extracting a shared helper to keep the behavior consistent and reduce future maintenance overhead.
- Superset-specific UI elements (headers, connector bars, left borders, etc.) have very similar style definitions in multiple screens; pulling these into a shared component or style module would reduce duplication and make it easier to tweak the visual design in one place.
## Individual Comments
### Comment 1
<location path="components/WorkoutCard.tsx" line_range="158" />
<code_context>
const isLoading = loadingExerciseIndex === index;
+
+ const { supersetGroupId } = exercise;
+ const partnerIndex = supersetGroupId
+ ? workout.exercises.findIndex(
+ (e, i) => i !== index && e.supersetGroupId === supersetGroupId,
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the superset partner and reordering logic into shared pure helper functions to simplify the component and centralize the rules.
You can reduce the added complexity by extracting the superset logic into small, pure helpers and reusing them in both `renderExerciseItem` and `handleOrderChange`. That keeps the component code as orchestration and makes the index juggling easier to test and reason about.
### 1. Extract superset helpers
Create a small set of utilities (ideally in a shared file used by WorkoutOverview/WorkoutDetails too) that centralize the “partner” and “superset adjacency” logic:
```ts
// supersetUtils.ts (or nearby file)
export function findSupersetPartnerIndex(
exercises: UserExercise[],
index: number,
): number {
const supersetGroupId = exercises[index]?.supersetGroupId;
if (!supersetGroupId) return -1;
return exercises.findIndex(
(e, i) => i !== index && e.supersetGroupId === supersetGroupId,
);
}
export function classifySupersetPosition(
exercises: UserExercise[],
index: number,
): {
isInSuperset: boolean;
partnerIndex: number;
isFirstInSuperset: boolean;
isSecondInSuperset: boolean;
} {
const partnerIndex = findSupersetPartnerIndex(exercises, index);
const isInSuperset = partnerIndex !== -1;
return {
isInSuperset,
partnerIndex,
isFirstInSuperset: isInSuperset && index < partnerIndex,
isSecondInSuperset: isInSuperset && index > partnerIndex,
};
}
```
Then `renderExerciseItem` becomes simpler and avoids duplicating the partner-finding logic:
```ts
const renderExerciseItem: SortableGridRenderItem<UserExercise> = useCallback(
({ item, index: exerciseIndex }) => {
// ...
const {
isInSuperset,
partnerIndex,
isFirstInSuperset,
isSecondInSuperset,
} = classifySupersetPosition(workout.exercises, exerciseIndex);
// use isInSuperset, isFirstInSuperset, isSecondInSuperset as before
},
[
handleCreateSuperset,
handleRemoveSuperset,
handleReplace,
menuVisible,
removeExercise,
workout.exercises,
workoutIndex,
],
);
```
This removes the inline `findIndex` plus boolean derivation, and gives a single place to update if superset rules change.
### 2. Extract superset-aware reordering
You can also pull the core “drag superset / don’t break superset” logic out of `handleOrderChange` into a pure helper that operates on arrays:
```ts
// supersetUtils.ts (continued)
export function reorderWithSupersetRules(
exercises: UserExercise[],
fromIndex: number,
toIndex: number,
): UserExercise[] {
if (fromIndex === toIndex) return exercises;
const updated = [...exercises];
const draggedExercise = updated[fromIndex];
const supersetGroupId = draggedExercise.supersetGroupId;
// Generic move
const [movedItem] = updated.splice(fromIndex, 1);
updated.splice(toIndex, 0, movedItem);
if (supersetGroupId) {
const originalPartnerIndex = findSupersetPartnerIndex(exercises, fromIndex);
const isDraggingFirst =
originalPartnerIndex !== -1 && fromIndex < originalPartnerIndex;
const draggedNewIdx = updated.findIndex(
(e) => e.exercise_id === draggedExercise.exercise_id,
);
const partnerNewIdx = updated.findIndex(
(e) =>
e.exercise_id !== draggedExercise.exercise_id &&
e.supersetGroupId === supersetGroupId,
);
if (partnerNewIdx !== -1 && Math.abs(draggedNewIdx - partnerNewIdx) !== 1) {
const [partner] = updated.splice(partnerNewIdx, 1);
const newDraggedIdx = updated.findIndex(
(e) => e.exercise_id === draggedExercise.exercise_id,
);
updated.splice(
isDraggingFirst ? newDraggedIdx + 1 : newDraggedIdx,
0,
partner,
);
}
} else {
const landedIdx = updated.findIndex(
(e) => e.exercise_id === draggedExercise.exercise_id,
);
const prevItem = landedIdx > 0 ? updated[landedIdx - 1] : null;
const nextItem =
landedIdx < updated.length - 1 ? updated[landedIdx + 1] : null;
if (
prevItem?.supersetGroupId &&
nextItem?.supersetGroupId &&
prevItem.supersetGroupId === nextItem.supersetGroupId
) {
updated.splice(landedIdx, 1);
const secondPartnerIdx = updated.findIndex(
(e) => e.exercise_id === nextItem.exercise_id,
);
updated.splice(secondPartnerIdx + 1, 0, draggedExercise);
}
}
return updated;
}
```
Now `handleOrderChange` becomes a thin wrapper:
```ts
const handleOrderChange = useCallback(
({ fromIndex, toIndex }: { fromIndex: number; toIndex: number }) => {
const updatedExercises = reorderWithSupersetRules(
workout.exercises,
fromIndex,
toIndex,
);
const updatedWorkouts = workouts.map((w, i) =>
i === index ? { ...w, exercises: updatedExercises } : w,
);
useWorkoutStore.setState({ workouts: updatedWorkouts });
},
[workout.exercises, workouts, index],
);
```
This preserves your current behavior but:
- Removes index arithmetic and `findIndex` branching from the component.
- Centralizes superset invariants in one place.
- Makes the core superset behaviors unit-testable in isolation (`reorderWithSupersetRules`, `classifySupersetPosition`, `findSupersetPartnerIndex`), which should ease future changes.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
store/workoutStore.ts (2)
231-265:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
addSetToExercisemirror copies the partner's last set, which can drift from the target set.When mirroring to the partner you copy
partner.sets[partner.sets.length - 1]rather thannewSet. If the user has just edited the target's latest set (different rest, warmup flag, reps range, etc.), the partner ends up with the partner's previous template instead of matchingnewSet. That's almost certainly not what the user expects from a "synchronised" superset.Consider mirroring
newSetitself (or at minimum its rest/warmup metadata) to the partner so additions stay symmetric:♻️ Suggested change
- // Mirror to superset partner: copy partner's last set as template + // Mirror to superset partner using the same set definition if (partner && exercise.exercise_id === partner.exercise_id) { - const lastSet = exercise.sets[exercise.sets.length - 1] ?? newSet; - return { ...exercise, sets: [...exercise.sets, { ...lastSet }] }; + return { ...exercise, sets: [...exercise.sets, { ...newSet }] }; }🤖 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 `@store/workoutStore.ts` around lines 231 - 265, In addSetToExercise, the superset partner is currently appended with the partner's previous lastSet which can diverge from the newly added set; instead, when partner exists, append a shallow copy of newSet (or at minimum copy its rest/warmup/metadata fields) to the partner's sets so the partner stays synchronized; update the mapping inside addSetToExercise to use newSet (e.g., {...newSet} or a minimal metadata object copied from newSet) when exercise.exercise_id === partner.exercise_id rather than using the partner's last set.
300-351:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftSuperset partners can drift out of sync when initial set counts differ.
createSupersetinsertsnewExercisewith its ownsetsarray (fromdefaultSets/defaultTimeSetsin the picker) while keeping the existing exercise's sets untouched. Once paired,addSetToExerciseappends one set to each partner andremoveSetFromExerciseremoves the samesetIndexfrom both — both of which only stay coherent if the two arrays start out the same length.If the original exercise already has more sets than the default (e.g. a user added extra sets before creating the superset), the partner stays one or more sets short forever, and
removeSetFromExercisewill silently no‑op on the partner wheneversetIndex >= partner.sets.length. The active session'snextSetlogic instore/activeWorkoutStore.ts(lines 286-316) also assumes both exercises sharenextSetIndex < currentExercise.sets.length, so divergence will cause one partner's set to be skipped or the superset to "finish" prematurely.Suggest aligning set counts at superset creation time so both partners always agree. Mirroring the target exercise's
setsonto the new partner (preserving the partner'stracking_type-appropriate defaults for time vs reps) is a low‑risk fix:♻️ Suggested fix in
createSupersetcreateSuperset: (workoutIndex, exerciseIndex, newExercise) => set((state) => { const groupId = Crypto.randomUUID(); const updatedWorkouts = state.workouts.map((w, i) => { if (i !== workoutIndex) return w; + const target = w.exercises[exerciseIndex]; + if (!target) return w; const exercises = [...w.exercises]; exercises[exerciseIndex] = { - ...exercises[exerciseIndex], + ...target, supersetGroupId: groupId, }; + // Align partner's set count to the target so additions/removals stay paired. + const alignedSets = target.sets.map(() => + newExercise.sets[0] + ? { ...newExercise.sets[0] } + : ({} as (typeof newExercise.sets)[number]), + ); exercises.splice(exerciseIndex + 1, 0, { ...newExercise, + sets: alignedSets, supersetGroupId: groupId, }); return { ...w, exercises }; }); return { workouts: updatedWorkouts }; }),Also applies to: 352-369
🤖 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 `@store/workoutStore.ts` around lines 300 - 351, removeSetFromExercise can leave superset partners out of sync because createSuperset inserts a newExercise with its own default sets, so instruct createSuperset to align the partner's sets length to the target exercise's sets length: when creating the superset (createSuperset), compute the target exercise's number of sets and fill the new partner's sets by generating that many entries using the partner's tracking_type with defaultSets or defaultTimeSets as appropriate (preserve each generated set shape but not copy values), and also harden removeSetFromExercise and addSetToExercise to guard against out‑of‑range indices by checking setIndex < exercise.sets.length before filtering/mirroring (use symbols supersetGroupId, partner, removeSetFromExercise, addSetToExercise, defaultSets, defaultTimeSets, createSuperset).
🧹 Nitpick comments (1)
store/activeWorkoutStore.ts (1)
205-259: ⚡ Quick winNice extraction of
buildNextSetValues.Pulling the warmup/drop-set + historical-carryover computation into a single helper keeps the superset branch readable and avoids duplicating the tracking-type fan-out. Consider following up by routing the non-superset path (lines 375-426) through the same helper to remove the remaining duplicate.
🤖 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 `@store/activeWorkoutStore.ts` around lines 205 - 259, The non-superset branch still duplicates the warmup/drop-set and historical carryover logic—refactor it to call the extracted helper buildNextSetValues instead of reimplementing the same logic: locate the non-superset code that currently computes weight/reps/time for a next set (the block around the other branch that uses weightAndReps, previousWorkoutData, and formatFromTotalSeconds) and replace its inline computation with a call to buildNextSetValues(exerciseIndex, exercise, fromSetIndex, toSetIndex) (or appropriate indices used there), then use the returned weight/reps/time values; ensure the helper’s dependencies (exTrackingType, weightAndReps, previousWorkoutData, formatFromTotalSeconds, workout.exercises) are in scope or passed through if needed.
🤖 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 `@app/`(app)/(workout)/index.tsx:
- Around line 490-512: The tap handler currently allows starting a superset by
tapping the "B" row which triggers the second-in-superset path; change the
TouchableOpacity onPress to guard using the computed isSecondInSuperset and
short-circuit (no-op) when true so taps on the B row do nothing. Locate the
supersetGroupId/partnerIndex/isFirstInSuperset/isSecondInSuperset logic and
update the onPress that calls handleExercisePress(index) to only call it when
isSecondInSuperset is false (i.e., allow tap when not the second member of a
superset).
In `@app/`(app)/(workout)/workout-session.tsx:
- Around line 394-397: When jumping directly to the superset partner in the
isFirstInSuperset branch, first cancel any active rest countdown/notification
before calling nextSet(); call the existing rest cancellation helper (e.g.,
cancelRestCountdown / clearRestTimer / stopRestTimer or the function that clears
the rest timeout and notification) and guard it (check existence) so the timer
and any scheduled notification are cleared, then call nextSet(); if no helper
exists, add a small helper that clears the timeout/notification and invoke it
here to prevent stale timers from firing during the partner exercise.
In `@components/WorkoutCard.tsx`:
- Around line 305-350: The current move-fix only reattaches the moved exercise’s
own superset partner, but can leave some other superset pair split; after
performing the existing reordering (the blocks that use updatedExercises,
draggedExercise, supersetGroupId, isDraggingFirst), run a final pass that scans
updatedExercises for any supersetGroupId groups with exactly two members and
ensures they are adjacent: for each such group, find both indices, and if
Math.abs(idxA-idxB) !== 1 remove the later element and splice it immediately
next to the earlier one (preserving their original relative order or the
same-side rule used above); this guarantees no unrelated superset pair remains
split after a drag.
In `@store/activeWorkoutStore.ts`:
- Around line 286-316: The second-in-superset branch only builds next-set values
for the partner (firstIndex) and never populates weightAndReps for the active
exercise (currentExerciseIndex); call buildNextSetValues for the active exercise
as well (e.g., compute secondNextValues =
buildNextSetValues(currentExerciseIndex, currentExercise, currentSetIndex,
nextSetIndex)) and merge it into updatedWeightAndReps alongside the partner's
entry (preserving existing weightAndReps entries) so both indices ([firstIndex]
and [currentExerciseIndex]) have [nextSetIndex] populated; keep
updatedSetIndices and the return shape the same.
- Around line 309-316: The superset completion check only compares nextSetIndex
to currentExercise.sets.length, which can mis-handle supersets when paired
exercises have differing sets; update the logic in the block using
nextSetIndex/currentExercise/updatedCompletedSets (and related
updatedSetIndices/updatedWeightAndReps) so it either (preferably) ensures sets
are aligned at superset creation in createSuperset (workoutStore.ts) or
defensively computes the superset length here (e.g., use the max of both partner
exercises' sets lengths) and compare nextSetIndex against that, and also
guard/clamp accesses into updatedCompletedSets and other per-exercise arrays so
no index goes out of bounds when partners differ. Ensure references:
nextSetIndex, currentExercise, updatedCompletedSets, updatedSetIndices,
updatedWeightAndReps, and createSuperset.
---
Outside diff comments:
In `@store/workoutStore.ts`:
- Around line 231-265: In addSetToExercise, the superset partner is currently
appended with the partner's previous lastSet which can diverge from the newly
added set; instead, when partner exists, append a shallow copy of newSet (or at
minimum copy its rest/warmup/metadata fields) to the partner's sets so the
partner stays synchronized; update the mapping inside addSetToExercise to use
newSet (e.g., {...newSet} or a minimal metadata object copied from newSet) when
exercise.exercise_id === partner.exercise_id rather than using the partner's
last set.
- Around line 300-351: removeSetFromExercise can leave superset partners out of
sync because createSuperset inserts a newExercise with its own default sets, so
instruct createSuperset to align the partner's sets length to the target
exercise's sets length: when creating the superset (createSuperset), compute the
target exercise's number of sets and fill the new partner's sets by generating
that many entries using the partner's tracking_type with defaultSets or
defaultTimeSets as appropriate (preserve each generated set shape but not copy
values), and also harden removeSetFromExercise and addSetToExercise to guard
against out‑of‑range indices by checking setIndex < exercise.sets.length before
filtering/mirroring (use symbols supersetGroupId, partner,
removeSetFromExercise, addSetToExercise, defaultSets, defaultTimeSets,
createSuperset).
---
Nitpick comments:
In `@store/activeWorkoutStore.ts`:
- Around line 205-259: The non-superset branch still duplicates the
warmup/drop-set and historical carryover logic—refactor it to call the extracted
helper buildNextSetValues instead of reimplementing the same logic: locate the
non-superset code that currently computes weight/reps/time for a next set (the
block around the other branch that uses weightAndReps, previousWorkoutData, and
formatFromTotalSeconds) and replace its inline computation with a call to
buildNextSetValues(exerciseIndex, exercise, fromSetIndex, toSetIndex) (or
appropriate indices used there), then use the returned weight/reps/time values;
ensure the helper’s dependencies (exTrackingType, weightAndReps,
previousWorkoutData, formatFromTotalSeconds, workout.exercises) are in scope or
passed through if needed.
🪄 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: f2b045f1-60b4-45bf-8bd4-cc888ccaa349
📒 Files selected for processing (14)
app.config.jsapp/(app)/(create-plan)/exercises.tsxapp/(app)/(create-plan)/sets-overview.tsxapp/(app)/(workout)/index.tsxapp/(app)/(workout)/workout-session.tsxcomponents/WorkoutCard.tsxcomponents/WorkoutDetailsScreen.tsxconstants/WhatsNew.tshooks/usePlanQuery.tsstore/activeWorkoutStore.tsstore/workoutStore.tsutils/database.tsutils/initAppCheck.tsutils/initUserDataDB.ts
…th production build
…ght and reps update
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@sourcery-ai review |
|
Sorry @isotronic, you have reached your weekly rate limit of 500000 diff characters. Please try again later or upgrade to continue using Sourcery |
1 similar comment
|
Sorry @isotronic, you have reached your weekly rate limit of 500000 diff characters. Please try again later or upgrade to continue using Sourcery |
…nd position classification
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@utils/supersetUtils.ts`:
- Around line 67-92: reorderWithSupersetRules relies on finding rows by
exercise_id via multiple findIndex calls (draggedNewIdx, partnerNewIdx,
landedIdx), which breaks if exercise_id duplicates exist; fix by either
enforcing uniqueness in the store action addExercise (validate and reject or
dedupe incoming exercises) or switch to stable per-row identifiers (capture and
pass the source index/toIndex or attach a UUID per row and use that for lookups
and Sortable.Grid keyExtractor). Also optimize the common case by using the
provided toIndex (use toIndex as draggedNewIdx) instead of calling findIndex
again in reorderWithSupersetRules and replace other findIndex usages with
index-based lookups or UUID-based lookups to avoid silent wrong-row operations.
🪄 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: d1fdbb4a-42ab-4dfd-b854-a141d2d5b06b
📒 Files selected for processing (6)
app/(app)/(workout)/index.tsxapp/(app)/(workout)/workout-session.tsxcomponents/WorkoutCard.tsxcomponents/WorkoutDetailsScreen.tsxstore/activeWorkoutStore.tsutils/supersetUtils.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- app/(app)/(workout)/workout-session.tsx
- app/(app)/(workout)/index.tsx
- components/WorkoutDetailsScreen.tsx
- store/activeWorkoutStore.ts
…ctive plan queries and updating workout card rendering
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
hooks/useActivePlanQuery.ts (1)
79-79: 💤 Low valueConsider simplifying the nullish coalescing.
The
?? undefinedoperator is redundant sincenullandundefinedalready evaluate toundefinedby default. You can simplify this to justrow.superset_group_id.♻️ Simplified version
- supersetGroupId: row.superset_group_id ?? undefined, + supersetGroupId: row.superset_group_id,🤖 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 `@hooks/useActivePlanQuery.ts` at line 79, The object property assignment uses a redundant nullish coalescing; replace "supersetGroupId: row.superset_group_id ?? undefined" with a direct assignment "supersetGroupId: row.superset_group_id" in the code that builds the result (look for supersetGroupId and row.superset_group_id in useActivePlanQuery.ts) so the value is passed through unchanged without the unnecessary "?? undefined".app/(app)/(workout)/index.tsx (1)
495-502: ⚡ Quick winConsider using a conditional wrapper for better accessibility.
Setting
onPresstoundefineddisables the press handler, butTouchableOpacitystill renders with touchable visual feedback (opacity changes, etc.), which may confuse users when they tap the "B" exercise card. For better accessibility and user experience, consider conditionally rendering eitherTouchableOpacityor a plainViewbased onisSecondInSuperset.♻️ Improved approach
- <TouchableOpacity - onPress={ - isSecondInSuperset - ? undefined - : () => { - handleExercisePress(index); - } - } - > + {isSecondInSuperset ? ( + <View> + {/* card content */} + </View> + ) : ( + <TouchableOpacity + onPress={() => { + handleExercisePress(index); + }} + > + {/* card content */} + </TouchableOpacity> + )}Note: This would require extracting the card content to avoid duplication, or wrapping the entire
Viewconditionally.🤖 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)/(workout)/index.tsx around lines 495 - 502, The TouchableOpacity currently sets onPress to undefined when isSecondInSuperset, which still renders touch feedback and can confuse users; replace the conditional onPress with a conditional wrapper: render TouchableOpacity with onPress={() => handleExercisePress(index)} when !isSecondInSuperset, and render a non-interactive View (or Pressable with accessible={false}) with the exact same child content when isSecondInSuperset. Extract the card JSX into a small subcomponent or variable (e.g., ExerciseCardContent) and use it inside either <TouchableOpacity> or <View> so you avoid duplicating markup while preserving accessibility and visuals.
🤖 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 `@utils/supersetUtils.ts`:
- Around line 7-17: The findSupersetPartnerIndex function assumes each
supersetGroupId maps to exactly two exercises but has no documentation or
runtime checks; update the code by adding a clear JSDoc above
findSupersetPartnerIndex that states the cardinality constraint (each
supersetGroupId should appear exactly twice and the function returns the other
index or -1), and add validation where supersetGroupId values are created or
loaded (or within a new helper invoked by data load) to assert/throw if a
supersetGroupId appears fewer or more than 2 times—reference the function name
findSupersetPartnerIndex and the supersetGroupId field when placing these checks
so future maintainers can find and enforce the constraint.
---
Nitpick comments:
In `@app/`(app)/(workout)/index.tsx:
- Around line 495-502: The TouchableOpacity currently sets onPress to undefined
when isSecondInSuperset, which still renders touch feedback and can confuse
users; replace the conditional onPress with a conditional wrapper: render
TouchableOpacity with onPress={() => handleExercisePress(index)} when
!isSecondInSuperset, and render a non-interactive View (or Pressable with
accessible={false}) with the exact same child content when isSecondInSuperset.
Extract the card JSX into a small subcomponent or variable (e.g.,
ExerciseCardContent) and use it inside either <TouchableOpacity> or <View> so
you avoid duplicating markup while preserving accessibility and visuals.
In `@hooks/useActivePlanQuery.ts`:
- Line 79: The object property assignment uses a redundant nullish coalescing;
replace "supersetGroupId: row.superset_group_id ?? undefined" with a direct
assignment "supersetGroupId: row.superset_group_id" in the code that builds the
result (look for supersetGroupId and row.superset_group_id in
useActivePlanQuery.ts) so the value is passed through unchanged without the
unnecessary "?? undefined".
🪄 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: 1a439c25-0624-4d35-929e-785d365da134
📒 Files selected for processing (6)
app/(app)/(workout)/index.tsxcomponents/WorkoutCard.tsxcomponents/WorkoutDetailsScreen.tsxhooks/useActivePlanQuery.tshooks/useAllPlansQuery.tsutils/supersetUtils.ts
✅ Files skipped from review due to trivial changes (1)
- hooks/useAllPlansQuery.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- components/WorkoutDetailsScreen.tsx
…ng in workout overview
Summary by Sourcery
Add support for creating, editing, and performing supersets across workouts, including persistence and UI indicators.
New Features:
Enhancements:
Build:
Documentation:
Summary by CodeRabbit
New Features
Chores