Commit graph

1125 commits

Author SHA1 Message Date
5f01abc590 Züchter-Editor: Wurfnamen sichtbar, 'undefined Medien' gefixt, Mitgliedschaften & Zertifikate
Rene: 'Züchter sollten mehr Einfluss haben — Wurfnamen (B-Wurf), Mitglied-
schaften und Zertifikate fürs Profil.'

- Wurfnamen: Infrastruktur existierte komplett (wurf_rang/wurf_name in DB,
  Backend, Wurfverwaltungs-Formular) — war nur im Editor und auf der
  öffentlichen Seite unsichtbar. Editor-Karten zeigen jetzt 'B-Wurf · Name'
  (Feldname-Bug geburtsdatum→geburt_datum), öffentliche Wurf-Karten bekommen
  den Rang/Namen als Badge, Hinweis im Editor verlinkt zur Vergabe.
- 'undefined Medien': my-editor lieferte kein foto_count (+photos fürs
  Profil-Grid) — ergänzt.
- NEU Mitgliedschaften & Zertifikate: entity_type 'certificate' im Foto-
  System (Ownership wie breeder), Editor-Sektion mit Upload (Bezeichnung als
  Caption) + Löschen, öffentliche Profilseite zeigt eigene Sektion mit
  Logos/Urkunden (klickbar, lazy). Public-Endpoint liefert result.zertifikate.

Tests: my-editor inkl. Wurfname/foto_count, Zertifikat-Roundtrip bis zur
öffentlichen Seite. Suite: 61 passed.
2026-06-07 21:00:14 +02:00
dfffd07a96 Settings entrümpelt: Züchter-Block komplett in den Züchter-Bereich umgezogen
Rene: 'Dieser Bereich könnte auch in den Züchter-Bereich, dann ist alles sauber.'
- Settings zeigt für verifizierte Züchter/Admins KEINE Züchter-Karte mehr —
  der Welten-Chip ist der Einstieg (wie beim Partner). Antrag/Prüfung/Abgelehnt
  bleiben in Settings (Nicht-Züchter sehen den Chip nicht).
- Züchter-Bereich neu: KI-Züchter-Assistenz-Karte (5 Toggles, optimistisches
  Update + Revert), Status-Badge unterscheidet Admin/Züchter, Admin ohne
  Profil bekommt 'Profil anlegen' direkt im Hub.
- Toter Code raus: _openBreederEditModal (75 Z., Settings-Doppel-Editor —
  breeder-editor-Seite ist der vollwertige), _kiToggleRow + 3 verwaiste
  Bindings aus settings.js.
2026-06-07 20:36:19 +02:00
8c76263ea0 Worktree-Verlust-Audit: alle Geister-Endpoints gefunden + Regressions-Test
Systematischer Abgleich aller 528 Frontend-API-Aufrufe gegen 576 Backend-
Routen (methodengenau, Wildcard-Matching für Parameter):

