Bug
Completing a program session out of order can write a currentWeek that doesn't exist in the program, stranding the user on an empty "ghost week".
Reproduction
Program: 2 weeks (W1: 2 sessions, W2: 2 sessions). Start at W1/S1, then complete W2/S2 while W2/S1 (and earlier) are still incomplete (out-of-order completion, which the app otherwise supports).
In complete-program-session.action.ts and app/api/programs/session-progress/[progressId]/complete/route.ts:
let nextWeek = currentWeek;
let nextSession = currentSession + 1;
const currentWeekSessions = enrollment.program.weeks.find((w) => w.weekNumber === currentWeek)?.sessions.length || 0;
if (nextSession > currentWeekSessions) {
nextWeek = currentWeek + 1; // <- never clamped
nextSession = 1;
}
const isCompleted = completedSessionsCount >= totalSessions;
// ...
data: { currentWeek: isCompleted ? currentWeek : nextWeek, ... }
The wrap increments the week without checking that currentWeek + 1 actually exists. For the repro above (currentWeek=2, currentSession=2, completed count 1 < total 4 → isCompleted=false):
{ isCompleted: false, nextWeek: 3, nextSession: 1 } // program only has weeks 1–2!
The enrollment is then saved as currentWeek=3, currentSession=1. On the program page (program-detail-page.tsx), setSelectedWeek(progress.stats.currentWeek) selects week 3, which has no sessions — the user is stuck on an empty week with no path forward (the only escape is re-selecting a real week manually, and their "resume" pointer is permanently wrong until they finish enough sessions to flip isCompleted).
The same wrap logic is duplicated in two files, so both are affected.
Fix
Clamp the week increment to the program's last week so it can never point past the end:
const maxWeek = Math.max(...enrollment.program.weeks.map((w) => w.weekNumber));
if (nextSession > currentWeekSessions) {
nextWeek = Math.min(currentWeek + 1, maxWeek);
nextSession = 1;
}
If the program is actually done, isCompleted already guards the write. This just stops the pointer from running off the end during out-of-order completion. I have a PR ready (fixing both copies).
Bug
Completing a program session out of order can write a
currentWeekthat doesn't exist in the program, stranding the user on an empty "ghost week".Reproduction
Program: 2 weeks (W1: 2 sessions, W2: 2 sessions). Start at W1/S1, then complete W2/S2 while W2/S1 (and earlier) are still incomplete (out-of-order completion, which the app otherwise supports).
In
complete-program-session.action.tsandapp/api/programs/session-progress/[progressId]/complete/route.ts:The wrap increments the week without checking that
currentWeek + 1actually exists. For the repro above (currentWeek=2, currentSession=2, completed count1 < total 4→isCompleted=false):The enrollment is then saved as
currentWeek=3, currentSession=1. On the program page (program-detail-page.tsx),setSelectedWeek(progress.stats.currentWeek)selects week 3, which has no sessions — the user is stuck on an empty week with no path forward (the only escape is re-selecting a real week manually, and their "resume" pointer is permanently wrong until they finish enough sessions to flipisCompleted).The same wrap logic is duplicated in two files, so both are affected.
Fix
Clamp the week increment to the program's last week so it can never point past the end:
If the program is actually done,
isCompletedalready guards the write. This just stops the pointer from running off the end during out-of-order completion. I have a PR ready (fixing both copies).