diff --git a/README.md b/README.md index bdfe093..f487265 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ A local-first PWA fitness training application. Plan, track, and execute your wo npm install npm run dev # dev server at http://localhost:5173 npm run lint # ESLint (src/) +npm run lint:fix # ESLint (src/), auto-fix npm run format # Prettier (auto-fix) npm run format:check # Prettier (check only) ``` diff --git a/docs/decisions-log.md b/docs/decisions-log.md index 24d2279..cb1a41e 100644 --- a/docs/decisions-log.md +++ b/docs/decisions-log.md @@ -2,11 +2,17 @@ Record of architectural and technical decisions made during development. +## 2026-07-24 — Check Updates section moved before Credits; shows last manual check date + +**Decision:** Renamed the Settings "Application" card to **Check Updates** and moved it to sit directly above **Credits** (previously between Sound and the restore-overlay/report block). Added a `last_update_check_at` settings key (ISO-8601 UTC, in-place addition) stamped by `SettingsView.onCheckForUpdate()` right after `checkForUpdate()` resolves; the section displays it via `formatDateTime` ("Last manual check: {{when}}") or a "Never checked manually" fallback. + +**Rationale:** User request (2026-07-24). The timestamp intentionally only reflects manual clicks — the Service Worker API has no event for the browser's own background SW checks, so there is nothing reliable to hook there; tracking manual checks only was confirmed with the user rather than guessing at the browser's schedule. + ## 2026-07-22 — Manual "Check for updates" button, reusing the existing choice modal (Step 27) **Decision:** `useAppUpdate.js` now captures the service-worker `registration` via `useRegisterSW`'s `onRegisteredSW` callback and exposes `checkForUpdate()`, which calls `registration.update()` to force the browser to re-fetch the SW script instead of waiting for its own schedule. If the script changed, Workbox flips the existing `needRefresh` flag and `AppUpdateDialog` appears exactly as it does for a passively-detected update — same "Update now" / "Later" choice. `SettingsView.vue` gained an "Application" card with a "Check for updates" button; since the Service Worker API has no "no update found" event, a plain timeout heuristic shows a transient "You're up to date" message if `needRefresh` hasn't flipped a couple of seconds after the check. -**Rationale:** The point of the manual button is only to force *detection* sooner (Step 24's passive check only runs on the browser's own schedule, roughly on navigation/reload) — it must not become a second code path that could apply an update without asking. Routing the found-update case through the same `needRefresh`/`AppUpdateDialog` flow guarantees updating stays a user decision (user decision 2026-07-21) whether the update was found automatically or via the manual check, with no new modal or auto-apply branch to keep in sync. +**Rationale:** The point of the manual button is only to force _detection_ sooner (Step 24's passive check only runs on the browser's own schedule, roughly on navigation/reload) — it must not become a second code path that could apply an update without asking. Routing the found-update case through the same `needRefresh`/`AppUpdateDialog` flow guarantees updating stays a user decision (user decision 2026-07-21) whether the update was found automatically or via the manual check, with no new modal or auto-apply branch to keep in sync. ## 2026-07-09 — Deleting an exercise/combo cleans session usage and warns about it (code review F4) diff --git a/docs/plan.md b/docs/plan.md index 7409bd2..914a4dd 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -915,7 +915,7 @@ Hugo 0.123.7 static site under `website/`. FR default at `/`, EN at `/en/`. Depl ### Step 27 — Manual "Check for updates" button in Settings -**Status:** ✅ Done (2026-07-22) — manually validated by the user. 5 Playwright tests passing on Firefox + Chromium. +**Status:** ✅ Done (2026-07-22) — manually validated by the user. 5 Playwright tests passing on Firefox + Chromium. Follow-up (2026-07-24): section renamed to "Check Updates" and moved just above Credits; now also shows the last manual check date. 7 Playwright tests passing on Firefox + Chromium. **Requested:** 2026-07-21, user request — the PWA doesn't seem to update automatically; add a manual update button in Settings. @@ -923,22 +923,22 @@ Hugo 0.123.7 static site under `website/`. FR default at `/`, EN at `/en/`. Depl #### Decisions -| Decision | Choice | Rationale | -| ----------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | -| Detection mechanism | `registration.update()` via the SW registration captured through `useRegisterSW`'s `onRegisteredSW` callback | Forces the browser to re-fetch the SW script now instead of waiting for its own schedule | -| Found-update path | Reuses the existing `needRefresh` flow and `AppUpdateDialog` unchanged | Same "Update now" / "Later" choice regardless of how the update was detected — no auto-apply | -| No-update feedback | Timeout heuristic in `SettingsView.vue`: if `needRefresh` hasn't flipped ~2s after the check, show "You're up to date" for ~3s | There is no direct "no update found" event from the Service Worker API | -| Scope | No periodic/background auto-check — checking stays a deliberate user action | User decision 2026-07-21: updating stays optional even when available, checking stays opt-in too | +| Decision | Choice | Rationale | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | +| Detection mechanism | `registration.update()` via the SW registration captured through `useRegisterSW`'s `onRegisteredSW` callback | Forces the browser to re-fetch the SW script now instead of waiting for its own schedule | +| Found-update path | Reuses the existing `needRefresh` flow and `AppUpdateDialog` unchanged | Same "Update now" / "Later" choice regardless of how the update was detected — no auto-apply | +| No-update feedback | Timeout heuristic in `SettingsView.vue`: if `needRefresh` hasn't flipped ~2s after the check, show "You're up to date" for ~3s | There is no direct "no update found" event from the Service Worker API | +| Scope | No periodic/background auto-check — checking stays a deliberate user action | User decision 2026-07-21: updating stays optional even when available, checking stays opt-in too | #### Files created / modified - `src/composables/useAppUpdate.js` — capture `registration` via `onRegisteredSW`; add `checkForUpdate()` (calls `registration.update()`, guarded by a dev-only `mockCheckFn` test hook) and `checkingForUpdate` state -- `src/views/SettingsView.vue` — new "Application" card: "Check for updates" button (disabled while checking) + transient "up to date" message -- `src/i18n/locales/{en,fr,da}.json` — `settings.application`, `settings.checkForUpdate`, `settings.upToDate` +- `src/views/SettingsView.vue` — "Check Updates" section (moved just above Credits, 2026-07-24 follow-up): "Check for updates" button (disabled while checking), transient "up to date" message, and the last manual check date read/written via the `last_update_check_at` settings key (in-place addition, no schema bump) +- `src/i18n/locales/{en,fr,da}.json` — `settings.checkUpdates`, `settings.checkForUpdate`, `settings.upToDate`, `settings.lastManualCheck`, `settings.neverCheckedManually` - `tests/test_step27_check_for_updates.py` - `docs/decisions-log.md`, `website/content/developers/technical.en.md`, `website/content/docs/user-guide/index.{en,fr}.md` -**Verification:** Playwright tests (via the dev test hook `window.__appUpdateTest.mockCheckFn`) — button calls through to `registration.update()`; no update found shows the transient "up to date" message which then disappears; an update found via manual check still shows the "Update now" / "Later" modal (choice preserved, no auto-apply); the "up to date" message never appears when an update was found. Firefox + Chromium. +**Verification:** Playwright tests (via the dev test hook `window.__appUpdateTest.mockCheckFn`) — button calls through to `registration.update()`; no update found shows the transient "up to date" message which then disappears; an update found via manual check still shows the "Update now" / "Later" modal (choice preserved, no auto-apply); the "up to date" message never appears when an update was found; last-check date shows "never checked manually" on a fresh DB and updates + persists across reload after a click. Firefox + Chromium. --- diff --git a/src/i18n/locales/da.json b/src/i18n/locales/da.json index 3cdd51d..d9724df 100644 --- a/src/i18n/locales/da.json +++ b/src/i18n/locales/da.json @@ -278,9 +278,11 @@ "soundVolume": "Lydstyrke", "soundTest": "Test", "voiceAnnouncements": "Stemmemeddelelser", - "application": "Applikation", + "checkUpdates": "Søg efter opdateringer", "checkForUpdate": "Søg efter opdateringer", "upToDate": "Du har den nyeste version", + "lastManualCheck": "Seneste manuelle søgning: {{when}}", + "neverCheckedManually": "Aldrig søgt manuelt", "credits": "Kreditering", "manageJournal": "Administrer øvelseslogge", "manageJournalHint": "Slet øvelseslogposter inden for et datointerval.", diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 5656069..c499c28 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -278,9 +278,11 @@ "soundVolume": "Volume", "soundTest": "Test", "voiceAnnouncements": "Voice announcements", - "application": "Application", + "checkUpdates": "Check Updates", "checkForUpdate": "Check for updates", "upToDate": "You're up to date", + "lastManualCheck": "Last manual check: {{when}}", + "neverCheckedManually": "Never checked manually", "credits": "Credits", "manageJournal": "Manage Exercise Logs", "manageJournalHint": "Delete exercise log entries within a date range.", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 5559295..91beb1f 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -278,9 +278,11 @@ "soundVolume": "Volume", "soundTest": "Tester", "voiceAnnouncements": "Annonces vocales", - "application": "Application", + "checkUpdates": "Vérifier les mises à jour", "checkForUpdate": "Vérifier les mises à jour", "upToDate": "Vous êtes à jour", + "lastManualCheck": "Dernière vérification manuelle : {{when}}", + "neverCheckedManually": "Jamais vérifié manuellement", "credits": "Crédits", "manageJournal": "Gérer les journaux d'exercices", "manageJournalHint": "Supprimez les entrées du journal d'exercices sur une plage de dates.", diff --git a/src/views/SettingsView.vue b/src/views/SettingsView.vue index a67bdf8..017be98 100644 --- a/src/views/SettingsView.vue +++ b/src/views/SettingsView.vue @@ -47,6 +47,12 @@ const initializing = ref(false) const showUpToDate = ref(false) let upToDateTimer = null let hideUpToDateTimer = null +const lastUpdateCheckAtRaw = ref(null) + +const lastUpdateCheckDisplay = computed(() => { + if (!lastUpdateCheckAtRaw.value) return '' + return formatDateTime(lastUpdateCheckAtRaw.value, language.value) +}) // Backup reminder const lastBackupAtRaw = ref(null) @@ -82,6 +88,8 @@ onMounted(async () => { audioVolume.value = isNaN(parsed) ? 50 : parsed const voiceStr = await get('voice_enabled') voiceEnabled.value = voiceStr === null ? true : voiceStr === 'true' + const lastUpdateCheckStr = await get('last_update_check_at') + lastUpdateCheckAtRaw.value = lastUpdateCheckStr ? new Date(lastUpdateCheckStr) : null await loadSuffixes() await loadBackupInfo() await loadBackupStatus() @@ -123,6 +131,9 @@ async function onCheckForUpdate() { clearTimeout(hideUpToDateTimer) showUpToDate.value = false await checkForUpdate() + const now = new Date() + await set('last_update_check_at', now.toISOString()) + lastUpdateCheckAtRaw.value = now // There is no "no update found" event from the SW API, so fall back to a // timeout: if the update prompt hasn't appeared shortly after the check, // tell the user they're up to date. @@ -462,28 +473,6 @@ async function onBackupReminderDaysChange() { - -
- {{ $t('settings.upToDate') }} -
-