User-Report: nach Sprint-Migration auf _preview.webp tauchen
Fragezeichen-Icons auf — wenn weder Preview noch Original verfügbar.
Probleme im vorigen Fix:
- UI.escape() ist HTML-Escape, kein JS-String-Escape → URL mit
?param=value wurde &-encoded und damit kaputt
- 'opacity:0.3' lässt das Browser-Default-Broken-Image-Icon
durchscheinen (Fragezeichen sichtbar)
- Kein Loop-Schutz beim onerror
Fixes:
- String-Escape via .replace(/'/g, \"\\'\") statt UI.escape()
- display:none + .img-broken-Klasse bei finalem Fehler
- dataset.fb='1' verhindert Endlos-Loop wenn Original-URL auch 404
- Wenn URL nicht mit /media/ startet: direkt ausblenden (keine
Preview-Variante zu probieren)
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.
User-Report: 'Leckerlis'-Screen verschwindet nicht mehr.
Bug: Vorige Version nutzte 'await Promise.all([sw-unregister,
caches.delete])' VOR dem Reload. Auf iOS-PWA können diese Promises
gelegentlich nie resolven → Reload kommt nie.
Fix /force-update:
- Cleanup-Tasks fire-and-forget (kein await, kein Promise.all)
- Sofort-Reload nach 150ms (kein await-Block)
- Fallback 1: Nach 3s erscheint 'App neu starten'-Button für
manuellen Tap
- Fallback 2: Nach 6s automatisch location.href mit ?hard=1
Fix app.js Cooldown:
- localStorage statt sessionStorage — überlebt PWA-App-Close
- 10 Min statt 5 Min Cooldown (großzügiger Spielraum bei
Update-Wellen)
Symptom: 'Einen Moment, wir besorgen neue Leckerlis' Loading-Screen
erscheint beim User wiederholt beim Wechsel in andere Bereiche.
Ursachen:
1. In dieser Session wurden viele Bumps in kurzer Zeit ausgerollt
(1100 → 1104). Jeder Versions-Mismatch zwischen App-Cache und
Server triggert force-update.
2. /force-update Cache-Delete war fire-and-forget mit nur 1.5s
Reload-Timer — auf iOS-PWA oft zu kurz für asynchrone unregister/
caches.delete, daher landete der Reload manchmal noch im alten
Cache-Stand → erneuter Mismatch → erneuter force-update.
Fixes:
- app.js: Cooldown 5 Min nach force-update — verhindert Loop bei
mehrfachen schnellen Bumps. Mismatch wird erkannt aber nicht mehr
sofort reagiert.
- /force-update: async/await für SW-Unregister + Cache-Delete bevor
Reload. Safety-Timeout 4s. Reload-URL mit ?_t= Cache-Bust.
Neue zentrale Komponenten in ui.js:
1. UI.errorState({icon, title, message, retry})
Dedizierte Error-UI statt nur Toast. Mit optionalem Retry-Button
(asyncButton-integriert). Analog zu UI.emptyState. Behebt
Inkonsistenzen: Toast vs. ad-hoc HTML vs. Empty-State-Reuse.
2. UI.skeletonList(count)
Karten-Skeleton für Listen-Loading (Avatar + 2 Zeilen pro Item).
Erweitert UI.skeleton(lines) — beide bleiben verfügbar.
3. UI.moneyInput({name, value, currency='€', placeholder})
Euro-Input mit Prefix + locale-Format (Komma als Dezimal-
trenner). Extrahiert aus expenses.js Best-Practice.
Plus UI.parseMoney(str) als Parser-Helper.
4. UI.datePicker({name, label, value, min, max, required})
Standard-Date-Input mit Label, min/max ('today' wird zu ISO-
Datum konvertiert). Vereinfacht Form-Boilerplate.
5. UI.map.create(containerId, opts)
Zentraler Leaflet-Init mit OSM-Tiles + Dark-Mode-Filter-Option.
Konstanten OSM_URL + OSM_MAX_ZOOM zentral. Plus UI.map.svgMarker
für eigene divIcon-Marker (für events.js Diamant, lost.js Puls).
Alle Helper sind backward-kompatibel — bestehende Patterns funktionieren
weiter. Tests 19/19 grün. Migration der Aufrufer kommt in Sprint C+D.
- /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)
main.py liest APP_VER aus /app/VERSION beim Container-Start.
Dockerfile kopierte aber nur backend/, nicht die VERSION im Root.
→ /api/version lieferte '0'.
Fix: explizit COPY VERSION /app/VERSION ergänzt.