Skip to content

Commit d08214d

Browse files
authored
Merge pull request #31 from willchan/claude/fix-workout-timer-bug-b7Zoy
2 parents 1e618b2 + 9ddad82 commit d08214d

2 files changed

Lines changed: 64 additions & 1 deletion

File tree

workout-tracker/e2e/workout-reload.spec.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,58 @@ test.describe('Workout Reload Persistence', () => {
117117
});
118118
});
119119

120+
test.describe('Back Button Timer Cleanup', () => {
121+
/**
122+
* Regression: pressing Back during a rest timer left the timer state in
123+
* IndexedDB. On the next workout load, the recovery code would fire the
124+
* notification and/or show "Time's Up!" immediately.
125+
*/
126+
test('stale timer from previous session does not fire on new workout start', async ({ page }) => {
127+
await page.addInitScript(() => {
128+
(window as any).__vibrateCount = 0;
129+
Object.defineProperty(navigator, 'vibrate', {
130+
value: () => { (window as any).__vibrateCount++; return true; },
131+
writable: true,
132+
configurable: true,
133+
});
134+
});
135+
136+
await page.goto('/');
137+
await page.waitForSelector('#app');
138+
await page.click('#start-workout-btn');
139+
await page.waitForSelector('.workout-screen');
140+
141+
// Seed a stale timer (still has ~2 seconds remaining) — simulating what
142+
// the Back button leaves behind because it doesn't clear IndexedDB timer state.
143+
await page.evaluate(async () => {
144+
const { putTimerState } = await import('/src/db/database.ts');
145+
await putTimerState({
146+
expectedEndTime: Date.now() + 2000, // expires in 2 seconds
147+
durationMs: 90000,
148+
});
149+
});
150+
151+
// Navigate to home (simulating Back button navigation)
152+
await page.evaluate(() => { window.location.hash = 'home'; });
153+
await page.waitForSelector('#start-workout-btn');
154+
await page.evaluate(() => { (window as any).__vibrateCount = 0; });
155+
156+
// Start a new workout — stale timer should be cleared, not re-used
157+
await page.click('#start-workout-btn');
158+
await page.waitForSelector('.workout-screen');
159+
160+
// Wait long enough for the stale timer to expire (>2s)
161+
await page.waitForTimeout(2500);
162+
163+
// No notification should have fired from the stale timer
164+
const vibrateCount = await page.evaluate(() => (window as any).__vibrateCount);
165+
expect(vibrateCount).toBe(0);
166+
167+
// No "Time's Up!" UI should have appeared
168+
await expect(page.locator('[data-testid="timer-expired"]')).not.toBeAttached();
169+
});
170+
});
171+
120172
test.describe('Cancel/Abandon Workout', () => {
121173
test.beforeEach(async ({ page }) => {
122174
await page.goto('/');

workout-tracker/src/ui/workout.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export async function renderWorkout(container: HTMLElement): Promise<void> {
6161

6262
// Restore in-progress workout if one exists for this same day
6363
const activeWorkout = await getActiveWorkout();
64+
let resumingActiveWorkout = false;
6465
if (
6566
activeWorkout &&
6667
activeWorkout.templateId === state.templateId &&
@@ -71,6 +72,15 @@ export async function renderWorkout(container: HTMLElement): Promise<void> {
7172
completedSets.push(...activeWorkout.completedSets);
7273
currentSetIndex = activeWorkout.currentSetIndex;
7374
workoutStartTime = activeWorkout.startedAt;
75+
resumingActiveWorkout = true;
76+
}
77+
78+
// If starting fresh (not resuming), clear any stale timer state that may
79+
// have been left over from a previous session (e.g. user pressed Back
80+
// while a rest timer was running).
81+
if (!resumingActiveWorkout) {
82+
await putTimerState(null);
83+
cancelBackgroundTimerNotification();
7484
}
7585

7686
container.innerHTML = '';
@@ -498,10 +508,11 @@ export async function renderWorkout(container: HTMLElement): Promise<void> {
498508
}
499509

500510
// Event listeners
501-
document.getElementById('back-btn')?.addEventListener('click', () => {
511+
document.getElementById('back-btn')?.addEventListener('click', async () => {
502512
releaseWakeLock();
503513
if (timerInterval) clearInterval(timerInterval);
504514
cancelBackgroundTimerNotification();
515+
await putTimerState(null);
505516
navigate('home');
506517
});
507518

0 commit comments

Comments
 (0)