Commit graph

477 commits

Author SHA1 Message Date
c7a84438d1 Fix: Notes-Karten — Zeilenumbruch, Clamp + Detail-Modal beim Klick, SW by-v1119
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)
2026-05-27 14:42:47 +02:00
1ff66a7083 Sicherheit + Tests + A11y, SW by-v1118
PYDANTIC max_length (38 Routen, ~400 Field-Constraints):
Schützt vor DoS durch Riesen-Payloads (10MB Thread-Titel etc.).
Pragmatische Limits:
- Titel/Name: 200 · Beschreibung/Body: 10000 · Notiz: 5000
- Email: 254 (RFC 5321) · URL: 500 · Slug/Kategorie: 100
- Hund-Name/Rasse: 80 · Hund-Bio: 2000

Top-betroffen: forum.py, diary.py, health.py, dogs.py, expenses.py,
notes.py, auth.py, profile.py. Manuelle len()-Checks in profile,
chat, ki entfernt (jetzt durch Field abgedeckt).

PYTEST COVERAGE (+19 Tests, 37 grün + 1 xfail):
- test_security.py: require_owner (Places GET/PATCH/DELETE mit
  Fremduser → 403), JWT-Blacklist (Logout invalidiert Token),
  Login-Lockout (5 Fehlversuche → 429 + Retry-After Header)
- test_race.py: Invoice-Counter (20 parallele Threads, alle unique),
  Founder-Number (atomare Vergabe, voll bei 100)
- test_validation.py: Forum-Titel 30k Zeichen → 422, Diary-Text
  50k → 422 (verifiziert Pydantic max_length-Sweep)

A11Y (Tap-Targets ≥44×44 + Dark-Mode-Kontrast):
- #header-user-btn 36→44px, .header-back 40→44, .header-menu-btn 40→44
- dog-profile Wrapped-Slider Prev/Next 40→44
- forum-Lightbox Close 40→44
- --c-text-muted Light: #B0A090 (2.37:1 FAIL) → #7F6B58 (4.74:1 PASS)
- --c-text-muted Dark:  #806A58 (3.58:1 FAIL) → #A08878 (5.46:1 PASS)
- Branding-Farben unangetastet
2026-05-27 13:40:30 +02:00
c517c9281d Refactor: 1167 _esc() → UI.escape() in 36 Dateien, SW by-v1113
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.
2026-05-27 10:15:33 +02:00
2d98eb9374 Fix: Friends-Avatare wieder Original-URL (kein Preview), SW by-v1111
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.
2026-05-27 09:25:19 +02:00
2f37e0ed16 Perf: Freunde-Seite nutzt _preview.webp + lazy loading, SW by-v1109
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.
2026-05-27 08:50:09 +02:00
f6633d65b0 Erste-Hilfe: Telefonnummern für AT + CH eingetragen, SW by-v1108
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.
2026-05-27 08:45:43 +02:00
73872e2c21 Sprint D: Karten-Familie auf UI.map.create+svgMarker konsolidiert, SW by-v1107
Neue zentrale Helper (in Sprint B vorbereitet) jetzt von 5 Seiten genutzt:

walks.js (1 Karten-Init):
- L.map+L.tileLayer → await UI.map.create('walks-map', {...})
- _initMap zu async, Aufrufer in _switchView und _loadData angepasst
- Mini-Karte im Walk-Formular (Modal) bleibt unverändert
  (braucht eigene dragging/scrollWheelZoom-Options)
- view-toggle nicht migriert (responsive CSS-Konflikt mit Desktop)

poison.js (1 Karten-Init):
- L.map+L.tileLayer → await UI.map.create('poison-map', {...})
- _initMap zu async, manueller UI.loadLeaflet entfernt
- DangerCircle + User-Marker unverändert

events.js (1 Karten-Init + Diamant-Marker):
- await UI.map.create('ev-map', {...})
- Rotierter Diamant: L.divIcon+L.marker → UI.map.svgMarker
  (HTML 1:1 erhalten)

lost.js (1 Karten-Init + Puls-Marker):
- Eigene async _loadLeaflet() Funktion komplett entfernt — UI.map.create
  übernimmt das jetzt zentral
- await UI.map.create('lost-map', {...})
- Puls-Animation 🐕: L.divIcon+L.marker → UI.map.svgMarker
- _initMap zu async

routes.js (6 von 7 Karten-Inits):
- _suggestMap, _recMap, _searchMap, _navMap, trimMap, _buildDetailMap
  alle auf UI.map.create umgestellt + zu async
