diff --git a/package.json b/package.json index fd78ec81..30571700 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homepage", - "version": "1.70.0", + "version": "1.70.1", "private": true, "type": "module", "scripts": { diff --git a/src/lib/js/workoutSync.svelte.ts b/src/lib/js/workoutSync.svelte.ts index f8f7a7a6..31e4a612 100644 --- a/src/lib/js/workoutSync.svelte.ts +++ b/src/lib/js/workoutSync.svelte.ts @@ -37,7 +37,6 @@ export function createWorkoutSync() { let status: SyncStatus = $state('idle'); let serverVersion = $state(0); - let lastFinishedSession: any = $state(null); let eventSource: EventSource | null = null; let debounceTimer: ReturnType | null = null; let reconnectTimer: ReturnType | null = null; @@ -167,15 +166,8 @@ export function createWorkoutSync() { } catch {} }); - eventSource.addEventListener('finished', (e) => { - // Another device finished the workout. If they passed along the saved - // session, expose it so the active page can build the completion overview. - try { - const data = JSON.parse(e.data); - lastFinishedSession = data?.session ?? null; - } catch { - lastFinishedSession = null; - } + eventSource.addEventListener('finished', () => { + // Another device finished the workout workout.cancel(); disconnectSSE(); }); @@ -225,22 +217,14 @@ export function createWorkoutSync() { connectSSE(); } - /** Called when workout finishes or is cancelled — clean up server state. - * Pass the just-saved session id so other devices receive it via SSE - * and can render the finish overview. */ - async function onWorkoutEnd(sessionId?: string | null) { + /** Called when workout finishes or is cancelled — clean up server state */ + async function onWorkoutEnd() { disconnectSSE(); try { - const qs = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : ''; - await fetch(`/api/fitness/workout/active${qs}`, { method: 'DELETE' }); + await fetch('/api/fitness/workout/active', { method: 'DELETE' }); } catch {} } - /** Clear the finished-session payload after the page has consumed it. */ - function clearFinishedSession() { - lastFinishedSession = null; - } - /** Called on app load — reconcile local vs server state */ async function init() { try { @@ -306,11 +290,9 @@ export function createWorkoutSync() { return { get status() { return status; }, get serverVersion() { return serverVersion; }, - get lastFinishedSession() { return lastFinishedSession; }, init, onWorkoutStart, onWorkoutEnd, - clearFinishedSession, notifyChange, destroy }; diff --git a/src/routes/api/fitness/workout/active/+server.ts b/src/routes/api/fitness/workout/active/+server.ts index 595687eb..13a6bdf3 100644 --- a/src/routes/api/fitness/workout/active/+server.ts +++ b/src/routes/api/fitness/workout/active/+server.ts @@ -2,7 +2,6 @@ import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; import { dbConnect } from '$utils/db'; import { ActiveWorkout } from '$models/ActiveWorkout'; -import { WorkoutSession } from '$models/WorkoutSession'; import { broadcast } from '$lib/server/sseManager'; // GET /api/fitness/workout/active — fetch current active workout @@ -92,9 +91,7 @@ export const PUT: RequestHandler = async ({ request, locals }) => { }; // DELETE /api/fitness/workout/active — clear active workout (finish/cancel) -// Optional ?sessionId= attaches the just-saved session to the broadcast so -// other devices can render the finish overview instead of a blank page. -export const DELETE: RequestHandler = async ({ locals, url }) => { +export const DELETE: RequestHandler = async ({ locals }) => { const session = locals.session ?? await locals.auth(); if (!session?.user?.nickname) { return json({ error: 'Unauthorized' }, { status: 401 }); @@ -105,17 +102,8 @@ export const DELETE: RequestHandler = async ({ locals, url }) => { const userId = session.user.nickname; await ActiveWorkout.deleteOne({ userId }); - const sessionId = url.searchParams.get('sessionId'); - let sessionDoc: unknown = null; - if (sessionId) { - try { - sessionDoc = await WorkoutSession.findOne({ _id: sessionId, createdBy: userId }).lean(); - } catch { - sessionDoc = null; - } - } - - broadcast(userId, 'finished', { active: false, session: sessionDoc }); + // Notify all devices that workout is finished + broadcast(userId, 'finished', { active: false }); return json({ ok: true }); } catch (error) { diff --git a/src/routes/fitness/[workout=fitnessWorkout]/[active=fitnessActive]/+page.svelte b/src/routes/fitness/[workout=fitnessWorkout]/[active=fitnessActive]/+page.svelte index 644180b5..f62d0ad1 100644 --- a/src/routes/fitness/[workout=fitnessWorkout]/[active=fitnessActive]/+page.svelte +++ b/src/routes/fitness/[workout=fitnessWorkout]/[active=fitnessActive]/+page.svelte @@ -161,36 +161,8 @@ let templateDiffs = $state([]); let templateUpdateStatus = $state('idle'); // 'idle' | 'updating' | 'done' - // Track whether we've ever observed an active workout on this page so the - // remote-end redirect doesn't fire during the brief gap before localStorage - // + server hydration completes on initial mount. - let _everActive = false; - - // Mirror the finish overview when another device finishes the workout. - // Sync surfaces the saved session via SSE; treat it like the local finish path. - $effect(() => { - const remoteSession = sync.lastFinishedSession; - if (!remoteSession || completionData) return; - completionData = buildCompletion(remoteSession, remoteSession); - computeTemplateDiff(completionData); - sync.clearFinishedSession(); - }); - - // If the workout ends remotely without a session payload (cancel from another - // device, or that device couldn't post the session), exit cleanly instead of - // leaving this device on a blank active page. - $effect(() => { - if (workout.active) { - _everActive = true; - return; - } - if (!_everActive || completionData || sync.lastFinishedSession) return; - goto(`/fitness/${sl.workout}`); - }); - - // Celebratory fanfare on workout completion. Fires once, regardless of - // whether completionData was populated by the local finish path or by the - // remote-finish sync effect above. + // Celebratory fanfare on workout completion. Fires once when completionData + // becomes truthy after a successful finish. let _completionSoundPlayed = false; $effect(() => { if (completionData && !_completionSoundPlayed) { @@ -786,16 +758,15 @@ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(sessionData) }); + await sync.onWorkoutEnd(); if (res.ok) { const d = await res.json(); completionData = buildCompletion(sessionData, d.session); computeTemplateDiff(completionData); - await sync.onWorkoutEnd(d.session?._id); } else { await queueSession(sessionData); offlineQueued = true; completionData = buildCompletion(sessionData, { _id: null }); - await sync.onWorkoutEnd(); } } catch { await queueSession(sessionData);