diff --git a/backend/main.py b/backend/main.py index 928c345..99d431c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -410,7 +410,7 @@ async def serve_media(path: str, request: _Request): raise _HE(404, "Nicht gefunden.") return _media_response(filepath) -APP_VER = "1086" # muss mit APP_VER in app.js übereinstimmen +APP_VER = "1087" # muss mit APP_VER in app.js übereinstimmen @app.get("/.well-known/assetlinks.json") async def assetlinks(): diff --git a/backend/static/index.html b/backend/static/index.html index fed4422..7abc609 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -101,9 +101,9 @@ - - - + + + @@ -625,11 +625,11 @@ - - - - - + + + + + diff --git a/backend/static/js/app.js b/backend/static/js/app.js index ff6bcce..242695f 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '1086'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '1087'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator) window.APP_VERSION = APP_VERSION; diff --git a/backend/static/js/offline-indicator.js b/backend/static/js/offline-indicator.js index a895b24..770367f 100644 --- a/backend/static/js/offline-indicator.js +++ b/backend/static/js/offline-indicator.js @@ -40,33 +40,35 @@ window.OfflineIndicator = (() => { return must.every(name => urls.some(u => u.includes('/js/pages/' + name))); } }, - { step: 3, title: 'Hund- und Tagebuchdaten', - detail: 'Letzte Einträge und Hund-Profil', + { step: 3, title: 'Hund-Daten', + detail: 'Profil, Tagebuch und Gesundheit', probe: async () => { const c = await caches.open(CACHE_API).catch(() => null); if (!c) return false; const urls = (await c.keys()).map(r => r.url); - return urls.some(u => /\/api\/dogs\/\d+/.test(u)) - && urls.some(u => /\/api\/dogs\/\d+\/diary/.test(u)); + return urls.some(u => /\/api\/dogs\/\d+(\?|$)/.test(u)) + && urls.some(u => /\/api\/dogs\/\d+\/diary/.test(u)) + && urls.some(u => /\/api\/dogs\/\d+\/health/.test(u)); } }, - { step: 4, title: 'Karten-Kacheln', - detail: `Mindestens ${TILE_MIN} Tiles im Umkreis`, + { step: 4, title: 'Weitere Listen', + detail: 'Ausgaben, Routen, Notizen', + probe: async () => { + const c = await caches.open(CACHE_API).catch(() => null); + if (!c) return false; + const urls = (await c.keys()).map(r => r.url); + return urls.some(u => u.includes('/api/expenses')) + && urls.some(u => u.includes('/api/routes')) + && urls.some(u => u.includes('/api/notes')); + } }, + + { step: 5, title: 'Karten-Kacheln', + detail: `Mindestens ${TILE_MIN} OSM-Tiles im Umkreis`, probe: async () => { const c = await caches.open(CACHE_TILES).catch(() => null); if (!c) return false; return (await c.keys()).length >= TILE_MIN; } }, - - { step: 5, title: 'Welt-Daten', - detail: 'Streak, Wetter, Hundepass — kommt beim Welten-Aufruf', - probe: async () => { - const c = await caches.open(CACHE_API).catch(() => null); - if (!c) return false; - const urls = (await c.keys()).map(r => r.url); - return urls.some(u => u.includes('/api/streak/')) - && urls.some(u => u.includes('/api/weather')); - } }, ]; // Tile-Prefetch-Konfiguration @@ -154,23 +156,14 @@ window.OfflineIndicator = (() => { if (dogId) { tasks.push(fetch(`/api/dogs/${dogId}`).catch(() => {})); tasks.push(fetch(`/api/dogs/${dogId}/diary?limit=20`).catch(() => {})); + tasks.push(fetch(`/api/dogs/${dogId}/health`).catch(() => {})); } } else if (m.step === 4) { - if (navigator.serviceWorker?.controller) { - const pos = await new Promise(res => - navigator.geolocation?.getCurrentPosition(p => res(p), () => res(null), { timeout: 4000 })); - if (pos) { - navigator.serviceWorker.controller.postMessage({ - type: 'CACHE_TILES', lat: pos.coords.latitude, lon: pos.coords.longitude, - zoom: 14, radius: 2, - }); - } - } + tasks.push(fetch('/api/expenses').catch(() => {})); + tasks.push(fetch('/api/routes').catch(() => {})); + tasks.push(fetch('/api/notes').catch(() => {})); } else if (m.step === 5) { - // Welt-Daten: Streak braucht Hund-ID, Wetter braucht GPS - tasks.push(fetch('/api/weather').catch(() => {})); - const dogId = window._appState?.activeDog?.id; - if (dogId) tasks.push(fetch(`/api/streak/${dogId}`).catch(() => {})); + await _prefetchTiles(); } } await Promise.all(tasks); @@ -214,9 +207,28 @@ window.OfflineIndicator = (() => { } catch {} } + // Daten-Prefetch beim App-Start: Listen die offline brauchbar sein müssen, + // auch wenn der User die Seiten noch nie geöffnet hat + async function _prefetchData() { + fetch('/api/expenses').catch(() => {}); + fetch('/api/routes').catch(() => {}); + fetch('/api/notes').catch(() => {}); + // Hund-spezifische Daten nur wenn aktiver Hund bekannt + const dogId = window._appState?.activeDog?.id; + if (dogId) { + fetch(`/api/dogs/${dogId}/health`).catch(() => {}); + fetch(`/api/dogs/${dogId}/diary?limit=20`).catch(() => {}); + } + } + function init() { refresh(); - _prefetchTiles(); // im Hintergrund + _prefetchTiles(); // Karten-Tiles (nur wenn GPS schon erlaubt) + _prefetchData(); // Listen-Daten (Expenses, Routes, Notes, Health) + + // Wenn der aktive Hund erst nach init() gesetzt wird → nochmal triggern + setTimeout(() => { _prefetchData(); refresh(); }, 3000); + if (navigator.serviceWorker) { navigator.serviceWorker.addEventListener('message', e => { if (e?.data?.type === 'CACHE_UPDATE') refresh(); diff --git a/backend/static/sw.js b/backend/static/sw.js index 59c9a12..16ae848 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -4,7 +4,7 @@ ============================================================ */ // ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab -const VER = '1086'; +const VER = '1087'; const CACHE_VERSION = `by-v${VER}`; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten