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
Äußerer Wrapper hatte max-width:860px aber kein margin:0 auto → klebte links.
Toolbar und List-Container bekamen fehlende Seitenabstände.
SW by-v1015, APP_VER 1015
Beim ersten offline-Event pro Session erscheint ein blauer Info-Toast (8s):
'App im Vordergrund lassen — so bleiben Offline-Funktionen wie GPS und
Datenspeicherung aktiv.'
sessionStorage-Guard verhindert Wiederholung. SW by-v997, APP_VER 997
_startRecInOvl() crashte bei null _recMap auf L.polyline().addTo(_recMap) →
WakeLock, watchPosition, _resetRecInactTimer() wurden nie erreicht → Dim-Screen
wurde nie aktiviert, GPS-Track lief nicht.
- L.polyline nur erstellen wenn _recMap && window.L vorhanden
- watchPosition-Callback: _recPolyline?.addLatLng, _recLocMarker?.setLatLng,
_recMap?.setView alle mit Optional Chaining gesichert
- SW by-v996, APP_VER 996
L.map() warf ReferenceError wenn Leaflet offline nicht geladen → _openRecOvl()
crashte, Event-Listener für #rk-rec-cancel und #rk-rec-startbtn wurden nie
angehängt. Fix:
- Listener direkt nach appendChild() registrieren (vor jeder async-Operation)
- Map-Setup in try/catch; bei fehlendem Leaflet: Offline-Platzhalter im Map-Bereich
- _recMap?.setView / _recLocMarker?.setLatLng mit Optional Chaining (null-safe)
- SW by-v994, APP_VER 994
Statt sofort zu schließen zeigt das Modal nach dem Submit eine Bestätigung:
'Wir kümmern uns darum und melden es den anderen Nutzern in der Umgebung.
Vielen Dank, dass du die Community schützt!'
Auto-Close nach 5 Sekunden, OK-Button zum sofortigen Schließen.
Bei gequeuter Meldung (offline) zusätzlicher Hinweis auf spätere Synchronisierung.
SW by-v993, APP_VER 993
Wenn der SW einen POST in die Offline-Queue legt, gibt api.js { _queued: true }
zurück (202). Ohne Guard versuchten poison/walks/diary den Response als echtes
Server-Objekt zu nutzen → undefined lat/lon → Leaflet-Crash, undefined id → Upload-Fehler.
Nach dem Guard wird das Modal nur geschlossen; der QUEUE_PROCESSED-Toast informiert
den User sobald synchronisiert.
- poison.js: _queued guard nach API.poison.report()
- walks.js: _queued guard + try-catch statt navigator.onLine
- diary.js: _queued guard nach API.diary.create()
- SW by-v992, APP_VER 992
- Pulsierender Marker: Wechsel von position:absolute-Ring auf box-shadow-Animation
(by-lost-pulse-r/p), kein Overflow-Problem mit Leaflet divIcon, iOS-kompatibel
- navigator.onLine iOS-Falsch-Positiv: Formular-Submit versucht API zuerst,
fällt nur bei TypeError (fetch failed) auf Pending-Modus zurück
- _openDetail(): früher Return für Pending-Einträge (verhindert delete mit
string-ID "pending_..." → Backend-Fehler "unable to parse integer")
- SW by-v991, APP_VER 991
- Deduplication in _loadReports(): Pending-Einträge die bereits auf dem Server
sind (Race-Condition beim Sync) werden automatisch aus dem Pending-Store entfernt
- Verwerfen-Button für offline-gespeicherte Meldungen (pending), Notiz-Button nur
für Server-Einträge sichtbar
- Pulsierender Kreis-Marker (CSS @keyframes by-lost-ping) statt statischem Pin;
Pending-Einträge in Orange, Server-Einträge in Rot
- Card-Click für pending deaktiviert (kein Detail-Modal für unsynchronisierte Daten)
- worlds.js: Alert-Radius für vermisste Hunde von 5 auf 20 km erhöht (wie Giftköder)
- SW by-v990, APP_VER 990
- sw.js: /api/places, /api/breeder/map-markers, /api/gassi-zeiten in _CACHEABLE_GET; /api/lost/report und /api/walks in _QUEUEABLE
- diary.js: localStorage-Cache pro Hund, Fallback bei Offline mit Toast
- poison.js: localStorage-Cache, Fallback bei Offline mit Toast (sicherheitsrelevant)
- map.js: POI-Cache (places/poison/breeders) in localStorage, Offline-Toast + Fallback auf gecachte Daten
- worlds.js: 'Datenschutz · AGB' in der Welt-Welt-Fußzeile
- settings.js: AGB-Checkbox über Widerrufs-Checkbox; beide müssen gecheckt sein bevor 'Anfrage senden' aktiv wird