- _buildMiniMap (Route-Card-Preview) bleibt unverändert
  (braucht 6 spezifische Interaction-Disable Options)
- View-Toggle auf neue .map-list-toggle Klasse umgestellt
  (Border-Inline-Styles raus)

NEUE CSS-KLASSE in components.css:
- .map-list-toggle (vereinheitlichter Karten/Listen-Umschalter)
- Verwendet von routes.js; walks/events können später folgen

Tests 19/19 grün. GPS-Tracking-Logik (Polylines, Recording, Trim)
komplett unangetastet. Marker-Cluster-Logik unverändert.
2026-05-27 08:17:06 +02:00
9a066cb24c Sprint C: Listen-Familie konsolidiert (Notes/Expenses/Health), SW by-v1104
Neue zentrale CSS-Datei lists.css (~280 Zeilen) mit Listen-Komponenten:
- .list-shell, .list-filter-bar, .list-search-wrap
- .list-group-header
- .list-item-card + Modifier: --clickable, --milestone, --inactive
- .list-item-date-col + sub-elements (für Diary-Style)
- .list-item-meta-badge mit --meta-color (für Expenses/Health Icons)
- .list-item-body, .list-item-title, .list-item-text, .list-item-meta-row
- .list-item-chips + .list-item-chip mit --chip-color
- .list-item-micro-badges + .list-item-micro-badge
- .list-item-thumb (+ .list-item-thumb-count Overlay)
- .list-item-amount (+ --positive/--negative/--neutral)
- .list-item-actions + .list-item-action-btn (+ --danger)
- .list-reminders-banner + .list-reminder-item (+ --urgent/--warning/--success)
- .list-fab (FAB mit safe-area-inset)

MIGRATIONEN:

notes.js — 10+ Klassen ersetzt:
- .notes-card → .list-item-card list-item-card--clickable
- .notes-rubrik-chip → .list-item-chip mit --chip-color
- .notes-card-meta → .list-item-meta-row
- .notes-action-btn → .list-item-action-btn
- .notes-group-label → .list-group-header
- Notes-spezifische Klassen als Modifier behalten (vertikales Layout,
  pre-wrap text, Top-Zeile mit Actions rechts oben)
- Alte CSS-Definitionen im Inline-<style> als TODO markiert

expenses.js — komplette Item-Card-Migration:
- .exp-entry → .list-item-card list-item-card--clickable
- .exp-entry-icon-badge mit --kat-color → .list-item-meta-badge --meta-color
- .exp-entry-betrag → .list-item-amount list-item-amount--negative
- .exp-entry-del → .list-item-action-btn list-item-action-btn--danger
- .exp-recurring-card--inaktiv → .list-item-card--inactive
- .exp-fab → .list-fab
- UI.moneyInput + UI.parseMoney in beide Forms integriert (€-Prefix,
  Komma-Dezimal)
- Hero-Card + Statistik/Kacheln behalten (spezifisch)

health.js — 9 Card-Renderings migriert:
- Impfungen/Tierarzt/Gewicht/Läufigkeit/Medikamente/Allergien/
  Dokumente/Tierarztpraxis/Befunde
- .health-card → .list-item-card list-item-card--clickable
- Health-Ampel parallel behalten (.health-card-ampel + Linie links)
- Reminder-Banner: .health-reminder-* → .list-reminders-banner +
  .list-reminder-item--urgent/--warning
- Gewicht-Wert: .list-item-amount für kg-Anzeigen
- Form-Modals + KI-Buttons + Transponder-Chip unangetastet (anderer
  Scope)

Tests 19/19 grün. Kein visueller Diff erwartet — Modifier-Klassen
bewahren spezifische Layouts.
2026-05-27 07:31:21 +02:00
459cd425f2 Design-System Sprint A: utilities.css + 948 Inline-Styles → Utility-Klassen, SW by-v1102
PHASE 1 — Sofort-Cleanup ohne Risiko:
- Neue Datei utilities.css mit ~25 Klassen für häufige Kombinationen:
  * text-xs-muted, text-xs-secondary, text-sm-muted, text-sm-secondary
  * flex-gap-2/3, flex-col-gap-2/3/4, flex-center-gap-1/2/3
  * flex-between, flex-1-min, mb-1/3, mt-1/3
  * icon-xs/sm/md/lg, label-block, caption
- index.html bindet utilities.css ein
- mb-3/mt-3 ergänzt (waren in design-system.css unvollständig)

PHASE 2 — .by-tab Modifier für Vereinheitlichung:
- .by-tabs.grid (mit --tab-cols Variable für Admin/Health/etc.)
- .by-tabs.sticky (Desktop vertikale Tabs für Admin)
- .by-tabs.wrap (Zuchthunde, flex-wrap statt scroll)
- .by-tabs.separated (Sitting, mit eigenem Hintergrund + Border)

PHASE 3 — Inline-Style → Klassen-Migration (Python-Script):
- 948 Inline-Styles entfernt (5101 → 4153, -18%)
- 962 Migrationen über 47 Page-Dateien
- Top-Treffer: admin.js (180), health.js (67), dog-profile.js (67),
  litters.js (62), settings.js (61), zuchthunde.js (51)
- Patterns: text-muted, text-secondary, text-danger, text-xs-muted,
  text-sm-muted, grid-2 (Duplikat-Bug behoben!), flex-col-gap-3,
  p-3/4, mb-2/3/4, hidden, w-full, flex-1, ...
- Bewahrt bestehende class-Attribute (mergt korrekt)

Alle 19 Tests grün. Kein visueller Diff erwartet (gleiche Property-Werte).
2026-05-27 07:11:27 +02:00
15d319fbd5 Admin: POI-Statistik erweitert (Nutzer-POIs nach Typ + Labels), SW by-v1099
- /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
2026-05-26 21:37:35 +02:00
cc4f030fd0 Feature: Hundesalons in der Karte, SW by-v1098
- 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)
2026-05-26 21:13:16 +02:00
9394bab1fb Big Sweep: Security + Race-Conditions + Tests + DSGVO + A11y, SW by-v1095
SECURITY (auth.py, routes/auth.py, database.py, main.py)
- JWT bekommt jti; Logout trägt in neue jwt_blacklist-Tabelle ein,
  decode_token() prüft → server-side Invalidierung
- JWT-Expiry default 30 → 7 Tage (ENV JWT_EXPIRY_DAYS überschreibt)
- Sliding-Refresh-Middleware: erneuert Cookie wenn >50% verbraucht
  (Schwelle via JWT_REFRESH_FRACTION, Default 2)
- Login-Lockout in DB-Tabelle login_attempts (5 Versuche / 15 Min,
  überlebt Container-Restart) — alte In-Memory-Lockouts ersetzt
- SMTP-Versand: alle 'except: pass' durch logger.exception ersetzt;
  Fehlversuche landen in failed_emails-Tabelle für späteres Retry
- Referral-Counter Race gefixt: UPDATE partner_codes SET uses=uses+1
  ... WHERE uses<max_uses RETURNING — atomar statt SELECT+UPDATE

RACE CONDITIONS (routes/invoices.py, database.py)
- Neue invoice_counters-Tabelle für atomare Nummernvergabe
- _next_invoice_number nutzt BEGIN IMMEDIATE + atomares UPDATE
- Funktioniert für RG- und ST-Prefixe (Stornorechnungen)
- Race-Test verifiziert (5 Threads × 20 Calls = 100 eindeutige Nummern)

VERSION + TESTS + ERROR-DIGEST (VERSION, Makefile, tests/, scheduler.py)
- Neue VERSION-Datei (Single Source of Truth) — main.py liest beim
  Startup
- Makefile-Target 'make bump' propagiert in sw.js, app.js, index.html
- Makefile-Target 'make test' setzt venv auf, läuft pytest
- 19 Smoke-Tests in tests/ (health, auth, diary, invoice) — alle grün
- Scheduler: täglicher _job_error_digest um 06:30 → schickt Error-
  Zusammenfassung an ADMIN_EMAIL (still wenn keine Errors)

DSGVO + A11Y + ERSTE-HILFE
- landing.html: 'HTML und ODS' → 'JSON' (tatsächlich implementiert)
- datenschutz.js: Sektion Account-Löschung erweitert (sofort gelöscht /
  anonymisiert / 10 Jahre für Rechnungen)
- erste-hilfe.js: prominentes Warning-Banner oben (ersetzt keine
  Tierarzt-Beratung); Notfallnummern gruppiert nach Land, TODO-Platz-
  halter für AT-Uni-Klinik, CH Tox Info Suisse, CH Tierspital Zürich
- ui.js Modal: ESC schließt, Focus-Trap, Auto-Focus erstes Element,
  Restore Focus auf vorigen Caller
- impressum.js Kontaktformular: Labels mit for=cf-name etc.

