Platsch! (Funk-Blues-Groove, md5 7f5197a0) und die ruhige Ballade
Bester Freund (md5 c244dd15, Album-Ruhepol) ans Ende des SONGS-Arrays.
make bump → v1300.
Aus der einzelnen Hymne wird ein Album (WELT-Welt): runder Button / Banner
öffnen jetzt ein Album-Modal mit Liste (Play je Titel, aktiver Song orange,
Auto-Next zum nächsten, zentrales Audio läuft über Welt-Wechsel weiter). Songs
als Array (statischer Content), on-demand-Cache pro Datei.
Songs — alle Suno Pro, kommerziell lizenziert:
- Ban Yaro Blues (Hymne) — Pro-Version ersetzt die Free-Aufnahme (Cache-Bust ?v=2)
- Ban Yaro Mobil — erste Anhänger-Fahrt durch die Prärie
- Amy — Liebesromanze (Jack-Russell-Mädchen)
audio-src in index.html geleert (Album setzt src dynamisch).
Eigener Song (KI-Demo via Suno) als Marken-Hymne. Dezente Player-Karte unter
dem Tageszitat; preload=none → 6 MB MP3 lädt erst bei Play, der SW cacht sie
danach für offline. Der Banner ist einmalige Einladung und verschwindet nach
erstem Hören (durchgehört oder >30s + Pause); danach dezenter runder Play-Button
unten links als Gegenspieler zum FAB, nur in WELT. Audio-Element zentral in
index.html → übersteht Welt-Wechsel & Re-Renders.
„Gehört" wird hybrid gemerkt: localStorage (sofort/offline) + DB-Flag
anthem_heard am User (neue Spalte, über /auth/me, gesetzt via
POST /api/profile/anthem-heard) — geräte- und deploy-übergreifend, damit der
Banner nicht erneut nervt.
Wochenrückblick (diary.js _loadPraise) merkt sich jetzt das Wegklicken pro
Kalenderwoche (localStorage by_diary_praise_dismissed) — kommt nicht mehr bei
jedem Öffnen. Lob-Text abwechslungsreich (scheduler.py): wöchentlich
rotierender KI-Fokus + Fallback-Varianten-Pool statt einem festen Satz,
prominente Wochenzahl raus.
WELT-Welt Tageszitat: _QUOTES von 31 auf 171 erweitert (web-recherchiert,
57% mit benannter Quelle statt vorher 29%) — Wiederholung erst nach ~5,7
Monaten statt monatlich.
Wiederverwendbarer UI.noteMediaAttacher für beide Notiz-Stellen (UI.noteModal
+ Notizblock-Seite). note_media-Tabelle + POST/DELETE /api/notes/{id}/media
(vor der gierigen /{parent_type}/{parent_id}-Route). Audio per MediaRecorder,
serverseitig nach m4a/AAC transkodiert (ffmpeg) — iOS spielt Chrome-Opus-webm
nicht ab. UI.lightbox global eingeführt. Mikrofon-Policy microphone=(self) +
CSP media-src 'self' blob:, Datenschutz v6. Disk-Cleanup für note_media bei
Notiz-, Account- und Admin-User-Delete. Reine Medien-Notiz ohne Text erlaubt.
noteModal-Bug gefixt: notes.get() liefert Array -> existing[0] statt
existing?.id (verhinderte Bearbeiten, erzeugte Duplikate). 12 neue Tests.
admin.py enthält außerdem KI-Vision-Statusfelder aus paralleler Arbeit
(nicht sauber trennbar ohne interaktives Staging).
Rene: gleiche URL #partner, anderer Inhalt — Nav-Button (data-page→navigate)
schaltete um, der <a href='#partner'>-Link nicht. Ursache: Router reagierte
nur auf [data-page]-Klicks + App.navigate(), nie auf echte hashchange-Events.
Plain Hash-Links änderten die URL ohne Seitenwechsel (Default blieb 'diary').
Globaler hashchange-Listener ergänzt (gleiches Hash-Parsing wie init, inkl.
?-Params); pushState in navigate() feuert kein hashchange → keine Schleife;
replaceState schreibt {page} in die History, damit Back/Forward die Seite kennt.
Repariert app-weit ALLE Seiten-Hash-Links (#settings, #agb, #litters,
#partner, #partner-dashboard), nicht nur den neuen Gründer→Partner-Link.
Interessierte verstehen jetzt das System: 3-Schritte-Erklärung (Partner-Code
→ erstes Hundeprofil aktiviert den Platz → für immer Gründer #N) + vier
Vorteils-Karten (nummerierte Badge in Profil+Forum, lebenslang Pro gratis,
50% für Geworbene, Teil der Geschichte). Wording konsistent zur /partner-
Landing + Settings; CTA verlinkt auf #partner wenn noch Plätze frei sind.
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.
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.
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.
- 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.
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.
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).
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.
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.
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)
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
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.
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.
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.
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.
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
- 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
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
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
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