Echte Opfer (zusätzlich zu partner/* und breeder/my-editor):
- worlds.js Nearby-Alerts riefen /poison/nearby + /lost/nearby auf — beide
  existierten NIE; doppeltes catch verschluckte die 404s → die Welten zeigten
  seit jeher keine Giftköder-/Vermisst-Warnungen. Fix: bestehende Listen-
  Endpoints mit Geo-Filter nutzen (/poison?radius=Meter, /lost?radius_km).
- API.weather.alerts → /weather/alerts existierte nie, hatte aber auch keinen
  Aufrufer — toter Wrapper entfernt.

False Positives geprüft: invoices (Router bringt eigenen Prefix mit, alle 9
Routen ok), Seiten↔Dateien↔window.Page_*↔index.html-Sections alle konsistent.

Neu: tests/test_api_surface.py — statischer API-Oberflächen-Abgleich als
Dauertest; Geister-Aufrufe sind ab jetzt Build-Fehler. Suite: 59 passed.
2026-06-07 20:22:20 +02:00
487dacc7c7 Fix: /breeder/my-editor Endpoint (Crash 'Cannot destructure profile') + Läufigkeit in Züchter-Bereich
breeder-editor.js (aus 459cd42) rief /api/breeder/my-editor auf — Endpoint
existierte nie (gleicher Worktree-Verlust wie /partner/my-profile). Jetzt
gebaut: profile + litters + storage_mb/limit; ohne Profil klare 404 statt
Destrukturierungs-Crash. 3 Tests.

Läufigkeit (Rene): eigener HUND-Chip entfällt, stattdessen vierte Karte im
Züchter-Bereich (Zyklen, Progesterontests, Deckdaten). Suite: 58 passed.
2026-06-07 20:04:43 +02:00
ed7c469c6a Züchter-Bereich (Hub) + Settings-Partner-Karte raus + Admin: alle Code-Einlösungen mit Kanal
- Neue Seite #breeder-dashboard (Welten-Chip 'Züchter' role:breeder in HUND,
  ersetzt die Einzel-Chips Zuchtkartei + Wurfverw.; beide FABs wandern an den
  neuen Chip; Läufigkeit bleibt eigener Chip in HUND, Rene-Vorgabe):
  Zwinger-Karte (Name, verifiziert-Badge, Profil-Editor), Wurfverwaltung mit
  Wurf-Anzahl, Zuchtkartei mit Hunde-Anzahl. Einzelseiten bleiben erreichbar.
- Settings: Partner-Karte entfernt — der 🤝-Welten-Chip ist der Einstieg.
- Admin 'Aktive Codes': 👥 zeigt jetzt ALLE Einlösungen eines Codes mit
  Kanal-Badge (QR #seq aus Kontingent vs. Link/manuell), Datum und
  Bestätigt-Status — Endpoint /admin/partner/codes/{id}/registrations.
Suite: 55 passed.
2026-06-07 19:55:51 +02:00
6a6a09d879 Marketing-Cockpit: Partner-Programm 🟢 (v1265) — Infra komplett, Influencer-Runde 3 mit Partner-Paket als Angebot 2026-06-07 19:44:57 +02:00
2927ae2672 Schutz gegen kursierende Partner-Codes (Rene: 'Bonus-Codes kursieren gerne das Internet')
1. QR-URL verrät den Code nicht mehr: /q/{token} → /?qr=TOKEN (vorher stand
   der tippbare Code in der Adresszeile jedes Scanners). Registrierung löst
   den Code server-seitig aus dem Token auf (auch ohne ref_code).
2. Notbremse: partner_codes.active — Admin kann Codes pausieren (Einlösung
   gesperrt, Info-Endpoint 404, Historie/QR-Kontingente bleiben) und
   reaktivieren. UI: ⏸/▶-Toggle + pausiert-Badge in der Codes-Tabelle.
3. max_uses im Anlege-Formular standardmäßig 50 statt unbegrenzt.

Tests: QR-only-Registrierung, Pause→keine Einlösung→Reaktivierung,
Redirect ohne Klartext-Code. Suite: 54 passed.
2026-06-07 19:35:31 +02:00
21bcc6b962 Partner-Dashboard vereinfacht: Scans + 'unbestätigt' raus (Rene: verwirrt)
Code-Karte: nur noch Registrierungen gesamt + diesen Monat, mit Hinweis dass
alle Wege zählen (Link, eingetippter Code, QR) — erklärt warum Code-Zahl >
QR-Zahl sein kann. QR-Kontingente: 'X von Y genutzt' statt Scans/Registr./
Versuche; Einzel-Code-Liste nur noch ● genutzt (N×) / ○ frei.
Admin behält die Detail-Sicht (Scans, Versuche, Account-Liste).
2026-06-07 19:25:18 +02:00
0cca716c3d Partner-Dashboard: Zähler selbsterklärend — 'X von Y Codes erfolgreich' statt 'verbraucht', '+N unbest.' ausgeschrieben, Einzel-Code-Badge zeigt Registrierungszahl (ein Code kann mehrere Accounts bringen) 2026-06-07 19:18:46 +02:00
fe783ef01b Fix: page-Section für partner-dashboard in index.html — Loader fand keinen Container (weiße Seite) 2026-06-07 19:10:44 +02:00
0a262989f3 Feature: Partner-Dashboard (#partner-dashboard) — operative Daten raus aus dem Profil-Editor
Rene: QR-Stats gehören nicht ins öffentliche Profil, eigene Seite fehlte.
Neue Seite 'Partner-Bereich' (Welten-Chip 🤝 zwischen Moderation und Admin,
role:partner — sichtbar für is_partner + Admin; _mergeDefaults reicht den
Chip an bestehende Welt-Configs nach):
- Einladungscode groß + Link-kopieren-Button
- Kacheln: Registrierungen gesamt / diesen Monat / unbestätigt
- QR-Kontingente mit Einzel-Code-Status (aus partner-profil.js hierher verschoben)
- Profil-Status-Karte (Entwurf/Prüfung/frei) mit Sprung zum Editor

Backend: GET /partner/my-stats (Codes mit Zahlen + Profil-Status).
Settings-Partner-Karte: zwei Buttons (Partner-Bereich primär, Profil sekundär);
Dank-Mail-CTA zeigt auf #partner-dashboard. Suite: 52 passed.
2026-06-07 19:06:51 +02:00
3d7d5dc1c4 Feature: Dank-Mail an Partner bei bestätigter Registrierung — mit Statistik
Trigger ist die E-Mail-Bestätigung des Geworbenen (nicht die rohe
Registrierung — konsistent zur Registrierungen/Versuche-Zählung) und nur
beim ersten Verify (Doppelklick auf den Link = keine zweite Mail).

Inhalt: Dank + Bilanz (bestätigte Registrierungen gesamt + diesen Monat),
bei QR-Herkunft der Sticker (#seq, Kontingent-Label), bei Gründer-Codes
die offenen Plätze; CTA zur Partner-Statistik. Versand über das
partner@-Konto, Fehler landen in failed_emails (context partner_thank_you).

Env-Fund dabei: SMTP_PASS fehlte in BEIDEN .env (nur SMTP_SUPPORT_PASS da)
— Partner-Konto-Versand wäre fehlgeschlagen; auf der DS ergänzt.
Test: Mail-Capture per monkeypatch, prüft Statistik + Sticker-Nr +
Einmaligkeit. Suite grün.
2026-06-07 18:51:54 +02:00
df2f42f8ac Partner-Self-Service: Einzel-Code-Status — welcher Sticker ist verbraucht?
Rene: 'wo sieht der Partner welche QR-Codes er hat und wieviele verbraucht
sind?' Neu in 'Meine QR-Codes':
- Kontingent-Zeile zeigt 'X/Y verbraucht' (Codes mit ≥1 bestätigter Registrierung)
- Listen-Button klappt Einzel-Codes auf: #Nr, Kurz-URL, Scans und Status
  ● verbraucht (mit Registrierungs-Datum) / ◐ gescannt / ○ frei
- Endpoint /partner/my-qr/{id}/codes (owner-gated, keine personenbezogenen
  Daten — nur Zähler + Zeitstempel)
2026-06-07 18:46:54 +02:00
970480c1d6 QR-Stats: Registrierungen (bestätigt) vs. Versuche (unbestätigt) + Account-Detail-Liste
Rene: Statistik zählte alles in einen Topf (3 statt 2) und zeigte nicht,
WER sich registriert hat. Jetzt:
- registrations = email_verified=1, attempts = unbestätigte Versuche —
  Versuche werden bei späterer Bestätigung automatisch zu Registrierungen
- Admin: 👥-Button pro Kontingent klappt Account-Liste auf (Name, E-Mail,
  Datum, ✓ bestätigt/ Versuch, Sticker-Nr #seq) — lazy geladen, admin-only
  (personenbezogene Daten); Partner sehen weiter nur Zahlen (Registr. +N)
- Test deckt Versuch→Bestätigung-Übergang und Detail-Endpoint ab
2026-06-07 18:43:18 +02:00
f604ab7c4f Feature: QR-Kontingente für Partner — Bestellung, Übergabe, Rückverfolgung
Partner verteilen gedruckte QR-Codes (Sticker/Flyer); jeder physische Code
ist einzeln rückverfolgbar von Scan bis Registrierung.

Backend:
- partner_qr_batches + partner_qr_codes (Token 8-stellig, ohne 0/O/1/l/I),
  users.referred_qr, partner_codes.owner_user_id (+Backfill über referred_by)
- /q/{token}: Scan zählen (scans, first/last_scan_at) → Redirect
  /?ref=CODE&qr=TOKEN — dockt am bestehenden Referral-Flow an
- Registrierung: qr_token wird nur zugeordnet, wenn er zum eingelösten
  Partner-Code gehört (Manipulationsschutz)
- Admin: Kontingent bestellen (max 500), Liste mit Scans/Registrierungen,
  Löschen (Zweiklick), druckfertiges A4-PDF (segno+fpdf2, 3×4 Grid mit
  Kurz-URL + laufender Nummer), Code-Besitzer zuordnen
- Partner-Self-Service: /partner/my-qr (+PDF) für Code-Besitzer

Frontend:
- Admin-Partner-Tab: Karte 'QR-Kontingente' (Bestellung, Stats, PDF, Besitzer)
- Partner-Profil: 'Meine QR-Codes' mit Scans/Registrierungen + PDF-Download
- boot.js/app.js speichern ?qr=, Registrierung schickt qr_token mit

Neu: segno==1.6.6 (pure-python QR). Tests: 5 neue (PDF, Scan-Zählung,
Attribution, Fremd-Token-Schutz, Self-Service). Suite: 51 passed.
2026-06-07 18:20:23 +02:00
cadfb24a8d Partner-Freigabe: Live-Vorschau im Admin + Mail-Fehler sichtbar machen
Rene: 'kann nichts prüfen — ich würde den Output erwarten, der auf der
Partner-Seite zu sehen sein wird'. Freigabe-Zeile hat jetzt einen
Vorschau-Toggle, der die Karte 1:1 wie auf #partner rendert (Logo/Initial,
Slogan, Website, Instagram, Bio, Medien-Grid).

Mail-Ursache gefunden: Staging-.env fehlte SMTP_SUPPORT_USER → Code-Default
support@banyaro.de → 535 Auth-Fehler, vom Silent-Catch verschluckt.
.env ergänzt (partner@banyaro.app wie Prod); Submit loggt SMTP-Fehler jetzt
über _log_smtp_failure in failed_emails statt still zu schlucken.
2026-06-07 17:43:42 +02:00
a40aa183ec Admin: offene Partner-Profil-Freigaben in 'Zu erledigen'-Leiste + ADMIN_EMAIL-Befund
Rene reichte ein Partner-Profil ein und sah als Admin nirgends einen Hinweis:
1. Action-Items kannten Partner-Profile nicht — partner_profiles_pending
   (submitted_at gesetzt, approved=0) jetzt im Endpoint + Chip im Admin-Kopf
   (Klick -> Partner-Tab). Test ergänzt (7 passed).
2. ADMIN_EMAIL fehlte in BEIDEN .env auf der DS (Prod+Staging) — damit wurden
   auch Upgrade-Anfragen-Mails still verschluckt (bekanntes Silent-Skip-Muster).
   Auf der DS nachgetragen; greift je beim nächsten Deploy.
2026-06-07 17:34:56 +02:00
73ca66bbf5 bump v1254 2026-06-07 17:30:07 +02:00
8a614eef1a Fix: HEIC/MOV-Konvertierung bei Partner-Uploads
Logo-Pfad akzeptierte .heic, öffnete aber direkt mit Pillow (kein HEIF-Opener)
— iPhone-Fotos schlugen fehl. Jetzt convert_media-Vorstufe wie im Foto-Pfad.
Fehlgeschlagene Konvertierungen (HEIC→JPEG, MOV→MP4) brechen mit klarer
Meldung ab statt rohe Dateien zu speichern (MOV wäre als <img> kaputt gerendert).
Test: echter HEIC-Roundtrip (pillow-heif) für Logo + Foto.
2026-06-07 17:29:59 +02:00
21f54f478b Fix: partner_profiles Alt-Schema-Migration — Tabelle aus verlorener v1102-Session existiert auf Staging/Prod (photos statt photos_json); Umbau mit Datenübernahme 2026-06-07 17:22:59 +02:00
ce8aa2b699 Feature: Partner-Profile Backend + Pro-Zugang für Partner
Die Partner-Showcase-Seite (#partner) und der Profil-Editor (#partner-profil)
existierten seit v1102 nur als Frontend — /api/partners/public und
/api/partner/my-profile gab es nie (vermutlich Worktree-Merge-Verlust).

Backend neu:
- partner_profiles-Tabelle (user_id PK, ON DELETE CASCADE → DSGVO-Delete greift)
- GET/PUT /partner/my-profile (Texte, Website-Normalisierung, @-Instagram)
- Logo-Upload (≤5 MB → WebP 512px, altes Logo wird geräumt)
- Foto/Video-Upload (max 6, 200-MB-Budget, HEIC→JPEG, MOV→MP4 via ffmpeg,
  Bilder→WebP 1600px) + Lösch-Endpoint
- Submit-Workflow (approved 0/1/-1) + Admin-Mail (best effort)
- GET /partners/public (nur freigegebene, JOIN users für Name/Avatar)
- Admin: GET /admin/partner/profiles + POST .../review

Pro für Partner: has_pro_access() + App._hasPro() prüfen jetzt is_partner —
Multiplikatoren bekommen Pro gratis (mehrere Hunde, KI-Trainer etc.).

UI: Admin-Partner-Tab mit Freigabe-Sektion (offen-Badge, ✓/✗),
Settings zeigt Partnern eine Karte mit Link zum Profil-Editor.

Tests: tests/test_partner_profile.py — 5 Smoke-Tests (403, Voll-Flow
inkl. Freigabe/Ablehnung, Pflicht-Anzeigename, Logo+Foto-Upload, Pro-Zugang).
Suite: 44 passed.
2026-06-07 17:20:20 +02:00
178aef7fb0 Fix: Design-System-Regression v1102 — .hidden(!important) vs style.display app-weit
Rene: 'Tagebuch Kalenderansicht/Karte nicht mehr da' — Root-Cause: 459cd42
ersetzte style="display:none" durch class="hidden", aber die Show-Pfade
setzten weiter style.display. .hidden hat !important und gewinnt immer
(gleiche Klasse wie Filter-Panel-Hotfix v1242). Prod-Logs bewiesen: kein
einziger /diary/calendar- oder /locations-Request kam je an.

Unsichtbar seit v1102, jetzt per classList gefixt:
- diary: Stats-Bar mit View-Switcher (Liste/Medien/Kalender/Karte) + Medien-Grid neuer Eintrag
- health: KI-Tierarzt-Ergebnis erschien nie
- walks: Challenge-/Stamm-Gassi-Tabs leer
- welcome: iOS-Panel der Desktop-Install-Anleitung
- wiki: Fotos-Mod-Badge + Foto-Fallback (via app.js data-fb show-el/sibling-Handler)
- routes: Filter-Badge; breeder: Fotos-Section

Zweite Fehlerklasse aus demselben Sprint: doppelte class-Attribute
(class="x" id=… class="hidden") — Browser verwirft das zweite Attribut.
87 Vorkommen in 23 Dateien zusammengeführt; betroffene Show/Hide-Pfade
(ev-map, rk-mine/nearby-group, chat-partner-dot, eh-panel, zh-section)
auf classList umgestellt.
2026-06-07 15:09:43 +02:00
5acbaaa97b Landing: 'ohne App Store'-Formulierungen entschärft (3.1.1-Trigger) — PWA-Pitch jetzt 'direkt im Browser' 2026-06-07 11:03:36 +02:00
6d9a04fd10 Navi: Fenster-Index statt globaler Suche — Loop-Routen brachen Bellen/Fortschritt/Gruen
Praxistest Rene (Gassirunde Siegenhofen, Loop Start=Ende): _closestIdx suchte
GLOBAL -> am Start war der END-Index genauso nah -> Fortschritt sprang ans
Track-Ende: nie Abbiege-Bellen (keine Turns mehr 'vor' einem), done-Linie
malte sofort alles gruen, 99%.
- _closestIdx: Fenster um den aktuellen Index (-15/+80), global nur beim
  ersten Fix (mit Start-Praeferenz bei Quasi-Gleichstand <25m) oder wenn
  verloren (>300m). Simulation auf der echten Route: 13/13 Turns feuern
  bei 41-49m Vorlauf.
- Gelaufener Weg als BREADCRUMB: gruen = auf der Route, rot = daneben
  (Segment-Wechsel nahtlos); done-Linie (Track-Slice-Malen) entfernt —
  nie gelaufene Abschnitte bleiben jetzt orange
- Off-Route-Schwelle 50->35m (Klaeffen kam ~5m zu spaet), Abbiege-Ansage 45->50m
Bump v1250
2026-06-07 08:22:02 +02:00
a31d08a2dc Karte: Blickrichtungs-Kegel (Kompass) + ruhigeres Folgen (Rene: 'weiss nicht wo es ist/hinschaut')
- Standort-Punkt zeigt jetzt einen Kompass-KEGEL (wie Google Maps): CSS-Kegel
  im loc-icon (GL + Leaflet), deviceorientation mit webkitCompassHeading (iOS)
  bzw. 360-alpha (Android), Glaettung ueber kuerzesten Winkelweg, rAF-throttled.
  iOS-Permission via User-Geste (Follow-/Standort-Button startet den Kompass).
- Follow pant nur noch bei brauchbarem Fix (accuracy < 75m) — ungenaue
  Positionen liessen die Karte zappeln; GPS-Fixes frischer (maximumAge 2s
  statt 5s). Marker aktualisiert weiterhin jeden Fix.
Bump v1249
2026-06-06 20:30:22 +02:00
85d578874a Navi-Sounds: zurueck zu HTMLAudio mit Gesten-Priming — WebAudio war stummschalter-stumm
Prod-Befund Rene: kein Ton. Ursache: v1244-Umbau auf AudioBuffer/WebAudio —
der respektiert den iOS-STUMMSCHALTER (= Gassi-Normalzustand) -> Stille.
HTMLAudio spielt wie Medien trotz Stummschalter (deshalb klang Staging v1243
richtig). Den urspruenglichen Lazy-Load-Bug loest jetzt GESTEN-PRIMING:
im unlock() stumm anspielen (play+pause, muted) = Wiedergabe-Erlaubnis fuer
spaetere gestenlose Ansagen + erzwingt das Laden. readyState>=2-Check,
Synthese bleibt Fallback.
Bump v1248
2026-06-06 20:24:35 +02:00
0d976b90b7 Karte: Bewegungs-Gate fuer den POI-Scanner — Follow-Mode triggerte ihn laufend
Rene: aktiviertes Crosshair pannt alle paar Sekunden -> jedes moveend
startete den Scan. Scans aus KARTENBEWEGUNG laufen jetzt erst, wenn sich
das Zentrum >= 20% der Viewport-Breite bewegt hat oder der Zoom wechselt
(bei Z16 ~ alle 100-150m Fussweg). Alle anderen Trigger (Marker
gespeichert, Layer-Toggle, Retry, Init, Scan-Queue) ungebremst.
Bump v1247
2026-06-06 20:07:23 +02:00
fc55943e90 Karte: Follow-Crosshair zwischen Zoom-Reglern und Offline-Puls-Icon (Rene)
position:fixed + safe-area (+70px) wie das Offline-Icon (+110px) statt
absolute im Karten-Container — sitzt jetzt exakt in der Luecke; Offline-
Puls bleibt unveraendert an seinem Platz.
Bump v1246
2026-06-06 20:04:49 +02:00
f4f597edf8 Karte: Follow-Button (Crosshair) wie in Routen — ein Tipp folgt dem Standort
Wunsch Rene: sichtbarer Follow-Schalter direkt auf der Karte statt nur ueber
das Speed-Dial. Crosshair links unter den Zoom-Reglern (unterhalb des
Offline-Puls-Icons): Tipp = zentrieren + folgen (Button farbig), Karte
ziehen = pausiert (grau). Synchron mit Standort-Speed-Dial-Button und
Aufzeichnungs-Start (beide aktivieren Follow ebenfalls).
Bump v1245
2026-06-06 19:58:56 +02:00
448066567d Navi-Sounds: AudioBuffer statt HTMLAudio — echtes Gebell zuverlaessig ab dem ersten Wuff
HTMLAudio+preload laedt auf iOS lazy (canplaythrough feuert nie) und verlor
beim ersten Einschalten das Rennen gegen den Probe-Wuff -> Game-Boy-Fallback.
Jetzt: fetch + decodeAudioData in den bestehenden AudioContext (praezises
Timing via BufferSource, offline via SW-Precache); Probe-Wuff 450ms verzoegert.
Synthese bleibt Fallback bei 404/Decode-Fehler.
Bump v1244
2026-06-06 19:55:44 +02:00
2042a3f513 Routenvorschlaege: Schwierigkeit aus Distanz+Hoehenmetern + Yaro-Navi mit echtem Gebell
Schwierigkeit (Wunsch Rene): ORS elevation=true -> ascent; leicht <4km & <50hm,
mittel <=7km & <150hm, sonst anspruchsvoll (vorher NUR km — flache 6km galten
als 'anspruchsvoll'). ACHTUNG: elevation=true codiert die Polyline 3D —
eigener _decode_polyline3d (Roundtrip-getestet), Track enthaelt jetzt alt;
Hoehenmeter im Vorschlag als Pill ('X hm') + in der API (hoehenmeter).

Navi-Sounds: echtes Schaeferhund-Gebell (Renes Aufnahme zugeschnitten):
/sounds/wuff.mp3 (0,34s Einzel-Beller, 2x=links 1x=rechts) +
/sounds/klaeffen.mp3 (2,8s Sequenz, falscher Weg — spielt 1x statt 4x);
mono, loudnorm -14 LUFS, Fades. /sounds-Mount (main.py), SW-Precache
(bellt auch im Funkloch). Synthese bleibt Fallback.
pytest 39 passed. Bump v1243
2026-06-06 19:51:20 +02:00
0967623342 Hotfix: Routen-Filter oeffnete nicht — .hidden hat !important, style.display verlor immer
Kaputt seit 27a3f95 (2026-06-05, 'Filter standardmaessig zu' setzte die
hidden-KLASSE ins Markup, der Toggle blieb aber auf style.display='').
.hidden = display:none !important (design-system.css) -> Klick wirkte nie.
Import funktionierte weiter (Label+File-Input nativ) — dadurch gemeldet
als 'Filter geht nicht mehr' (Angie + Rene).
Fix: classList.toggle('hidden') im Toggle + konsistent im Suggest-Tab.
Bump v1242
2026-06-06 19:05:34 +02:00
ca23b3ec46 Navi-Sounds: 2x Wuff = links, 1x Wuff = rechts, Klaeffen = falscher Weg (Idee Rene)
- NavSound (routes.js): WebAudio-Wuff-Synthese (Saegezahn-Sweep + Tiefpass,
  kein Asset, laeuft offline); echte Aufnahmen unter /sounds/wuff.mp3 +
  klaeffen.mp3 (z.B. von Yaro) werden automatisch bevorzugt
- Abbiege-Erkennung: Turns einmalig aus dem Track (Peilung ueber 15-m-
  Stuetzpunkte gegen GPS-Zickzack, >=40 Grad), Ansage bei <=45 m davor,
  einmal pro Abbiegepunkt
- Falscher Weg (>50 m, bestehende Warnung): Klaeffen beim Abkommen +
  Erinnerung alle 30 s; zurueck auf Route reset
- Toggle im Navi-Header (Lautsprecher-Icon, by_nav_sound, Default AN),
  iOS-Audio-Unlock beim Navi-Start + Toggle (Probe-Wuff als Bestaetigung)
Bump v1241
2026-06-06 18:57:20 +02:00
a1b2644cce DWD-PoC: Binaer-Artefakte aus dem Repo (nur decode_poc.py bleibt) 2026-06-06 18:34:41 +02:00
502f0f4921 DWD-Compose: container_name entfernt — Staging+Prod teilen den Docker-Host (Namenskonflikt) 2026-06-06 18:33:05 +02:00
6565d6a999 DWD-Pipeline: Dauer-Container mit 5-Min-Schleife statt DSM-Cron
DSM-Aufgabenplaner kann minimal stuendlich (Rene) — Container laeuft jetzt
dauerhaft (restart: unless-stopped) mit interner Schleife (loop.sh, idle =
sh+sleep, Python/GDAL nur waehrend des Laufs). Einmal 'up -d --build',
ueberlebt Reboots. make_radar_tiles.py ist idempotent, Fehler brechen die
Schleife nicht.
2026-06-06 18:21:52 +02:00
e6558b64d3 DWD-Plan: Umsetzungsstand dokumentiert (Staging v1240, Cron + Geraetetest offen) 2026-06-06 18:13:30 +02:00
5330681059 DWD-Regenvorhersage: Pipeline + /radar-Route + Timeline-Integration + Settings-Toggle
PoC BESTANDEN (tools/dwd-radar/poc): Anker (9E,51N) = Pixel-Mitte (470/600),
Ecken decken sich mit der DWD-DE1200-Spec — Georeferenzierung bewiesen.
- tools/dwd-radar: RV-Komposit (25 Frames, 0-120min) -> kolorierte RGBA-
  PMTiles z4-7 je Frame (MapLibre overzoomt darueber) + manifest.json,
  atomarer Swap, KEEP_RUNS-Aufraeumen; 25 Frames in ~14s lokal
- docker-compose.dwd.yml (DSM-Cron alle 5 min, NIE --remove-orphans)
- main.py: /radar/manifest.json (no-store) + /radar/{run}/{file} (Range/206,
  immutable — Run-Id im Pfad); sw.js: /radar/ pass-through
- map.js: Radar-Frames heterogen ({url,time,dwd}) — DWD ersetzt RainViewer-
  Nowcast (0-120min, 5-min-Schritte) wenn Toggle an + GL + Karte in DE +
  Manifest frisch (<30min); sonst RainViewer-Fallback; Label '+X Min - DWD'
- settings.js: Toggle 'DWD-Regenvorhersage' (by_dwd_radar, Default AN)
- pytest 39 passed
Bump v1240
2026-06-06 18:08:57 +02:00
6a06c9be7e POI-Bewertung: Live-Praesenz zaehlt als GPS-Beleg (Rene stand am Ort, wurde abgewiesen)
Vorher zaehlte NUR eine aufgezeichnete Tour der letzten 48h (<=50m am POI,
>=2 Punkte) — die aktuelle Geraeteposition floss gar nicht ein.
- DogFriendlyIn: optional user_lat/user_lon (Frontend schickt _userPos mit)
- Beleg-Weg a): Geraetestandort <= 75m am POI (50m Radius + GPS-Toleranz)
  -> route_id=None markiert Live-Beleg; Weg b) Tour-Beleg unveraendert
- Fehlermeldung nennt jetzt beide Wege (hin gehen ODER Standort aktivieren)
- pytest 39 passed
Bump v1239
2026-06-06 17:26:20 +02:00
abd7447d29 Karte: Follow-Mode + Live-Strecke bei Routen-Aufzeichnung (Wunsch Rene)
- MapGLMini-Polyline hatte KEIN addLatLng (nur setLatLngs) -> TypeError im
  Rec-Overlay: Strecke unsichtbar, Marker fror ein, kein Folgen. Facade
  ergaenzt (Leaflet-kompatibel).
- Rec-Overlay (routes.js): Follow-Mode default AN — Karte wandert mit dem
  Standort, Drag pausiert, Crosshair-Button (unten rechts) reaktiviert;
  erster Fix setzt Zoom 16, danach bleibt der Zoom erhalten
- Zentrale Karte (map.js): Standort-Button aktiviert Follow (+Toast),
  dragstart beendet es (beide Engines); GPS-Tracking folgt sanft (easeTo);
  Aufzeichnung startet im Follow-Mode, _updateRecMap pant nur noch im Follow
Bump v1238
2026-06-06 17:20:38 +02:00
2cdb743ce7 Selektives Loeschen: auch Funkloch-Gebiete bleiben + Keep-Set haertung
Rene: Funkloecher + Routen waren nach 'Alles loeschen' weiter weg.
- Funkloch-Regionen jetzt im Keep-Set (geloescht wird NUR Manuelles);
  Zonen behalten ihren Fuellstatus (Komplett-Wipe setzt weiter zurueck)
- Korridor-Migration beim Loeschen: keepTracks=[{name,track}] schreibt
  Tracks in Alt-Eintraege ohne r.track (Bestand vor v1236) bzw. legt
  fehlende Korridor-Regionen an — kein Warten auf Self-Healing
- clear() liefert Summary; Toast zeigt 'behalten: Standort, X Routen,
  Y Funkloch-Gebiete' — Diagnose-Sichtbarkeit fuer Geraetetests
Bump v1237
2026-06-06 13:55:37 +02:00
ca97613938 Fix: Selektives Loeschen griff auf Geraet nicht — Keep-Set jetzt selbsttragend aus der Meta
Renes Befund: 'Alles loeschen' wischte weiter alles. Ursachen: (a) Bestands-
Gebiete hatten keine standort-Region (ensureHomeArea legt nur bei FEHLENDER
Kachel los), (b) Korridor-Keys waren nur aus API-Tracks ableitbar -> leeres
Keep-Set = Komplett-Wipe.
- downloadCorridor speichert vereinfachten Track (<=60 Pkt) in der Region-Meta;
  clear() baut Korridor-Keep daraus — ohne API/Login/GPS
- Standort-ADOPTION: clear() mit center legt fehlende standort-Region
  synthetisch an (Bestandsdaten vor Runde 6)
- map.js: center-Fallback auf by_last_position wenn GPS noch keinen Fix hat
- Test r7 erweitert (clear ohne Optionen haelt Korridor aus Meta), alle gruen
Bump v1236
2026-06-06 13:44:01 +02:00
29cd489287 Offline-Karten Runde 7: 'Alles loeschen' selektiv — Standort + Routen-Korridore bleiben
Idee Rene (spart Vorladezeit + Daten): statt loeschen-und-neu-laden bleiben
- Standort-Gebiete (Regionen type 'standort')
- Korridore der gespeicherten Routen (clear({keepTracks}) aus preview_track)
- 5-km-Umkreis der aktuellen Position + Basis-Zooms 0-9
- Marker/Warnungen (p/) + Glyphs (f/)
Geloescht: manuelle Gebiete/Ausschnitte + Funkloch-Kacheln (Zonen bleiben
gemerkt, Nahe laden automatisch neu). Ohne Keep-Kandidaten: Komplett-Wipe.
Batch-Delete in einer Transaktion. Tests r7 neu, r6 angepasst, Regression gruen.
Bump v1235
2026-06-06 13:35:18 +02:00
94a6ce49ba Offline-Karten Runde 6: Standort-Grundversorgung — aktuelles Gebiet bleibt immer geladen
Renes Modell Punkt 1 war zu eng interpretiert (nur Funkloecher): das Gebiet
um die aktuelle Position gehoert zur Grundversorgung.
- ensureHomeArea(lat,lon): Zentrums-Kachel-Check -> bei Luecke Budget-Download
  (type 'standort', Cap-gated)
- Start-Check: Standort raw pruefen (ohne GL-Stack) und bei Bedarf laden
- 'Alles loeschen': Standort-Gebiet wird SOFORT neu geladen (+ Toast) —
  vorher war die Offline-Funktionalitaet genau am wichtigsten Ort weg
- Pfote Segment 5: Standort-Kachel da UND Zonen im 50-km-Umkreis gefuellt
  (ferne Zonen zaehlen nicht mehr — sie laden erst vor Ort)
- Tests r6 + Regression r1/r3/r4/r5 gruen
Bump v1234
2026-06-06 13:23:33 +02:00
97f0518c54 SW: Versions-Update reisst kein Offline-Loch mehr (Cache-Carry-Over + GL-Stack-Precache)
Geraetetest: Karte offline 'nicht verfuegbar' obwohl Pfote ready zeigte —
activate loeschte die alten Static-Caches, waehrend der neue erst im
Hintergrund befuellt wurde; der GL-Stack (maplibre/pmtiles/map-offline)
war zudem nie im Precache, nur nach erster Online-Kartenoeffnung.
- activate: _migrateStaticCaches uebernimmt fehlende Eintraege der alten
  Caches unter nacktem Key (Online-Refresh ueberschreibt sie sauber),
  DANN erst loeschen
- PRIORITY_PAGES + GL-Karten-Stack (maplibre-gl.js/.css, pmtiles,
  map-gl-style, map-offline, map-gl-markers, map-gl-mini)
- generischer Cache-First-Pfad: ignoreSearch-Fallback fuer eigene /js/+/css/
  (?v=APP_VER zeigt sonst am Carry-Over/bare-Bestand vorbei)
Bump v1233
2026-06-06 13:14:37 +02:00
a914101c5b SW: Shell-Assets nach 2,5s aus dem Cache statt endlos aufs Netz warten
Bei schwachem Empfang blieb die App beim Start SEHR LANGE weiss: CSS +
Kern-JS + index.html waren Network-First OHNE Timeout — der Cache griff
nur bei hartem Fetch-Fehler, nicht bei troepfelnder Verbindung.
Jetzt: Promise.race(Netz, 2,5s) -> Cache-Fallback (stale-while-revalidate,
Netz aktualisiert den Cache im Hintergrund weiter). Versions-Updates laufen
weiter ueber x-app-version + controllerchange.
Bump v1232
2026-06-06 13:05:34 +02:00
6c313aca05 Offline-Karten Runde 5: Geraetetest-Feedback (Indikator, Flugmodus-Signal, Ent-Funklochen, Warnungs-Aktualitaet, Routen-Start-Check)
- Indikator links unter die Zoom-Regler (rechts verdeckte Legenden-Chips)
- Flugmodus bei offener App -> Position raw als Funkloch-Zone (offline-Event)
- Ent-Funklochen: Zonen-Liste im Offline-Modal mit X (removeDeadZone)
- Warnungs-Aktualitaet: _mergeStore Bbox-Replace (aufgehobene Giftkoeder/
  gefundene Hunde verschwinden; Fetch-Kreis deckt Bbox via sqrt2 ab;
  fresh=null merged nie) + 24h-Refresh im 50km-Umkreis beim Start
- Routen offline nutzbar halten: ensureRouteCorridors beim Start-Check
  (Stichproben-Verify, Re-Download aus preview_track, Region-Dedupe)
- Stub-Tests ins Repo: tests/js/ (r1/r3/r4/r5, alle gruen)
Bump v1231
2026-06-06 13:00:20 +02:00
53bc27faa3 Offline-Karten Runde 4: Minimal-Speicher-Modell (Modell Rene)
Funkloch-Gedaechtnis = Quelle der Wahrheit, Kacheln = ableitbarer Cache:
- Ephemeres Vorausladen: Prefetch-Kacheln werden bei Aufzeichnungsende
  geloescht, wenn die Runde kein Funkloch hatte (nur neue Keys)
- Netz-Probe bei Aufzeichnung (~2min, 6s-Timeout): erkennt Funkloecher auch
  in bereits gespeicherten Gebieten (dort kein Remote-Miss als Signal)
- clear() behaelt Zonen (filled=false) -> naechster Online-Start laedt
  Funkloch-Gebiete automatisch neu, auch nach 'Alles loeschen'
- Start-Check mit Position: nur Zonen im 50-km-Umkreis, naechste zuerst,
  Coverage-Verify (faengt Eviction ab); Pfote Segment 5 = alle Zonen gefuellt
- Coverage-Layer zweifarbig: Funkloch orange, manuell blau + Modal-Legende
- Stub-Tests Runde 4 (Prune/Probe/clear/Naehe/Verify/Faerbung) bestanden
Bump v1230
2026-06-06 12:46:12 +02:00
763108fa7c Offline-Karten Runde 3: Puls-Icon, rollendes Vorausladen, Ausschnitt-Download, Speicher-Cap
- Offline-Indikator: pulsierendes 32px-Icon oben rechts (unter Kopfzeilen-Hoehe)
  statt Leiste ueber die volle Breite — verdeckte '<- Zurueck' in der
  Routennavigation (Geraetetest Rene)
- Rollendes Vorausladen: setGps laedt alle ~400m still fehlende z14+-2-Kacheln
  um die Position — deckt den Weg schon beim ERSTEN Funkloch-Besuch ab
- Bereichsauswahl light: 'Sichtbaren Ausschnitt speichern' im Offline-Modal
  (downloadBbox, Cap 40 MB, Zu-gross-Schutz)
- Speicher-Cap 250 MB als Soft-Guard fuer automatische Pfade + totalBytes-Zaehler
  + navigator.storage.persist() best-effort; echte LRU vertagt (Refcounting noetig)
- Auto-OSM-Raster-Prefetch entfernt (manueller Leaflet-Pfad bleibt)
- Logik-Tests (Node-Stubs) fuer Bbox/Cap/Throttle/persist bestanden
Bump v1229
2026-06-06 12:34:48 +02:00
3426d2b7c8 Bump v1228 (Offline-Karten Prod-Release) 2026-06-06 12:21:15 +02:00