NEUE DB-TABELLEN (idempotent via CREATE TABLE IF NOT EXISTS)
- jwt_blacklist, login_attempts, failed_emails, invoice_counters

NEUE ENV-VARS
- JWT_REFRESH_FRACTION (Default 2)
- JWT_EXPIRY_DAYS Default geändert (30 → 7)
2026-05-26 20:12:01 +02:00
0ba0de12b3 UX: Wetter-Preset + robusteres Tile-Prefetch (gemeinsamer LastPos), SW by-v1091
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
2026-05-26 17:10:52 +02:00
280213c11d UX: Rechnungs-Modal Footer für Mobile, SW by-v1076
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)
2026-05-26 14:03:09 +02:00
c4a82e96fd UX: Rechnungs-Edit-Modal status-bewusst + Storno-Button, SW by-v1075
- _openNeueRechnungModal akzeptiert jetzt status+invoice_number; je
  nach Status anderes Verhalten:
  • draft   → bearbeitbar, Stornieren + Abbrechen + Speichern
  • sent    → readonly Banner, Stornieren + Schließen
  • paid    → readonly Banner, Stornieren + Schließen
  • neu     → Abbrechen + Erstellen (kein Storno-Slot)
- Footer-Layout: Stornieren links, Abbrechen/Speichern rechts
  (justify-content:space-between → symmetrisch)
- Stornieren öffnet den existierenden _openStornoModal; das Edit-
  Modal wird durch UI.modal.open() automatisch geschlossen
- Submit-Handler ignoriert locked-Status (Schutz auch wenn Button
  irgendwie sichtbar wäre)
- Upgrades-Tab + Rechnungen-Tab geben jetzt inv.status+invoice_number
  beim Öffnen mit; Reload-Callback aus Upgrades-Tab rendert den Tab
  neu, damit nach Stornierung der gelbe Button zurück auf orange geht
2026-05-26 13:59:40 +02:00
5886e1b269 UX: Upgrades-Tab — Button zeigt vorhandene Rechnung an, SW by-v1074
- 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.
2026-05-26 13:50:03 +02:00
e5abdcab62 Fix: Tagebuch Foto-Löschen — null-crash + 404-Cleanup, SW by-v1073
- '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.
2026-05-26 13:38:11 +02:00
c03884cb81 Perf: 9 Performance-Fixes — SW by-v1072
Backend:
- DB: 3 neue Indizes (forum_posts thread+user, routes user) — Forum/Routen-Queries
- Caching: cache.py (TTL-Cache ohne neue Dependency) für 5 statische Listen
  (training_exercises, pflege_tipps, wiki_stats, wiki_gruppen, help_articles)
- diary.py + breeder_photos.py: Bildverarbeitung (ffmpeg/PIL/EXIF) per
  run_in_executor → blockiert Event-Loop nicht mehr
- scheduler.py: 11 kollidierende Jobs auf 5-Min-Intervalle gestaggert, coalesce=True
- social.py: ORDER BY RANDOM() ohne LIMIT in 2 Stellen gefixt
- alerts.py: Haversine-Loop bekommt SQL-Bounding-Box-Vorfilter

Frontend:
- sw.js: Tile-Cache mit LRU-Eviction (max 500 Einträge)
- admin.js: Event-Listener-Leak — Tab-Klicks per Delegation statt N Listener
- api.js: compressImage() Helper — Client-seitiges Resize auf max 2000px
  (HEIC/Videos/<500KB unverändert), integriert in 8 Upload-Stellen
  (diary, dog-profile×2, walks, poison, lost, health×2)

