_check_post_limits verglich datetime.utcnow() (UTC) mit created_at, das aber
als Client-Lokalzeit (CEST=UTC+2) gespeichert wird → diff negativ → immer 429.
Jetzt rechnen Cooldown + Stunden-Limit in derselben Zeitbasis (Client-Zeit des
Requests). Backend-only, kein SW-Bump.
- update_streak NICHT mehr in GET /achievements/me (lief bei jedem Öffnen).
Streak wird nur noch von create_route (aufnehmen) + record_walk (ablaufen)
hochgezählt.
- Angezeigter Streak korrekt: nur gültig wenn letzte Tour heute/gestern war,
sonst 0 (gerissen) — ohne den gespeicherten Wert zu verändern.
- 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.
Apple-Reviewer braucht eine publik erreichbare Support-URL. Die
SPA-Hilfeseite (/#hilfe) ist hinter dem Welcome-Overlay für nicht
angemeldete User versteckt. Neue /help-Route rendert serverseitig:
- Holt aktive FAQ-Artikel aus help_articles (über bestehendes
TTL-Cache _load_active_help_articles).
- Gruppiert nach Kategorie mit deutschen Labels.
- Native HTML5 <details>/<summary> Akkordeon — kein JS nötig.
- Dark Mode via prefers-color-scheme.
- Direkter mailto support@banyaro.app + Verweis auf die volle
Hilfe nach Login.
Damit haben wir https://banyaro.app/help als Support-URL für App
Store Connect.
Bug: daily_photo_cache zeigte auf gelöschte Tagebuch-Foto-URLs, weil
Löschen eines Eintrags oder einzelnen Medien-Items den Cache nicht
mit-bereinigte. Heim-Tab in der iOS-App lud dann 404 → kein Tagesbild.
Fix in dogs.py /welcome-dashboard:
- Bevor das Cache-Foto zurückgegeben wird, prüfen ob die URL noch in
diary_media existiert. Wenn nicht: Cache-Eintrag löschen und neu
wählen → selbstheilend für alte verwaiste Einträge.
Fix in diary.py:
- delete_diary: vor dem CASCADE-Delete von diary_media die URLs
sammeln und alle daily_photo_cache-Zeilen darauf löschen.
- delete_media_item: gleicher Cleanup für die eine URL.
Cache ist klein (max 1 Eintrag pro Hund pro Tag) — Hygiene-Cleanup
ist günstig und macht das System defensiv.
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.
Bisher: App.navigate() rief nur dann Worlds.hide() (und damit
worlds-back-visible) wenn Worlds gerade sichtbar war. Wer aus dem
Onboarding direkt nach #dog-profile navigiert (kein vorheriges
Worlds-Anzeigen) hatte keinen Zurück-Pfeil zu den Welten + FAB —
saß auf dem Profil fest.
Fix: in navigate() unabhängig vom Worlds-State die Klasse
worlds-back-visible setzen, sobald ein eingeloggter User auf einer
nicht-welcome/onboarding-Seite ist. Bump 1136→1137.
- admin.py delete_user: löscht jetzt auch Hund-zentrierte Daten (diary,
health, training_sessions, training_streaks, expenses), dogs,
upgrade_requests, push_subscriptions, notifications, forum_posts
bevor der User-Row weg ist. Vorher: nur DELETE FROM users → Waisen in
allen FK-Tabellen.
- profile.py delete_account: gleicher Cleanup-Set, vergisst jetzt
upgrade_requests nicht mehr.
- admin.py Dashboard-Counter 'Zu Erledigen': JOIN users, damit
verwaiste Anfragen nicht mehr im Header-Badge erscheinen (Liste
selbst filtert sie schon korrekt via JOIN). Bump 1135→1136.
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.
Neue Tabelle daily_photo_cache(dog_id, datum, photo_url) hält die Wahl
fest, sobald irgendein Client (PWA oder iOS) das erste Mal heute fragt.
Alle nachfolgenden Aufrufe liefern dieselbe URL — kein Kippen mehr, wenn
zwischendrin neue Fotos hochgeladen werden und die tick%len-Rotation auf
einen anderen Index zeigt. CREATE TABLE IF NOT EXISTS inline, daher kein
Migrations-Eingriff nötig.
KATEGORIE_META als Dict mit id → {label, color} (preserved Order).
KATEGORIEN bleibt als Set für Validierung. Neuer Endpunkt liefert
[{id, label, color}, …] als Single Source of Truth für PWA und mobile
Clients — bisher war die Liste in JS und Python dupliziert.
Mobile-Client (banyaro-ios) holt die Liste jetzt dynamisch.
Aus Container-Log gefundene Backend-Errors:
1. _job_anniversary_reminders: 'no such column: d.user_id'
diary-Tabelle hat keine user_id — User-Bezug geht über dogs.user_id.
→ JOIN dogs ON dogs.id = d.dog_id ergänzt + SELECT dogs.user_id.
Job läuft täglich 09:00 — war seit Tag X kaputt, kein Push für
Jahrestage gesendet.
2. RASFF API 404 (EU Rapid Alert System for Food and Feed):
webgate.ec.europa.eu/rasff-window/backend/public/... ist umgezogen.
→ HTTPStatusError mit 404/410/503 wird jetzt nur als WARNING geloggt
(vorher ERROR → Error-Digest spammte täglich). Fallback ist eh schon
ein leeres Array, App läuft weiter. EU-Endpoint-URL muss nochmal
recherchiert werden, dann RASFF_URL aktualisieren — Folge-Sprint.
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)
Container startete mit USER appuser nicht: SQLite gibt
'attempt to write a readonly database' — Synology DSM Volume-
Permissions blockieren chown auf gemountete Pfade.
User-Anlage (groupadd/useradd) bleibt im Dockerfile, plus
chown nach mkdir. Nur die USER-Zeile ist auskommentiert mit
Kommentar warum. Für Non-DS-Deployments einfach Zeile
aktivieren.
VAPID-Keys-Migration bleibt — die war erfolgreich.