diff --git a/backend/main.py b/backend/main.py index 8c5b390..4fdbfae 100644 --- a/backend/main.py +++ b/backend/main.py @@ -327,7 +327,16 @@ MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media") os.makedirs(MEDIA_DIR, exist_ok=True) app.mount("/media", StaticFiles(directory=MEDIA_DIR), name="media") -APP_VER = "760" # muss mit APP_VER in app.js übereinstimmen +APP_VER = "774" # muss mit APP_VER in app.js übereinstimmen + +@app.get("/.well-known/assetlinks.json") +async def assetlinks(): + """TWA-Verifikation für Google Play Store (app.banyaro.twa).""" + return Response( + content='[{"relation":["delegate_permission/common.handle_all_urls"],"target":{"namespace":"android_app","package_name":"app.banyaro.twa","sha256_cert_fingerprints":["49:02:DC:5B:63:C0:D7:42:7F:A4:DC:2F:EB:78:73:11:CC:B9:36:22:00:01:A0:03:1C:0A:F9:41:35:9F:D4:B7"]}}]', + media_type="application/json", + headers={"Cache-Control": "no-cache"}, + ) @app.get("/api/version") async def app_version(): @@ -848,7 +857,7 @@ async def share_target(request: Request): # Weiterleitung zur App mit den Daten return FileResponse( f"{STATIC_DIR}/index.html", - headers={"Cache-Control": "no-cache"} + headers={"Cache-Control": "no-store, no-cache"} ) # Öffentliche Hunde-Profilseite (für NFC-Tags, kein Login nötig) @@ -1182,17 +1191,17 @@ async def public_dog_page(dog_id: int): # ------------------------------------------------------------------ @app.get("/teilen/{token}") async def invite_page(token: str): - return FileResponse(f"{STATIC_DIR}/index.html", headers={"Cache-Control": "no-cache"}) + return FileResponse(f"{STATIC_DIR}/index.html", headers={"Cache-Control": "no-store, no-cache"}) @app.get("/breeder/{zwingername}") async def breeder_profile_page(zwingername: str): - return FileResponse(f"{STATIC_DIR}/index.html", headers={"Cache-Control": "no-cache"}) + return FileResponse(f"{STATIC_DIR}/index.html", headers={"Cache-Control": "no-store, no-cache"}) @app.get("/litters") async def litters_page(): - return FileResponse(f"{STATIC_DIR}/index.html", headers={"Cache-Control": "no-cache"}) + return FileResponse(f"{STATIC_DIR}/index.html", headers={"Cache-Control": "no-store, no-cache"}) # ------------------------------------------------------------------ @@ -1200,7 +1209,7 @@ async def litters_page(): # ------------------------------------------------------------------ @app.get("/widget") async def widget_page(): - return FileResponse(f"{STATIC_DIR}/index.html", headers={"Cache-Control": "no-cache"}) + return FileResponse(f"{STATIC_DIR}/index.html", headers={"Cache-Control": "no-store, no-cache"}) # ------------------------------------------------------------------ @@ -1355,7 +1364,14 @@ async def ausweis_page(dog_id: int, request: Request):
- +
+ + +

Impfungen

@@ -1726,7 +1742,7 @@ async def partner_landing():
""" - return HTMLResponse(content=html, headers={"Cache-Control": "no-cache"}) + return HTMLResponse(content=html, headers={"Cache-Control": "no-store, no-cache"}) # ------------------------------------------------------------------ @@ -1924,8 +1940,8 @@ async def spa_fallback(full_path: str): '', '', ) - return HTMLResponse(content=html, headers={"Cache-Control": "no-cache"}) + return HTMLResponse(content=html, headers={"Cache-Control": "no-store, no-cache"}) return FileResponse( f"{STATIC_DIR}/index.html", - headers={"Cache-Control": "no-cache"} + headers={"Cache-Control": "no-store, no-cache"} ) diff --git a/backend/routes/dogs.py b/backend/routes/dogs.py index 42b9b32..6c35334 100644 --- a/backend/routes/dogs.py +++ b/backend/routes/dogs.py @@ -742,9 +742,18 @@ async def get_hunde_buch( - +
{cover_img} diff --git a/backend/static/css/components.css b/backend/static/css/components.css index 2f95c24..71b87ca 100644 --- a/backend/static/css/components.css +++ b/backend/static/css/components.css @@ -8370,3 +8370,336 @@ svg.empty-state-icon { .breed-community-chip:hover, .breed-community-chip:active { background: #fff3e0; } + +/* ============================================================ + REFACTORING: Extrahierte Inline-Styles aus worlds.js, + dog-profile.js und settings.js + ============================================================ */ + +/* ---------------------------------------------------------- + Bottom-Sheet Overlay (position:fixed, flex-column, flex-end) + Verwendet in: _openFab, _openAllChips, _openQuickGassi, + _openConfigModal (worlds.js) + ---------------------------------------------------------- */ +.w3-sheet-overlay { + position: fixed; + inset: 0; + z-index: 460; + display: flex; + flex-direction: column; + justify-content: flex-end; +} + +/* Backdrop (halbtransparent + blur) */ +.w3-backdrop { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.55); + backdrop-filter: blur(2px); +} + +/* Sheet-Panel (weißer Boden, abgerundete Oberkante) */ +.w3-sheet-panel { + position: relative; + z-index: 1; + background: var(--c-bg); + border-radius: 24px 24px 0 0; + padding: 20px 16px calc(env(safe-area-inset-bottom, 16px) + 16px); + box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.2); +} + +/* Sheet-Panel mit Scroll (für lange Inhalte) */ +.w3-sheet-panel--scroll { + max-height: 82vh; + overflow-y: auto; +} + +/* Sheet-Header-Zeile (Titel links, Button rechts) */ +.w3-sheet-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.w3-sheet-header--mb20 { + margin-bottom: 20px; +} + +/* Sheet-Titel */ +.w3-sheet-title { + font-size: var(--text-base); + font-weight: 700; +} + +/* Runder Schließen-Button (28px) */ +.w3-close-btn { + background: var(--c-border); + border: none; + border-radius: 50%; + width: 28px; + height: 28px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +/* Runder Schließen-Button (32px — größere Variante) */ +.w3-close-btn--lg { + background: var(--c-border); + border: none; + border-radius: 50%; + width: 32px; + height: 32px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +/* FAB-Options-Button (Zeilen-Stil, flex, card-bg) */ +.w3-fab-option { + display: flex; + align-items: center; + gap: 14px; + width: 100%; + background: var(--c-bg-card, var(--c-surface)); + border: 1px solid var(--c-border); + border-radius: 14px; + padding: 14px 16px; + cursor: pointer; + text-align: left; + transition: background 0.12s; +} + +/* Icon-Dot (runder farbiger Container für ph-icons) */ +.w3-icon-dot { + width: 40px; + height: 40px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +/* Icon-Dot groß (44px) */ +.w3-icon-dot--lg { + width: 44px; + height: 44px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +/* Chip-Button (Grid-Spalten, vertikal gestapelt) */ +.w3-chip-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + background: var(--c-bg-card, var(--c-surface)); + border: 1px solid var(--c-border); + border-radius: 14px; + padding: 12px 6px; + cursor: pointer; + transition: background 0.12s; +} + +/* Chip-Button Label */ +.w3-chip-label { + font-size: 10px; + font-weight: 600; + color: var(--c-text); + text-align: center; + line-height: 1.2; + word-break: break-word; +} + +/* Vertikal gestapelte Button-Gruppe (Modal-Footer) */ +.w3-btn-stack { + display: flex; + flex-direction: column; + gap: var(--space-2); + width: 100%; +} + +/* Sektion-Label (uppercase, klein, gedämpft) */ +.w3-section-label { + font-size: var(--text-xs); + font-weight: 700; + color: var(--c-text-secondary); + letter-spacing: 0.08em; + text-transform: uppercase; + margin-bottom: 10px; +} + +/* Schnell-Gassi Dauer-Button */ +.w3-dur-btn { + padding: 12px 6px; + border-radius: 12px; + border: 2px solid var(--c-border); + background: var(--c-bg-card, var(--c-surface)); + cursor: pointer; + font-weight: 700; + font-size: var(--text-sm); + color: var(--c-text); +} +.w3-dur-btn.active { + border-color: var(--c-primary); + background: var(--c-primary-subtle); + color: var(--c-primary); +} + +/* Submit-Button (volle Breite, primary, flex-center) */ +.w3-submit-btn { + width: 100%; + padding: 16px; + border-radius: 14px; + background: var(--c-primary); + color: white; + border: none; + cursor: pointer; + font-size: var(--text-base); + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +/* "Weitere Funktionen"-Link-Button */ +.w3-all-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + width: 100%; + padding: 12px; + border: none; + background: none; + cursor: pointer; + color: var(--c-primary); + font-size: var(--text-sm); + font-weight: 600; +} + +/* ---------------------------------------------------------- + Settings / Dog-Profile: Card-Sektion-Header + (uppercase Label mit Border-Bottom) + ---------------------------------------------------------- */ +.by-card-section-header { + padding: var(--space-3) var(--space-4); + font-size: var(--text-xs); + font-weight: 600; + color: var(--c-text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; + border-bottom: 1px solid var(--c-border); +} + +/* ---------------------------------------------------------- + Dog-Profile: Info-Card-Zeile (xs-Label + Wert) + ---------------------------------------------------------- */ +.dp-info-label { + font-size: var(--text-xs); + color: var(--c-text-secondary); + margin-bottom: 2px; +} + +/* Passport: Datensatz-Zeile */ +.pp-data-row { + display: flex; + align-items: flex-start; + gap: var(--space-3); + padding: var(--space-3) 0; + border-bottom: 1px solid var(--c-border); +} + +/* Settings: Sidebar-Item mit Padding */ +.settings-sidebar-item { + padding: var(--space-4); + border-radius: 0; + border-bottom: 1px solid var(--c-border); +} + +/* Settings: Toggle-Zeile (flex, icon + label + toggle) */ +.settings-toggle-row { + display: flex; + align-items: center; + gap: var(--space-3); + padding: var(--space-4); + border-bottom: 1px solid var(--c-border); +} + +/* Settings: Toggle-Label-Block */ +.settings-toggle-label { + flex: 1; +} + +/* Settings: Inline-Toggle (44×24px) */ +.by-toggle-wrap { + position: relative; + display: inline-block; + width: 44px; + height: 24px; + flex-shrink: 0; +} +.by-toggle-wrap input { + opacity: 0; + width: 0; + height: 0; + position: absolute; +} +.by-toggle-track { + position: absolute; + cursor: pointer; + inset: 0; + border-radius: 12px; + background: var(--c-border); + transition: background 0.2s; +} +.by-toggle-thumb { + position: absolute; + top: 2px; + width: 20px; + height: 20px; + border-radius: 50%; + background: #fff; + transition: left 0.2s; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); +} + +/* Settings: Version-Badge */ +.by-version-badge { + background: var(--c-surface-2); + border: 1px solid var(--c-border); + border-radius: 100px; + padding: 2px 10px; + font-family: monospace; + font-size: 10px; + color: var(--c-text-muted); +} + +/* Avatar-Kreis (56px, primary bg) */ +.by-avatar-circle { + width: 56px; + height: 56px; + border-radius: 50%; + background: var(--c-primary); + color: #fff; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + font-weight: 700; + flex-shrink: 0; + cursor: pointer; + overflow: hidden; + position: relative; +} diff --git a/backend/static/index.html b/backend/static/index.html index c3abcea..856efe8 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -578,7 +578,7 @@ - + @@ -640,7 +640,7 @@ // Wenn neuer SW die Kontrolle übernimmt → Seite neu laden navigator.serviceWorker.addEventListener('controllerchange', () => { - window.location.reload(); + window.location.replace('/?_t=' + Date.now()); }); navigator.serviceWorker.addEventListener('message', e => { diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 0fe4e0e..9887ed7 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,9 +3,11 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '760'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '774'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.5.0'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; +// Cache-Bust-Parameter nach Update-Reload sofort entfernen +if (location.search.includes('_t=')) history.replaceState(null, '', '/'); const App = (() => { @@ -1037,15 +1039,15 @@ const App = (() => { btn.textContent = 'Lädt…'; btn.disabled = true; sessionStorage.setItem('by_update_reload', APP_VER); + // ?_t= Timestamp zwingt iOS bfcache zur Aufgabe — wird beim Start sofort entfernt + setTimeout(() => location.replace('/?_t=' + Date.now()), 800); try { - // SW aktivieren + alle Caches leeren für sauberen Reload const reg = await navigator.serviceWorker?.getRegistration(); if (reg?.waiting) reg.waiting.postMessage({ type: 'SKIP_WAITING' }); - await reg?.update(); + reg?.update().catch(() => {}); // kein await — kann hängen const keys = await caches.keys(); await Promise.all(keys.map(k => caches.delete(k))); } catch { /* ignorieren */ } - setTimeout(() => location.reload(), 600); }); } @@ -1090,6 +1092,7 @@ const App = (() => { } return { init, navigate, callModule, state, setActiveDog, + hasPro: (user) => _hasPro(user ?? state.user), renderDogSwitcher: _renderDogSwitcher, getInstallPrompt: () => _installPrompt, requireAuth, showOnboarding: _showOnboardingModal, diff --git a/backend/static/js/pages/dog-profile.js b/backend/static/js/pages/dog-profile.js index 58d5aa7..85ce02d 100644 --- a/backend/static/js/pages/dog-profile.js +++ b/backend/static/js/pages/dog-profile.js @@ -108,8 +108,7 @@ window.Page_dog_profile = (() => { margin-bottom:var(--space-5);text-align:left"> ${geburtstag ? `
-
Geburtstag
+
Geburtstag
${geburtstag}
${_calcAlter(dog.geburtstag)} @@ -118,8 +117,7 @@ window.Page_dog_profile = (() => { ` : ''} ${dog.geschlecht ? `
-
${dog.geschlecht === 'm' ? '' : ''} Geschlecht
+
${dog.geschlecht === 'm' ? '' : ''} Geschlecht
${dog.geschlecht === 'm' ? 'Rüde' : 'Hündin'}
@@ -127,8 +125,7 @@ window.Page_dog_profile = (() => { ` : ''} ${dog.gewicht_kg ? `
-
Gewicht
+
Gewicht
${dog.gewicht_kg} kg
` : ''} @@ -202,7 +199,7 @@ window.Page_dog_profile = (() => { Visitenkarte teilen ` : ''} - ${!dog.is_guest ? `` : ''} ${!dog.is_guest ? `
`, @@ -676,7 +671,7 @@ window.Page_dog_profile = (() => { `; const footer = ` -
+
${hasPhoto ? `` : ''}
${hasPhoto ? `` : ''} @@ -1044,7 +1039,7 @@ window.Page_dog_profile = (() => { title: 'Weiteren Hund anlegen', body: _formHTML(null, true), footer: ` -
+
@@ -1061,7 +1056,7 @@ window.Page_dog_profile = (() => { title: `${dog.name} bearbeiten`, body: _formHTML(dog, true), footer: ` -
+
@@ -1657,8 +1652,7 @@ window.Page_dog_profile = (() => {
` : vaccs.map(v => `
+ class="pp-data-row">
${_esc(v.krankheit)}
@@ -1695,8 +1689,7 @@ window.Page_dog_profile = (() => {
` : meds.map(m => `
+ class="pp-data-row">
${_esc(m.name)}
@@ -2073,8 +2066,8 @@ window.Page_dog_profile = (() => { const modalEl = document.createElement('div'); modalEl.style.cssText = 'position:fixed;inset:0;z-index:9999;background:#0d0d1a;display:flex;flex-direction:column;overflow:hidden;'; modalEl.innerHTML = ` -
- +
+
${cards[0]}
diff --git a/backend/static/js/pages/settings.js b/backend/static/js/pages/settings.js index 0197f58..07cfab3 100644 --- a/backend/static/js/pages/settings.js +++ b/backend/static/js/pages/settings.js @@ -120,12 +120,7 @@ window.Page_settings = (() => {
-
+
${avatarInner}
-
Aktivität
+
Aktivität
Lädt…
@@ -238,9 +231,7 @@ window.Page_settings = (() => {
-
Trophäen
+
Trophäen
Lädt…
@@ -285,29 +276,31 @@ window.Page_settings = (() => { ⭐ Ban Yaro Pro kommt bald — mehr Features, mehrere Hunde.
` : ''} -
-
+
App-Einstellungen
-
+
-
+
Dark Mode
Erscheinungsbild der App @@ -329,9 +322,9 @@ window.Page_settings = (() => {
-
+
-
+
KI-Notiz-Assistent
Erkennt Muster in deinen Notizen und macht Vorschläge @@ -353,9 +346,9 @@ window.Page_settings = (() => {
-
+
-
+
Goldene Gassi-Stunde täglich
Täglich um 07:00 Uhr: bestes Wetterfenster für den Gassi-Gang @@ -392,10 +385,7 @@ window.Page_settings = (() => {
-
+
App installieren
@@ -711,7 +701,7 @@ window.Page_settings = (() => { `, footer: ` -
+
@@ -747,27 +737,24 @@ window.Page_settings = (() => { } if (btn) btn.textContent = 'Prüfe…'; try { - // Aktuelle Version vom Server holen (no-cache) - const serverResp = await fetch('/js/app.js', { cache: 'no-store' }); - const serverText = await serverResp.text(); - const match = serverText.match(/APP_VERSION\s*=\s*'([^']+)'/); - const serverVersion = match?.[1] || null; - const localVersion = typeof APP_VERSION !== 'undefined' ? APP_VERSION : '0'; + // Versionsnummer direkt vom API-Endpunkt holen + const r = await fetch('/api/version', { cache: 'no-store' }); + const { version: serverVersion } = await r.json(); + const localVersion = typeof APP_VER !== 'undefined' ? APP_VER : '0'; - // SW update anstoßen const reg = await navigator.serviceWorker.getRegistration(); - await reg?.update(); + reg?.update().catch(() => {}); // kein await — kann hängen if (serverVersion && serverVersion !== localVersion) { - // Neuere Version verfügbar — Seite neu laden if (reg?.waiting) reg.waiting.postMessage({ type: 'SKIP_WAITING' }); - UI.toast.info(`Update auf v${serverVersion} verfügbar — Seite wird neu geladen…`); - setTimeout(() => location.reload(), 1500); + UI.toast.info(`Update auf v${serverVersion} — Seite wird neu geladen…`); + setTimeout(() => location.replace('/?_t=' + Date.now()), 1500); } else if (reg?.waiting) { reg.waiting.postMessage({ type: 'SKIP_WAITING' }); UI.toast.success('Update wird installiert…'); + setTimeout(() => location.replace('/?_t=' + Date.now()), 1500); } else { - UI.toast.success(`Ban Yaro ist aktuell — v${localVersion}`); + UI.toast.success(`Ban Yaro ist aktuell — Build ${localVersion}`); } } catch { UI.toast.error('Update-Prüfung fehlgeschlagen.'); @@ -1039,12 +1026,7 @@ window.Page_settings = (() => { slot.innerHTML = `
-
- Züchter-Profil -
+
Züchter-Profil
${statusBadge} ${actionBlock} @@ -1266,7 +1248,7 @@ window.Page_settings = (() => { `, footer: ` -
+
diff --git a/backend/static/js/pages/wetter.js b/backend/static/js/pages/wetter.js index 31d6a4f..8eae462 100644 --- a/backend/static/js/pages/wetter.js +++ b/backend/static/js/pages/wetter.js @@ -450,7 +450,7 @@ window.Page_wetter = (() => {
- ${_esc(compass)} · ${Math.round(d.windspeed_max ?? 0)} km/h + ${_esc(compass)} · ${Math.round(d.wind_kmh ?? 0)} km/h
${_esc(bft)}
@@ -803,7 +803,7 @@ window.Page_wetter = (() => { let score = 10; const temp = d.temp_max ?? 20; const precip = d.precip_prob ?? 0; - const wind = d.windspeed_max ?? 0; + const wind = d.wind_kmh ?? 0; const asphalt = d.asphalt_temp ?? 0; // Temperatur (ideal: 10–20°C) @@ -998,7 +998,7 @@ window.Page_wetter = (() => { const temp = d.temp_max ?? 20; const tempMin = d.temp_min ?? temp; const precip = d.precip_prob ?? 0; - const wind = d.windspeed_max ?? 0; + const wind = d.wind_kmh ?? 0; const asphalt = d.asphalt_temp ?? 0; const wcode = d.weathercode ?? 0; const isSnow = wcode >= 71 && wcode <= 77; diff --git a/backend/static/js/worlds.js b/backend/static/js/worlds.js index 438dd1f..4fcd9de 100644 --- a/backend/static/js/worlds.js +++ b/backend/static/js/worlds.js @@ -274,28 +274,20 @@ window.Worlds = (() => { const ov = document.createElement('div'); ov.id = 'fab-overlay'; - ov.style.cssText = 'position:fixed;inset:0;z-index:460;display:flex;flex-direction:column;justify-content:flex-end'; + ov.className = 'w3-sheet-overlay'; ov.innerHTML = ` -
-
-
-
${options.length ? title : 'Schnellzugriff'}
-
${options.map(o => ` - `).join('')}
- @@ -353,20 +343,14 @@ window.Worlds = (() => { if (!chips.length) return ''; return `
-
${worldLabels[w]}
+
${chips.map(c => ` - `).join('')}
@@ -376,16 +360,13 @@ window.Worlds = (() => { const ov = document.createElement('div'); ov.id = 'fab-overlay'; - ov.style.cssText = 'position:fixed;inset:0;z-index:460;display:flex;flex-direction:column;justify-content:flex-end'; + ov.className = 'w3-sheet-overlay'; ov.innerHTML = ` -
-
-
-
Ausgeblendete Funktionen
-
@@ -430,7 +411,8 @@ window.Worlds = (() => { const ov = document.createElement('div'); ov.id = 'quick-gassi-overlay'; - ov.style.cssText = 'position:fixed;inset:0;z-index:400;display:flex;flex-direction:column;justify-content:flex-end'; + ov.className = 'w3-sheet-overlay'; + ov.style.zIndex = '400'; const weatherLine = weatherData ? `
@@ -438,20 +420,17 @@ window.Worlds = (() => {
` : ''; ov.innerHTML = ` -
-
-
+
+
+
-
🐾 Schnell-Gassi
+
🐾 Schnell-Gassi
${_esc(dog.name)} · ohne GPS
${weatherLine}
-
@@ -459,20 +438,13 @@ window.Worlds = (() => {
Dauer
${durations.map(d => ` - `).join('')}
- @@ -490,10 +462,7 @@ window.Worlds = (() => { btn.addEventListener('click', () => { selectedMin = parseInt(btn.dataset.min); ov.querySelectorAll('.qg-dur').forEach(b => { - const active = parseInt(b.dataset.min) === selectedMin; - b.style.borderColor = active ? 'var(--c-primary)' : 'var(--c-border)'; - b.style.background = active ? 'var(--c-primary-subtle)' : 'var(--c-bg-card)'; - b.style.color = active ? 'var(--c-primary)' : 'var(--c-text)'; + b.classList.toggle('active', parseInt(b.dataset.min) === selectedMin); }); }); }); @@ -505,15 +474,8 @@ window.Worlds = (() => { submitBtn.textContent = 'Wird eingetragen…'; try { - const payload = { - typ: 'gassi', - titel: 'Schnell-Gassi 🐾', - text: `Kurze Runde, ${selectedMin} Minuten`, - }; - if (weatherData) { - payload.weather_json = JSON.stringify(weatherData); - } - await API.post(`/dogs/${dog.id}/diary`, payload); + // Kein Tagebucheintrag — nur Streak pingen + await API.post(`/streak/${dog.id}/ping`); _close(); UI.toast?.success(`Gassi eingetragen! ${selectedMin} min 🐾`); // Streak-Cache invalidieren @@ -1103,7 +1065,7 @@ window.Worlds = (() => { ${alertHtml} ${user && dog ? `
-
+
${weatherEmoji}
@@ -1112,7 +1074,7 @@ window.Worlds = (() => { ${gassiScore ?? '—'} ${gassiScore ? `/10` : ''}
- ${w ? `
${Math.round(w.temp_c ?? 0)}° · ${w.precip_prob ?? 0}% Regen
` : ''} + ${w ? `${Math.round(w.temp_c ?? 0)}° · ${w.precip_prob ?? 0}% Regen` : ''}
diff --git a/backend/static/sw.js b/backend/static/sw.js index ba39608..171d7f4 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v760'; +const CACHE_VERSION = 'by-v774'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache @@ -292,7 +292,8 @@ self.addEventListener('fetch', event => { } return response; }) - .catch(() => caches.match(event.request)) + .catch(() => caches.match(event.request) + .then(cached => cached || new Response('', { status: 503 }))) ); return; } @@ -324,9 +325,8 @@ self.addEventListener('fetch', event => { }) ) .catch(() => { - if (event.request.mode === 'navigate') { - return caches.match('/'); - } + if (event.request.mode === 'navigate') return caches.match('/'); + return new Response('', { status: 503 }); }) ); }); @@ -344,6 +344,10 @@ self.addEventListener('sync', event => { // MESSAGE — Tile-Vorausladung (Offline-Speicherung) + Queue-Steuerung // ---------------------------------------------------------- self.addEventListener('message', event => { + if (event.data?.type === 'SKIP_WAITING') { + self.skipWaiting(); + return; + } if (event.data?.type === 'PROCESS_QUEUE') { event.waitUntil(_processQueue()); return;