Bump APP_VER 1071 → 1072 (sw.js, app.js, main.py, index.html)
2026-05-26 06:30:36 +02:00
3abf974d29 Feature: Parallele Bild-Uploads, Heartbeat last_seen, Admin zuletzt aktiv, SW by-v1071
- Tagebuch: Bilder werden parallel hochgeladen (Promise.all), Button zeigt Fortschritt
- Auth: /heartbeat Route ergänzt — aktualisiert last_seen alle 5 Min
- Admin: last_seen + last_login in Nutzer-Liste angezeigt (🟢/🔵/)
- Bump SW by-v1071
2026-05-25 20:26:58 +02:00
9677d1e71a Fix: Pfoten-Button bleibt rechts (display:flex statt '' beim Einblenden), SW by-v1070 2026-05-19 19:04:32 +02:00
9f47cfe1d4 Fix: Pfoten-Button rechts neben Suchzeile (Dog-Chip eigene Zeile), SW by-v1069 2026-05-19 19:02:22 +02:00
c5bb3ee2cb UX: 'Stand erfassen' als Pfoten-Button rechts neben Dog-Chip+Suche 2026-05-19 18:51:17 +02:00
81b5199e18 Fix: Suche+Stand-erfassen Zeile als Flexbox (kein Table-Overflow mehr) 2026-05-19 18:46:32 +02:00
4407b9b27f Fix: 'Stand erfassen' Button auf Mobile sichtbar (kein zweizeiliger Text) 2026-05-19 18:43:59 +02:00
dcb966ca54 Feature: Protokoll-Tab Toggle 'Nach Datum / Nach Übung'
- 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
2026-05-19 18:34:30 +02:00
738571d958 Fix: Verlauf-Tab kein Endlos-Spinner + Save-422 bei null-Feldern
- 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
2026-05-19 18:28:56 +02:00
cc841ef6d7 Feature: Trainingsprotokoll-Tab in Übungen, kein Tagebuch-Spam
- 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
2026-05-19 18:17:50 +02:00
d2c2c59abb Fix: Züchter-Header (Wurfverwaltung/Zuchtkartei/Läufigkeit) Hell-Mode kompatibel (CSS-Variablen statt hardcoded Dark-Gradient), SW by-v1036 2026-05-16 13:59:07 +02:00
a50158d522 Fix: Worlds.refresh() nach Profil-Speichern aufrufen
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
2026-05-16 12:07:31 +02:00
b54d9fda99 Fix: users.geburtstag Migration + Format TT.MM statt MM-DD
- database.py: ALTER TABLE users ADD COLUMN geburtstag TEXT (fehlte!)
- profile.py: Validierung auf \d{2}\.\d{2} (TT.MM Format)
- settings.js: Placeholder/Pattern auf TT.MM geändert
- worlds.js: Birthday-Check auf DD.MM Format angepasst
SW by-v1029, APP_VER 1029
2026-05-16 12:02:52 +02:00
a4377033ec Feature: User-Geburtstag im Profil + Glückwunsch in JETZT-Welt
Settings:
- Feld 'Dein Geburtstag (optional)' im Profil-Formular (Format MM-TT)
- Hinweis: nur für Geburtstagsgrüße, kein Jahr nötig
- profile.py: geburtstag gespeichert + Format-Validierung MM-DD

JETZT-Welt wenn heute User-Geburtstag:
- Greet-Text: 'Herzlichen Glückwunsch' statt Tageszeit-Gruß
- Animiertes Geburtstags-Reminder-Card (confetti + cake Icons)
- 'Alles Gute zum Geburtstag, [Name]!'

SW by-v1028, APP_VER 1028
2026-05-16 11:55:34 +02:00
19640af288 Style: Marker + Cluster Rand Tactical-Olive statt Weiß
rgba(255,255,255,0.7/.8) → rgba(52,68,36,0.55/.65)
Dezentes Dunkeloliv auf allen Marker-Icons und Cluster-Bubbles.
SW by-v1025, APP_VER 1025
2026-05-16 11:35:01 +02:00
2cf0bc0d97 Fix: Karte CSS-Filter statt CartoDB Dark, Social Wrapper 860px
Karte Dark-Mode:
- CSS-Filter statt CartoDB Dark Matter (war zu dunkel)
  invert(93%) hue-rotate(180deg) brightness(0.88) contrast(0.88) saturate(0.85)
  Gleiche OSM-Tiles, Marker bleiben unbeeinflusst (tilePane separater Layer)
- Filter sofort bei Init + bei Theme-Wechsel via MutationObserver

Social:
- Outer Wrapper width:100%;max-width:860px;margin:0 auto
  Header-Karte und Tabs sind jetzt konsistent gleich breit
SW by-v1023, APP_VER 1023
2026-05-16 11:19:37 +02:00
721e630a34 Fix: Dark-Mode Karte + Badge-Farben + --c-bg-secondary
Karte:
- Dark-Mode: CartoDB Dark Matter Tiles statt OSM Standard
- MutationObserver + matchMedia watchee für Live-Theme-Wechsel
- _buildTileLayer() / _applyTileTheme() / _isDarkMode()

Badges litters.js:
- Hardgekodete dunkle Hintergründe → CSS-Klassen (badge-warning/-success/-muted)
- Funktioniert jetzt in Light + Dark Mode korrekt

movies.css:
- .movie-tag-stirbt/.movie-tag-ueberlebt → CSS-Variablen (danger-/success-subtle)
- Kein weißer Hintergrund mehr in Dark Mode

