From 244050fa75792a3ada18c69488d2e51b6efaf730 Mon Sep 17 00:00:00 2001 From: Alexander Bocken Date: Fri, 8 May 2026 16:28:26 +0200 Subject: [PATCH] feat(fitness): cache more fitness shells & show unsynced workouts on history Precache: - Service worker now precaches /fitness/workout(/active) shells (DE+EN) on install so a fresh device can log workouts offline without first visiting /fitness online. - Layout-level precache adds /fitness/__data.json itself plus the static sub-routes nutrition/meals and check-in/body-parts (DE+EN). Unsynced workouts on history: - History page reads the offline outbox via getQueuedSessions on mount and merges queued sessions into the displayed list, sorted by startTime. Duration is computed locally so the Clock stat still shows. - SessionCard gains an 'unsynced' prop: renders as a non-clickable div with an orange-accent border and a CloudOff badge labelled 'Unsynced' / 'Nicht synchronisiert'. - On window 'online', the page waits briefly for the layout's flushQueue to drain the outbox, then re-reads the queue and calls invalidateAll to swap unsynced placeholders for the now-saved server sessions. --- package.json | 2 +- src/lib/components/fitness/SessionCard.svelte | 53 +++++++++++++-- src/lib/i18n/fitness/de.ts | 1 + src/lib/i18n/fitness/en.ts | 1 + src/routes/fitness/+layout.svelte | 8 ++- .../[[month=fitnessMonth]]/+page.svelte | 66 ++++++++++++++++++- src/service-worker.ts | 8 ++- 7 files changed, 126 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 1fa6579a..e8da2e59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homepage", - "version": "1.67.5", + "version": "1.68.0", "private": true, "type": "module", "scripts": { diff --git a/src/lib/components/fitness/SessionCard.svelte b/src/lib/components/fitness/SessionCard.svelte index 0e448e62..d7d7822d 100644 --- a/src/lib/components/fitness/SessionCard.svelte +++ b/src/lib/components/fitness/SessionCard.svelte @@ -8,10 +8,12 @@ import Route from '@lucide/svelte/icons/route'; import Gauge from '@lucide/svelte/icons/gauge'; import Flame from '@lucide/svelte/icons/flame'; - import { detectFitnessLang, fitnessSlugs } from '$lib/js/fitnessI18n'; + import CloudOff from '@lucide/svelte/icons/cloud-off'; + import { detectFitnessLang, fitnessSlugs, m } from '$lib/js/fitnessI18n'; const lang = $derived(detectFitnessLang(page.url.pathname)); const sl = $derived(fitnessSlugs(lang)); + const t = $derived(m[lang]); /** * @type {{ @@ -30,10 +32,11 @@ * gpsPreview?: number[][], * sets: Array<{ reps?: number, weight?: number, rpe?: number, distance?: number, duration?: number }> * }> - * } + * }, + * unsynced?: boolean * }} */ - let { session } = $props(); + let { session, unsynced = false } = $props(); /** @param {number} mins */ function formatDuration(mins) { @@ -153,9 +156,17 @@ }); - +{#snippet cardBody()}
-

{session.name}

+

+ {session.name} + {#if unsynced} + + + {t.unsynced_label} + + {/if} +

{formatDate(session.startTime)} · {formatTime(session.startTime)}
@@ -207,7 +218,13 @@ {session.prs.length} PR{session.prs.length > 1 ? 's' : ''} {/if} -
+{/snippet} + +{#if unsynced} +
{@render cardBody()}
+{:else} + {@render cardBody()} +{/if}