Skip to content

Commit 5efd98c

Browse files
tpcmurrayclaude
andcommitted
fix(workout): count skipped-status exercises with logged sets as real efforts
In the workout-complete summary, an exercise whose status is 'skipped' but which has logged sets is now treated as a real (partial) effort: its progression is computed and its sets are shown, instead of being struck through and labelled "skipped". Only genuinely empty skipped exercises are counted/shown as skipped. Stats are computed on read, so this also corrects already-completed workouts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent dc47f07 commit 5efd98c

2 files changed

Lines changed: 48 additions & 7 deletions

File tree

server/src/__tests__/integration/workouts.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,45 @@ describe('PUT /api/workouts/:id/complete', () => {
211211
expect(mockClient.release).toHaveBeenCalled();
212212
});
213213

214+
it('counts a skipped-status exercise that has logged sets as a real effort', async () => {
215+
const mockClient = createMockClient([
216+
{}, // BEGIN
217+
{ rows: [{ id: WID, completed_at: null }] }, // Verify session
218+
{}, // Mark pending as skipped
219+
{}, // Set completed_at
220+
{}, // COMMIT
221+
// buildCompletionStats: workout
222+
{ rows: [{ id: WID, started_at: '2026-01-25T14:00:00Z', completed_at: '2026-01-25T15:00:00Z' }] },
223+
// buildCompletionStats: exercises (status skipped but it has sets)
224+
{ rows: [{ id: 'se1', exercise_id: 'ex1', status: 'skipped', skip_reason: 'ran out of time', exercise_name: 'Stairs', muscle_group: 'legs' }] },
225+
// set_logs for se1 (fetched first now)
226+
{ rows: [
227+
{ set_number: 1, weight_lbs: '1.0', reps: 8 },
228+
{ set_number: 2, weight_lbs: '1.0', reps: 5 },
229+
{ set_number: 3, weight_lbs: '1.0', reps: 6 },
230+
] },
231+
// fetchLastSession: no prior session
232+
{ rows: [] },
233+
]);
234+
235+
pool.connect.mockResolvedValue(mockClient);
236+
237+
// buildWorkoutResponse (uses pool)
238+
pool.query.mockResolvedValueOnce({
239+
rows: [{ id: WID, program_day_id: DAY_ID, started_at: '2026-01-25T14:00:00Z', completed_at: '2026-01-25T15:00:00Z', notes: null }],
240+
});
241+
pool.query.mockResolvedValueOnce({ rows: [] }); // exercises
242+
243+
const res = await request(app).put(`/api/workouts/${WID}/complete`);
244+
245+
expect(res.status).toBe(200);
246+
const prog = res.body.data.progression;
247+
expect(prog.skipped).toBe(0);
248+
expect(prog.progressed).toBe(1); // first_time counts as progressed
249+
expect(prog.details[0].status).not.toBe('skipped');
250+
expect(prog.details[0].exercise_name).toBe('Stairs');
251+
});
252+
214253
it('returns 404 when workout not found', async () => {
215254
const mockClient = createMockClient([
216255
{}, // BEGIN

server/src/controllers/workoutController.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,15 @@ async function buildCompletionStats(client, workoutId) {
390390
let skipped = 0;
391391

392392
for (const ex of exercises) {
393-
if (ex.status === 'skipped') {
393+
// Get current session sets
394+
const { rows: currentSets } = await client.query(
395+
'SELECT set_number, weight_lbs, reps FROM set_logs WHERE session_exercise_id = $1 ORDER BY set_number',
396+
[ex.id]
397+
);
398+
399+
// Only count as skipped when no sets were logged. An exercise with logged
400+
// sets is a real (partial) effort even if its status is 'skipped'.
401+
if (ex.status === 'skipped' && currentSets.length === 0) {
394402
skipped++;
395403
details.push({
396404
exercise_id: ex.exercise_id,
@@ -402,12 +410,6 @@ async function buildCompletionStats(client, workoutId) {
402410
continue;
403411
}
404412

405-
// Get current session sets
406-
const { rows: currentSets } = await client.query(
407-
'SELECT set_number, weight_lbs, reps FROM set_logs WHERE session_exercise_id = $1 ORDER BY set_number',
408-
[ex.id]
409-
);
410-
411413
// Get last session for comparison
412414
const lastSession = await fetchLastSession(client, ex.exercise_id, workoutId);
413415
const progression = compareProgression(currentSets, lastSession?.sets || []);

0 commit comments

Comments
 (0)