--c-bg-secondary: Zoom-Control 30px bleiben aus dem letzten Commit
SW by-v1022, APP_VER 1022
2026-05-16 11:11:09 +02:00
e44414015a Fix: Karte — Zoom-Ausrichtung, 'Z14' kürzer, Marker-Count ausgeblendet
- leaflet-top padding-top 28→33px (Zoom-Buttons mittig zu 2 Filter-Reihen)
- Zoom-Anzeige: 'Zoom 14 · ab 14: alle Layer' → 'Z14' (Hinweis als title)
- #map-osm-status ausgeblendet (Marker-Anzahl nimmt Platz von Wetter etc.)
- components.css ?v=1012, SW by-v1019, APP_VER 1019
2026-05-16 10:39:12 +02:00
562d64979f Fix: Karte — Filter-Button mit Label, kürzere Chip-Texte
- #map-legend-all: 'Filter' Text neben dem List-Icon
- 'Hundefreundl. Café/Restaurant' → 'Café & Restaurant'
- 'Hundefreundl. Hotel' → 'Hotel'
- .map-legend-all: padding angepasst für Text-Label
- components.css ?v=1011, SW by-v1018, APP_VER 1018
2026-05-16 10:32:11 +02:00
835c0ada21 Fix: laeufi.js width:100% für konsistente Breite kollabiert/aufgeklappt
SW by-v1016, APP_VER 1016
2026-05-16 10:20:19 +02:00
af4a61f5b3 Fix: laeufi.js Desktop — margin:0 auto + padding für Zentrierung
Ä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
2026-05-16 10:10:55 +02:00
902e6b8602 Feature: Einmaliger Offline-Hinweis 'App im Vordergrund lassen'
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
2026-05-15 18:28:50 +02:00
1d67d6307e Fix: Routen-Aufzeichnung offline — Dim-Screen + WakeLock + GPS funktionieren
_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
2026-05-15 18:19:55 +02:00
c59326af17 Fix+Polish: Phosphor-Icons Danke-Overlay, Quartalsbericht paid_amount
Giftköder Danke-Overlay (poison.js):
- Emoji 🚨/🐾/📡 durch Phosphor-Icons ersetzt: siren, paw-print, wifi-slash

Quartalsbericht (invoices.py + admin.js):
- Backend: _effective_gross() — für bezahlte Rechnungen wird paid_amount statt
  amount_gross für die Quartalssumme verwendet (Kulanz/Teilzahlung korrekt)
- Admin-Preview: effectiveAmt in der Vorschau-Tabelle, bei Abweichung Hinweis
  "(RG: xx,xx €)" für Nachvollziehbarkeit
- CSV: Spalte "Betrag (eingegangen)" + separate Spalte "Rechnungsbetrag"
- SW by-v995, APP_VER 995
2026-05-15 18:18:22 +02:00
57192ea010 Fix: Routen-Aufzeichnung offline — Buttons Abbruch/Start reagieren nicht
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
2026-05-15 18:11:52 +02:00
0878684402 Feature: Giftköder — Danke-Overlay nach erfolgreicher Meldung
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
2026-05-15 18:03:28 +02:00
32fde79a40 Fix: _queued-Guard in poison/walks/diary — kein Crash bei SW-gequeuten Requests
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
2026-05-15 17:56:29 +02:00
f2856b8acb Fix: Lost — Puls-Animation (box-shadow), false-offline, Pending-Guard
- 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
2026-05-15 17:44:59 +02:00
be12550df1 Fix: Lost-Hund — kein Doppeleintrag nach Sync, pulsierender Marker, Verwerfen-Button, 20km-Alert
- 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
2026-05-15 17:37:16 +02:00
f0c1ee3386 Fix: Offline-UX — 📡 statt 🚧 bei offline-Seiten, schnellerer Warm-up, mehr Endpoints gecacht (SW by-v989) 2026-05-15 17:25:24 +02:00
53fcb61933 Offline-Fallbacks für diary, poison, map + SW-Erweiterung
- 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
2026-05-15 17:02:26 +02:00
0c0daaad6b Feat: Routen offline aufzeichnen — LocalStorage-Queue, Cache-Fallback, Auto-Sync (SW by-v987) 2026-05-15 16:53:38 +02:00
3fae57a0e2 Feat: Kontaktformular im Impressum + /api/contact Endpoint ohne Auth (SW by-v986) 2026-05-15 16:46:37 +02:00