Stufe 1 (Guard): Während aktiver Aufzeichnung wird der SW-/Force-Update-Reload
aufgeschoben (window._byRecording → boot.js/_bySwReload + app.js force-update);
nach Stop/Speichern via window._byReloadIfPending() nachgeholt.
Stufe 2 (Persistenz): Track wird gedrosselt nach localStorage (RecStore) gesichert
und beim nächsten Öffnen der Karten-/Routen-Seite als 'Aufzeichnung fortsetzen?'
angeboten (Resume seedet Track+km+Startzeit). Schützt auch bei Crash/OS-Kill/
manuellem Reload. Greift in map.js UND routes.js. SW v1167
- record_walk gibt total_km zurück → Toast "🐾 X km gezählt · Lebenswerk Y km".
- Nachtrag-Toast (_flushPendingNavWalk) zeigt ebenfalls km + Lebenswerk.
- list_routes liefert my_walk_count + my_last_walked → Routen-Karte zeigt
"🐾 X× gelaufen · zuletzt heute/gestern/vor N Tagen".
Macht für Angie sichtbar, dass das Ablaufen einer gespeicherten Route mitzählt.
In-App-Zurück geschlossen wird (Angie-Bug)
Bisher wurde walked_km NUR in _closeNav (In-App-Zurück-Pfeil) gespeichert.
Wer die Navigation anders verlässt (Handy sperren/Home/PWA schließen, oder
nur den Dim-Entsperrpfeil + normal schließen), verlor die km.
- Fortschritt laufend in localStorage sichern (überlebt App-Kill).
- _recordNavWalk() Einmal-Guard, aufgerufen von _closeNav UND pagehide.
- _flushPendingNavWalk() trägt beim nächsten App-Start einen nicht
gespeicherten Walk nach.
- Fehler nicht mehr still verschlucken: bleibt in localStorage → Retry.
- /osm-auth/status liefert signup_url + sandbox-Flag (Sandbox-URL auf Staging,
echte OSM in Prod).
- Settings-OSM-Karte: ausklappbare Hilfe "Noch kein OSM-Konto? Was ist das?"
mit Erklärung, 3-Schritt-Anleitung, Sandbox-Testphasen-Hinweis und
"Kostenloses OSM-Konto erstellen"-Link zur richtigen Instanz.
- dog=no zusätzlich zu dog=yes (Pächterwechsel → Ort nicht mehr hundefreundlich).
- Map-Popup: ein "Hund willkommen?"-Block mit Daumen hoch/runter statt zwei
Buttons. Beide rufen /dog-friendly mit welcome=true|false.
- Backend generisch: tag_value yes|no; vorhandene Markierung mit anderem Wert
wird umgedreht (Update statt 409); submit_dog_tag(value); Confirm/Revert prüft
gegen den jeweiligen tag_value; Changeset-Kommentar wertabhängig.
Bisher: wenn eine Route Fotos hatte, zeigte die Karten-Übersicht das
erste Foto statt der Mini-Map → kein Vergleich der Tracks auf einen
Blick möglich.
Jetzt: die Mini-Map ist immer da, ein kleiner Kamera-Badge oben rechts
zeigt die Anzahl Fotos an (analog zum Tagebuch). Die Fotos sind weiter
unverändert in der Route-Detail-Ansicht zu sehen.
CSS: .rk-card-preview bekommt position:relative für das Badge.
- settings.js Header-Badge unter dem Namen leitet jetzt aus
subscription_tier ab (analog _tierCard / has_pro_access): Admin/
Moderator, Züchter, Pro, sonst 'Kostenlos'. Vorher las nur das alte
is_premium-Flag, was beim Admin-Upgrade nicht mitgezogen wurde.
- admin.py fulfill_upgrade_request setzt jetzt is_premium synchron mit
subscription_tier (1 für pro/breeder, sonst 0). Hält Login-Response,
/auth/me und Reports konsistent.
Wer aus dem Onboarding (Schritt 1 'Los geht's' navigiert direkt auf
#dog-profile) keinen Hund anlegen will, war bisher in der Form
festgehängt — kein Skip, kein Zurück.
Jetzt: ghost-Button unter dem Submit, setzt by_onboarding_done und
schickt zurück auf die Welten/Welcome. Bumpe auf 1135.
User-Report: Zeilenumbrüche in Notes-Karten gingen nicht, kein Scroll,
keine Detail-Ansicht.
Drei Probleme behoben:
1. _truncate-Limit zu aggressiv (150 Zeichen)
→ erhöht auf 600 Zeichen damit Karten lange Notizen mit Newlines
sichtbar anzeigen können (CSS-Clamp erledigt visuell den Rest)
2. .list-item-text + .notes-card-text Override-Konflikt
list-item-text hat fest -webkit-line-clamp:2 mit display:-webkit-box.
Notes-Override hatte display:block — das deaktiviert clamp komplett,
aber dann zeigt der Text die ersten 150 Zeichen ohne Newline-Hinweis.
→ Neuer Override: display:-webkit-box + -webkit-line-clamp:5 +
white-space:pre-wrap → 5 Zeilen mit Newlines sichtbar, Rest '…'
3. Keine Detail-Ansicht beim Klick auf Karte
→ Neue Funktion _openDetailModal(note):
- Voller Notiz-Text scrollbar (.notes-detail-text mit max-height:60vh)
- Rubrik-Icon + Label im Titel
- Parent-Label, Micro-Badges, Meta (Zeit + Ort)
- Footer: 'Bearbeiten' (öffnet Edit-Modal) + 'Schließen'
→ Card-Click bindet darauf; Klicks auf Action-Buttons werden via
closest('.list-item-action-btn') ignoriert (kein doppeltes Handling)
Bündel 1 aus dem Duplikat-Audit: existierende zentrale Helper nutzen
statt lokale Duplikate.
Pure Migration ohne neuen Code:
- 1167 _esc()-Aufrufe in 36 Page-Modulen migriert auf UI.escape()
- 24 lokale _esc/_escape-Definitionen entfernt
- lost.js hatte _escape() (Variante) — 17 Aufrufe ebenfalls migriert
- jobs.js + breeder.js: tote Alias-Wrapper entfernt
UI.escape() existierte schon — wurde nur überall lokal nochmal
implementiert. Funktional identisch (gleiche 4-replace-chain für
& < > ").
Tests 19/19 grün. Frontend-LOC um ~120 Zeilen reduziert.
Hinweis: _emptyState (7 Stellen) und _icon (8 Stellen) wurden NICHT
migriert — sie haben abweichende Signaturen von UI.emptyState({...})
bzw. UI.icon(name). Eigener Sprint nötig.
User-Report: trotz onerror-Fallback weiter Fragezeichen.
Ursache: Das _preview.webp-System wurde damals nur konsequent für
Diary-Uploads ausgerollt. User-Avatare und Hund-Profilbilder haben
keine Preview-Variante → 404 vom _preview triggert kurz das
Browser-Default-Broken-Image-Icon BEVOR der onerror-Fallback das
Original lädt (Race-Condition).
Pragmatischer Fix: Preview-System in friends.js rückgebaut. Bilder
werden direkt mit Original-URL geladen. Performance kommt durch:
- loading=\"lazy\" (off-screen Bilder erst beim Scrollen)
- decoding=\"async\" (Main-Thread bleibt frei)
- onerror=\"this.style.display='none'\" (kaputte Bilder verschwinden
statt Fragezeichen zu zeigen)
UI.previewUrl + UI.previewFallback bleiben als Helper verfügbar
für später falls das Preview-System app-weit ausgerollt wird.
Symptom: Friends-Seite lädt Avatare langsam — Original-Bilder
(z.B. 4-12MB iPhone-Fotos) statt der vorhandenen _preview.webp
Vorschauen.
Neue zentrale Helper in ui.js:
- UI.previewUrl(url): ersetzt /media/...jpg → /media/..._preview.webp
- UI.previewFallback(originalUrl): onerror-Handler der Original
nachlädt falls _preview nicht existiert (für ältere Uploads)
friends.js 3 Stellen migriert:
- _userAvatar (Freundes-Karte + Aktivitäts-Feed)
- Activity-Avatar (dog_foto + avatar_url)
- Dog-Mini-Thumbs im Profil-Modal
Zusätzlich auf allen drei Stellen:
- loading="lazy" für off-screen Bilder
- decoding="async" damit der Hauptthread nicht blockiert
Reuse-Potential: wiki.js, dog-profile.js und andere können später
auf die zentralen Helper umgestellt werden.
AT:
- VetMedUni Wien Kleintier-Notdienst (24h): +43 1 25077-6900
CH:
- Tox Info Suisse: 145 (in CH gratis) bzw. international +41 44 251 51 51
(offizielle Notruf-Nummer auch für Tiergifte)
- Tierspital Zürich Kleintier-Notfall (24h): +41 44 635 83 37
Damit alle TODO-Platzhalter aus der Sprint60-Erweiterung der
Erste-Hilfe-Notfallnummern jetzt mit echten Nummern befüllt.
- /admin/stats liefert jetzt zusätzlich user_poi_total + user_poi_by_type
(User-POIs aus user_map_pois aufgeschlüsselt, komma-separierte Typen
werden einzeln gezählt)
- Admin System-Tab zeigt zwei Karten:
· OSM-Cache nach Typ (was Overpass-Cache enthält)
· Nutzer-POIs nach Typ (selbst erstellte Marker)
- Interne Typ-Namen werden in lokalisierte Labels gemappt
(tierarzt → Tierarzt, hundesalon → Hundesalon, etc.)
- Header der Karten zeigt Gesamtzahl inline
- OSM-Query 'hundesalon' nutzt shop=pet_grooming + craft=pet_grooming
- map.js: neuer Layer 'hundesalon' mit Schere-Icon (#EC4899 Pink),
in Layer-Liste, TYPEN, OSM_LAYER_MAP und PIN_TYPES eingetragen
- User können selbst Hundesalons als POI anlegen
(places.py TYPEN + osm.py ALLOWED_TYPES erweitert)
Beide nutzen jetzt by_last_position aus localStorage:
- wetter.js: speichert jede erfolgreiche GPS-Position; bei GPS-Fehler
(kein Netz, Permission verweigert) wird der letzte bekannte Ort
als Fallback genutzt — kein Error-Banner mehr wenn man ihn schon
einmal hatte
- offline-indicator.js: _prefetchTiles versucht erst GPS, fällt
dann auf den gespeicherten letzten Ort zurück → Step 5 wird auch
ohne aktive GPS-Permission grün, sobald Wetter (oder andere
Module) einmal eine Position eingeloggt haben
- TILE_MIN von 50 auf 20 gesenkt — 5x4 Tiles reichen für eine
brauchbare Offline-Karte im Nahbereich
Footer-Layout neu strukturiert — kein Umbruch-Chaos mehr:
- Erste Zeile: Abbrechen | Speichern (Grid 1fr 1fr, gleich breit)
oder bei sent/paid nur 'Schließen' volle Breite
- Zweite Zeile (wenn vorhanden): Stornieren als volle Breite,
ghost-Style mit rotem Rand — destruktive Aktion klar getrennt
- Button-Text 'Änderungen speichern' → 'Speichern' (kein Abschneiden
mehr auf iPhone)
- Backend: /admin/upgrade-requests liefert pro Request die offene
Rechnung (id+number+status) per Subquery aus der invoices-Tabelle
(status draft|sent → also nicht bezahlt, nicht storniert)
- Frontend: Wenn schon eine Rechnung existiert, wird statt 'Rechnung
erstellen' (orange) der Button 'Rechnung bearbeiten' (gelb,
#eab308) gezeigt. Klick lädt die Rechnung und öffnet das Modal im
Edit-Modus — kein doppeltes Anlegen, Nummerierung bleibt sauber.
- 'null is not an object (wrap2.remove)': Wrapper-Div hat keine
Klasse .diary-media-thumb-wrap → closest() lieferte null. Fallback
auf btn.parentElement + Null-Check vor remove()
- Bei 404 'Medium nicht gefunden' wird das verwaiste Foto jetzt
trotzdem lokal aufgeräumt (entry.media_items + DOM), statt einen
Error-Toast zu zeigen. Verwaiste Phantome verschwinden so beim
ersten Lösch-Klick.
- Toggle-Buttons oben im Protokoll-Tab
- 'Nach Übung': gruppiert alle Sessions pro Übung, sortiert nach zuletzt trainiert
- Pro Übung: Ø-Erfolgsquote als Kreis, Trend-Pfeil (↑↓→★), Anzahl Einheiten + TOP-Count
- Aufklappbare Session-Liste pro Übung (Datum · Emoji · % · Wdh.)
- Hinweis wenn mehr Sessions vorhanden als geladen
- Verlauf: _verlaufLoading-Flag verhindert parallele Loads
- Verlauf: el nach await neu holen (stale DOM-Referenz nach Re-Render)
- Verlauf: bei _renderContent() Shell nur rendern wenn keine Sessions im Cache
- Backend: hund_stimmung/zufriedenheit als Optional[str/int] → akzeptiert null
- Neuer Tab 'Protokoll' in der Übungen-Seite: zeigt alle Trainingseinheiten
chronologisch nach Datum gruppiert (Heute/Gestern/Datum-Label)
- Jede Einheit: Übungsname, Wdh., Erfolgs-Emoji, Stimmung, Sterne, Notiz, TOP-Badge
- 'Weitere laden' Pagination (30 Einheiten pro Seite)
- Backend: Training erstellt keine Tagebuch-Einträge mehr (weder bei ist_top noch manuell)
- Frontend: 'Als Meilenstein ins Tagebuch' Checkbox komplett entfernt
- onDogChange setzt Verlauf-State zurück
Nach Object.assign(_appState.user, updated) wurde Worlds.refresh() nie
aufgerufen → JETZT-Welt zeigte alten Render ohne Geburtstags-Greeting.
SW by-v1030, APP_VER 1030