fix(fitness): mirror finish overview to other devices via SSE

Other devices were left on a blank active-workout page when one device
hit Finish. The finish broadcast now carries the saved session document,
and the receiving page builds the completion overview from it. If the
remote end-of-workout has no session payload (cancel, or the finishing
device couldn't post the session), receivers redirect to the workout
home instead of stranding on a blank page.
This commit is contained in:
2026-05-10 14:42:50 +02:00
parent eeed31aaf4
commit e87b8bd864
4 changed files with 68 additions and 10 deletions
+23 -5
View File
@@ -37,6 +37,7 @@ 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<typeof setTimeout> | null = null;
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
@@ -166,8 +167,15 @@ export function createWorkoutSync() {
} catch {}
});
eventSource.addEventListener('finished', () => {
// Another device finished the workout
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;
}
workout.cancel();
disconnectSSE();
});
@@ -217,14 +225,22 @@ export function createWorkoutSync() {
connectSSE();
}
/** Called when workout finishes or is cancelled — clean up server state */
async function onWorkoutEnd() {
/** 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) {
disconnectSSE();
try {
await fetch('/api/fitness/workout/active', { method: 'DELETE' });
const qs = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : '';
await fetch(`/api/fitness/workout/active${qs}`, { 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 {
@@ -290,9 +306,11 @@ export function createWorkoutSync() {
return {
get status() { return status; },
get serverVersion() { return serverVersion; },
get lastFinishedSession() { return lastFinishedSession; },
init,
onWorkoutStart,
onWorkoutEnd,
clearFinishedSession,
notifyChange,
destroy
};