TT-Control — Erfahrungsbericht
Wie aus einer Idee in 21 Tagen ein vollständiges Turnierverwaltungssystem mit 57 Features wurde — in rund 85 Stunden Arbeitszeit, ausschließlich mit KI-Unterstützung.
Die Anfänge (21. März 2026)
Alles begann mit dem AI Coding Starter Kit — einem Next.js-Template mit vordefinierten Workflows für Requirements, Architecture, Frontend, Backend, QA und Deploy. Das Projekt startete als Experiment: Kann man mit KI-Unterstützung ein komplexes Fachsystem bauen, das im echten Vereinsleben funktioniert?
Der erste Commit war am 21. März 2026. Innerhalb weniger Stunden standen die Infrastruktur (Starter-Kit-Anpassungen, shadcn/ui-Komponentenbibliothek, Supabase-Anbindung), das PRD (Product Requirements Document) und die ersten 10 Feature-Specs. Die Vision war klar:
Ein System, das Papier und manuelle Tabellen bei Tischtennisturnieren ersetzt — mit Tablets an jedem Tisch, Echtzeit-Scoring und automatischer Auslosung.
Phase 1: Das Fundament (21. — 24. März)
PROJ-1 bis PROJ-5 waren der Grundstein:
- Admin-Login mit Supabase Auth
- Spielerverwaltung mit PIN-System
- Turniererstellung mit flexiblen Formaten
- Auslosungsalgorithmus (Gruppen + KO)
- Live Score Entry — das Herzstück: Ein Tablet-Scoreboard mit Touch-Bedienung
Erkenntnis 1: Hardware-Pläne bremsen. Ursprünglich waren ESP32-basierte physische Anzeigetafeln geplant. Nach einer Woche Firmware-Recherche fiel die Entscheidung: Alles browser-basiert. Tablets zeigen Scores, TVs zeigen Brackets — keine App-Installation, kein Hardware-Debugging. Diese Entscheidung sparte Wochen.
Erkenntnis 2: QA von Anfang an. Jedes Feature durchlief den /qa-Skill bevor es deployed wurde. PROJ-5 (Live Score Entry) hatte beim ersten QA-Lauf 20 Bugs — darunter 3 kritische. Ohne strukturiertes QA wären diese erst im Turnierbetrieb aufgefallen.
Phase 2: Das Spieler-Ökosystem (24. — 26. März)
Hier wurde aus dem Admin-Tool ein Multi-User-System:
- PROJ-12: Spieler können sich selbst registrieren
- PROJ-13: Spieler-Portal mit Live-Tracking der eigenen Spiele
- PROJ-14: Erweiterte Spielbestätigung (PIN am Tablet nach Spielende)
- PROJ-15: Kontoverwaltung (Passwort, PIN, E-Mail ändern)
- PROJ-16: Betreuer-Rolle (Trainer sehen die Spiele ihrer Schützlinge)
Erkenntnis 3: Rollen-Architektur früh festlegen. Die Einführung von app_metadata.role in Supabase war eine der besten Entscheidungen. Admin, Spieler, Betreuer, Schiedsrichter — jede Rolle hat eigene Middleware-Guards im proxy.ts. Das Muster (Login-Seite → Dashboard → Rollenspezifische Views) wurde zum Template für jede neue Rolle.
Größter Aufwand: PROJ-14 (Spielbestätigung) — 3 QA-Runden, 12 Bugs. Der PIN-Flow am Tablet war komplex: Wann wird PIN verlangt? Was passiert bei Timeout? Was wenn der Schiedsrichter übernimmt? Hier brauchte es mehrere Iterationen.
Phase 3: Der Turniertag (26. — 30. März)
Diese Woche war die intensivste. Alles musste für einen echten Turniertag funktionieren:
- PROJ-19: TTR-Werte und Altersklassen
- PROJ-20: Rollenverwaltung (Admin vergibt Rollen)
- PROJ-21: Spielverwaltung (Kampflos, 3-Aufruf-Regel)
- PROJ-22: Multi-Turnier-Management (mehrere Turniere parallel, Tischverwaltung)
- PROJ-23: Schiedsrichter-Rolle (PIN-Übernahme, Kartensystem, Bestätigung)
- PROJ-25: TTR-Lostöpfe (ausgewogene Gruppenauslosung nach Spielstärke)
- PROJ-26: Tablet-UI-Verbesserungen (größere Satzanzeige, Timer)
- PROJ-27: Zeitslots und Überfälligkeitsanzeige
Erkenntnis 4: Multi-Turnier ist ein Multiplier. Sobald zwei Turniere gleichzeitig laufen, verdoppelt sich die Komplexität nicht — sie vervielfacht sich. Tische werden geteilt, Spieler sind in beiden Turnieren. Die Tisch-Zuordnung über Labels statt UUIDs führte zu einem hartnäckigen Bug: Ein Doppel-Turnier zeigte auf "Tisch 1" das Spiel eines anderen Turniers. Lösung: Exakter UUID-Match statt Label-basiertem Lookup.
Erkenntnis 5: Der 3-Minuten-Bug. Die Auslosung für 36 DD-Spieler dauerte mehrere Minuten. Der Grund: Ein O(n!!)-Backtracking-Algorithmus (Doppelfakultät!). Fix: Greedy-Matching für N>16 mit O(n² log n). Von Minuten auf Millisekunden.
Phase 4: Die Community-Features (1. — 3. April)
Das System funktionierte — jetzt kamen die Features, die es besonders machen:
- PROJ-29: Landingpage mit Feature-Übersicht und Screenshot-Karussell
- PROJ-30: Admin-Spielerverwaltung (Portal-Zugang, TTR-Plausibilität)
- PROJ-31: Spieler-Profil mit TTR-Anzeige
- PROJ-32: Gruppen-Detailansicht (Kreuztabelle wie bei echten Turnieren)
- PROJ-33: Spectator Bracket (öffentlicher Turnier-Zugang ohne Login)
- PROJ-34: Dark Mode
- PROJ-36: Urkunden-Designer (5 Vorlagen, SVG-Editor, Druck)
- PROJ-37: Abschlussberichte (Spieler, Betreuer, Admin + CSV-Export)
Erkenntnis 6: Urkunden waren unterschätzt. Was als "PDF generieren" begann, wurde ein vollständiger Designer: 3 Tabs, SVG-Bibliothek, Layer-System, Resize, 5 vorgefertigte Vorlagen, Freitext-Bausteine, Platzierungs-Zuordnung. 7 Commits über 2 Tage. Manchmal wächst ein Feature organisch.
Phase 5: Multi-Tenancy + Doppel-Innovation (4. — 6. April)
PROJ-35: Multi-Tenancy — der größte Umbau. 15 Tabellen, 70 API-Routen, RLS-Policies. "Invisible Tenant"-Strategie: Default-Tenant-UUID als Fallback, keine Breaking Changes für bestehende Daten.
PROJ-38: Fotogalerie mit EXIF-Plausibilitätsprüfung und Magic-Byte-Validierung
PROJ-39: Dynamisches Doppel — das innovativste Feature. Wechseldoppel mit TTR-basierter Vorgabe, Handicap-Kurven-Editor, Partner-Rotation über Runden. Hier floss das meiste Domain-Wissen ein:
- Handicap-Berechnung aus TTR-Differenz
- Partner-Matching: Niemand spielt zweimal mit dem gleichen Partner
- Gegner-Vermeidung: Kein Team trifft zweimal aufeinander
- Greedy-Algorithmus für große Teilnehmerfelder
PROJ-40: Venue-Tischpool — gemeinsamer Tisch-Pool für parallele Turniere am selben Ort
Phase 6: Qualitätssicherung (6. April)
Der größte QA-Marathon: 16 parallele QA-Agents analysierten alle Features ohne bisheriges QA. Ergebnis: ~72 neue Bugs dokumentiert, davon 12 sofort gefixt (Security, Gameplay-Breaking, UI).
Erkenntnis 7: Bulk-QA deckt Muster auf. Viele Bugs waren Varianten desselben Problems: Hardcoded German strings (i18n), fehlende tenant_id-Filter (Multi-Tenancy), fehlende Dark-Mode-Varianten. Ein systematischer QA-Durchlauf zeigt diese Muster besser als Feature-für-Feature-Tests.
Phase 7: DTTB-Konformität + Logging (6. — 7. April)
PROJ-23 Überarbeitung: Das Kartensystem wurde DTTB-konform: Gelbe Karte (Verwarnung), Gelb-Rote Kombikarte (+1 Punkt), Rote Karte (+1 Punkt). Visuell mit Gradient-Button und SVG-Icon.
PROJ-41: Admin Live-Logging — CLF-Logs, lachsrosa tail-f-Popup, konfigurierbare Logserver (HTTP Webhook + Syslog).
Zahlen
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 8. Apr 2026 (19 Tage) |
| Commits | 370+ |
| Features | 46 (PROJ-1 bis PROJ-46) |
| Features deployed | 46 |
| Bugfixes | 150+ |
| Seiten/Views | 22+ (Admin, Spieler, Betreuer, Schiedsrichter, Spectator, Tablet) |
| API-Routen | 85+ |
| DB-Tabellen | 22+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Parallele QA-Agents max | 16 gleichzeitig |
Die wichtigsten Learnings
1. Workflow-Disziplin zahlt sich aus
Der Zyklus /requirements → /architecture → /frontend → /backend → /qa → /deploy war kein Overhead — er war die Versicherung. Jedes Feature, das diesen Zyklus durchlief, funktionierte beim ersten Turniereinsatz.
2. QA ist kein Schritt, sondern eine Kultur
Features mit QA hatten signifikant weniger Post-Deploy-Bugs. Die 16 Features, die ohne QA deployed wurden, produzierten zusammen ~72 Bugs — mehr als alle QA-geprüften Features zusammen.
3. Domain-Wissen ist der Flaschenhals
Die technische Umsetzung war selten das Problem. Die Fragen "Wie funktioniert das DTTB-Kartensystem wirklich?" oder "Wie berechnet man TTR-basierte Vorgaben?" brauchten Regelexperten-Wissen. Die Erstellung der dttb-regelreferenz.md war ein Wendepunkt — danach waren alle Tischtennis-spezifischen Entscheidungen fundiert.
4. Parallelisierung skaliert
Die Architektur mit spezialisierten Agents (QA, Frontend, Backend, Explore) erlaubte massive Parallelisierung. 16 QA-Agents gleichzeitig, Bug-Fixes parallel zum Deploy. Ein einzelner Entwickler könnte das nicht — aber mit KI-Unterstützung wurde es zum normalen Arbeitsrhythmus.
5. Einfachheit gewinnt
- Browser statt Hardware → keine Firmware-Bugs
- Supabase statt Custom-Backend → Auth + Realtime + Storage out-of-the-box
- shadcn/ui statt Custom-Components → konsistentes UI ohne Design-Debt
- Default-Tenant statt Migrations → Multi-Tenancy ohne Breaking Changes
6. Die teuersten Bugs sind Architektur-Bugs
Der Label-basierte Tisch-Lookup (statt UUID), der O(n!!)-Algorithmus, die fehlende tenant_id auf draw_log — diese Fehler waren jeweils mehr Aufwand als 10 UI-Bugs zusammen. Gute Architektur-Reviews vor dem Coding hätten sie verhindert.
Phase 6: Rechtliches & Polish (6.–7. April)
Am 6. April kamen in einer Session die drei Säulen der Rechtssicherheit: Impressum (§ 5 DDG), Datenschutzerklärung (DSGVO-konform mit 10 Abschnitten) und AGB (14 Paragraphen). Alle Registrierungsformulare erhielten Pflicht-Checkboxen für AGB und Datenschutz.
Parallel wurden zahlreiche Bugfixes für das Dynamische Doppel deployed — die DTTB-konforme Aufschlagrotation mit 3-Schritt-Wahl (Team → Aufschläger → Rückschläger), korrekte Vorgabe-Punkte in allen Sätzen, und die Urkunden-Generierung für DD-Turniere.
Weitere Verbesserungen: Vollbild-Button für Tablets, ausgewogene Gruppenspiel-Reihenfolge im GROUP_KO-Modus, Tischzuweisung in allen Turniermodi, und Foto-Thumbnails in der Galerie gefixt.
In Zahlen (Stand 7. April)
| Kennzahl | Wert |
|---|---|
| Geschätzte Gesamt-Arbeitszeit | ~70 Stunden |
| Kalendertage (Start bis heute) | 18 |
| Aktive Arbeitstage | 14 |
| Commits | 338 |
| Features (PROJ-1 bis PROJ-41) | 41 |
| Davon deployed | 37 |
| Intensivster Tag | 5. April (42 Commits, ~10h) |
Phase 7: Pool-Occupancy, Karten-System, neue Formate (7.–8. April)
In zwei intensiven Sessions wurden die letzten offenen Bugs beseitigt und vier neue Features implementiert.
Bugfixes (7. April)
Pool-weite Tischbelegung: Die Tischzuweisung in der Draw-Ansicht und der Tischverwaltung berücksichtigt jetzt alle Turniere die sich einen Tischpool teilen. Die API /api/tournaments/[id]/tables liefert occupied_table_ids pool-weit. Frontend-Komponenten (draw-tab, dd-draw-view, group-detail-sheet) zeigen belegte Tische mit roten Punkten.
Tischverwaltung Live-Upgrade: Auto-Polling alle 5 Sekunden, Live-Spielstand mit Satz-Ergebnissen, klickbare Links zur Turnier-Detailseite, und der Tisch-Display-Link funktioniert jetzt korrekt.
Karten auf Display + Satzende: Die Zuschauer-Anzeige (/display/table/...) zeigt jetzt Gelbe/Gelb-Rote/Rote Karten als Badge neben dem Spielernamen. Wenn eine Strafkarte (Gelb-Rot/Rot) den entscheidenden Punkt verursacht, wird der Satz/das Spiel automatisch server-seitig abgeschlossen — inklusive set_scores, Satzwechsel und ggf. Match-Ende.
Display Endergebnis: Nach Spielende zeigt die Tisch-Anzeige den vollständigen Endstand 30 Sekunden lang — inklusive des letzten entscheidenden Satzes (vorher fehlte dieser, weil die API match: null zurückgab).
Tablet-Synchronisation: Der Realtime-Callback im useScoreboard-Hook erkennt jetzt externe Satzabschlüsse (z.B. durch Karten), lädt die Satzhistorie nach, startet den Satzpause-Timer und triggert den Seitenwechsel.
Reset mit Karten-Dialog: Beim Zurücksetzen eines Spiels erscheint ein Zusatzdialog: "Karten auch zurücksetzen?" mit den Optionen "Karten behalten" und "Karten auch löschen".
PROJ-40 Bugfixes (6 Stück): Gruppendetail-Sheet pool-aware, Conflict-Meldung mit Spielnummer, Race-Condition-Check bei Tischzuweisung, Pool-Leave-API, Pool-UI bei Turnier-Neuerstellung sichtbar.
PROJ-35 Multi-Tenancy deployed (8. April)
Phase 1 (DB-Fundament) und Phase 2 (Tenant-Context im Code) als "Deployed" markiert. Alle 11 QA-Bugs behoben:
- Registrierung setzt
tenant_idinapp_metadata - Public-Tournament-Route filtert auf Tenant
- Table-Routen: Hardcoded
DEFAULT_TENANT_IDdurch UUID-basiertes Lookup ersetzt - Anon-RLS-Policies auf Default-Tenant eingeschränkt
get_tenant_id()SQL-Funktion auf SECURITY INVOKER umgestellt- Admin-Users: Kein Fallback mehr für User ohne
tenant_id
Neue Features (8. April)
PROJ-42: Spectator Live-Board — Venue-weite Zuschauer-Anzeige unter /display/venue?location=...&date=.... Zeigt alle Tische eines Veranstaltungsorts als responsives Grid mit Live-Punktestand, Spielernamen, Satzstand. Auto-Refresh 3s, Vollbild per Doppelklick. Links in Tischverwaltung und Turnier-Detail.
PROJ-43: Jeder gegen Jeden (Round Robin) — Neues Turnierformat: Alle Spieler spielen gegen alle. Circle-Method-Algorithmus für ausgewogene Spielreihenfolge. Live-Tabelle mit Rang, Siegen, Sätzen, Punkten und Differenz. DB-Constraint-Migration für das neue Format.
PROJ-44: Turnier kopieren — Copy-Button im Turnier-Header erstellt ein neues Turnier mit identischen Einstellungen (Format, Tische, Satzformat, Restriktionen) aber neuem Datum.
PROJ-46: Statistik-Dashboard — /admin/stats mit KPI-Karten (Turniere, Spieler, Spiele, Ø Dauer), Balkendiagrammen (Formate, Status, Spiele/Monat) und den letzten 10 Turnieren.
In Zahlen (Stand 8. April)
| Kennzahl | Wert |
|---|---|
| Geschätzte Gesamt-Arbeitszeit | ~78 Stunden |
| Kalendertage (Start bis heute) | 19 |
| Aktive Arbeitstage | 15 |
| Commits | ~370 |
| Features (PROJ-1 bis PROJ-46) | 46 |
| Davon deployed | 46 |
| API-Routen | 85+ |
| DB-Tabellen | 22+ |
Phase 8: Multi-Tenancy wird sichtbar (8.–9. April)
PROJ-35 Phase 3 brachte die erste sichtbare Multi-Tenancy-Funktionalität:
- Super-Admin Rolle: Neuer höchster Rollentyp, impliziert Admin-Rechte
- Mandanten-Verwaltung (
/admin/tenants): CRUD für Tenants mit Logo-Upload, Primärfarbe, Limits - Tenant-Switcher: Dropdown oben links im Admin-Header zum Wechsel zwischen Mandanten
- Tenant-Branding: Logo ersetzt Standard-Logo im Header, Primärfarbe als CSS Custom Property
- Nutzerverwaltung erweitert: Super-Admins sehen alle User aller Tenants, können Admins zwischen Mandanten verschieben, Filter nach Rolle und Mandant
- get_tenant_id() gehärtet: COALESCE-Fallback entfernt — kein tenant_id im JWT = Zugriff verweigert
Erkenntnis 8: Multi-Tenancy ist ein Multiplikator für Sicherheitstests. Die QA fand 10 Bugs in Phase 3, darunter einen Critical: hasRole("admin") erkannte super_admin nicht als Admin. Ohne QA wäre die gesamte Mandanten-Verwaltung unbenutzbar gewesen. Auch die Storage-RLS-Policies mussten sowohl das Array-Format als auch das Legacy-String-Format unterstützen.
Parallel entstanden die Feature-Specs für zwei neue Turnierformate:
- PROJ-47: Einfaches Doppelturnier (direkt) — eigenständiges Doppel-KO
- PROJ-48: Double-Elimination-Turnier — Winners/Losers Bracket mit Grand Final und Bracket Reset
Phase 9: Plan-System, Ampelstatus & Daten-Export (9.–10. April)
In zwei intensiven Sessions wurden vier neue Features deployed und die Monetarisierungs-Grundlage gelegt.
PROJ-49: Plan-System & Limit-Enforcement — Das Herzstück der Monetarisierung. Vier Tarife (Free/Starter/Club/Verband) mit zentraler Plan-Config, Enforcement in 8 API-Routen und einer Usage-Card im Dashboard die Auslastung als Fortschrittsbalken zeigt. Super-Admins können Plans pro Mandant zuweisen, die Limits werden automatisch aus der Config übernommen.
PROJ-53: Daten-Export — DSGVO Art. 20 konformer ZIP-Vollexport. 8 CSV-Dateien (Turniere, Spieler, Ergebnisse, Satzergebnisse, Doppelpaare, Betreuer, Schiedsrichter, Tische), Einstellungen als JSON, bis zu 100 Fotos aus Storage. Neuer Tab "Datenexport" in den Admin-Einstellungen.
PROJ-54: Spieler-Ampelstatus — Das Feature mit dem größten Impact auf den Turnierbetrieb. Vier Zustände (grün=spielbereit, rosa=aufgerufen, rot=im Spiel, gelb=pausiert) werden live in Brackets, Gruppen und Spiellisten angezeigt. Die Tischverwaltung wurde komplett überarbeitet: Split-Layout mit Drag & Drop Warteschlange, Status-Filter, Einzel/Doppel-Filter, numerische Tisch-Sortierung. Supabase Realtime für instantane Updates.
PROJ-55: Excel-Bulk-Import — Alternative zum click-TT CSV-Import für Vereine ohne click-TT-Zugang (z.B. Norddeutschland mit Henke-Software). Excel-Vorlage zum Download, SheetJS-Parsing, flexible Spaltenerkennung. Der Import-Dialog akzeptiert jetzt CSV + XLSX + XLS.
~20 Bugfixes in derselben Session: Tenant-Switcher State, Pool-Tisch-Erkennung, Auto-Tischzuweisung im Draw entfernt, Gruppenspiele in Warteschlange, numerische Sortierung in 7 API-Routen, AGB für Paid-Tarife angepasst.
Erkenntnis 9: Plan-Enforcement ohne Stripe ist ein guter erster Schritt. Die Limits funktionieren, Plans können manuell zugewiesen werden. Stripe kommt als separater Schritt — so kann das System sofort mit Pilotkunden getestet werden.
In Zahlen (Stand 10. April)
| Kennzahl | Wert |
|---|---|
| Geschätzte Gesamt-Arbeitszeit | ~85 Stunden |
| Kalendertage (Start bis heute) | 21 |
| Aktive Arbeitstage | 17 |
| Commits | ~410 |
| Features (PROJ-1 bis PROJ-57) | 57 |
| Davon deployed | 51 |
| API-Routen | 90+ |
| DB-Tabellen | 24+ |
Phase 10: Ticket-System & Support-Infrastruktur (11.–14. April)
Nach dem Plan-Fundament kam die erste echte Kundenorientierung: ein vollständiges Support-System, gebaut in einem Stück.
PROJ-58: Ticket-Erfassung — Admins und Spieler können Bug-Reports und Feature-Wünsche direkt aus der App einreichen. Das Besondere: KI-gestützte Deduplizierung erkennt ähnliche Tickets vor dem Einreichen und schlägt vor, ein bestehendes Ticket zu kommentieren statt ein neues zu öffnen. Kategorien, Prioritäten und Screenshots-Upload direkt im Formular.
PROJ-59: Ticket-Verwaltung — Das Superadmin-Dashboard zeigt alle Tickets aller Tenants, sortierbar nach Status, Priorität und Kategorie. Journal-Kommunikation erlaubt Hin-und-her-Kommentare zwischen Superadmin und Melder. Interne Notizen (nur für Superadmin sichtbar) halten den Bearbeitungsstand fest.
PROJ-60: Ticket-Benachrichtigungen — E-Mail bei Statusänderungen, neuen Kommentaren und Ticket-Abschluss. Admins können sich für Benachrichtigungen auf eigene Tickets abonnieren.
PROJ-61: UI-Modernisierung (Tablet & Display) — Die Scoring-Tablets und Live-Anzeigen erhielten ein visuelles Upgrade: Glassmorphism-Cards, Neon-Glow-Effekte, animierte Score-Übergänge. Die Dark-Mode-Implementierung wurde auf allen Display-Screens verfeinert.
Erkenntnis 10: Ein internes Support-System zahlt sich früh aus. Noch bevor der erste externe Kunde aktiv war, halfen die Tickets dabei, eigene Bugs strukturiert zu erfassen und zu priorisieren. Das Journal-Muster (sequentielle Kommentare, kein Chat-Chaos) erwies sich als überlegen gegenüber dem ursprünglichen Ansatz mit Thread-Strukturen.
Phase 11: Monetarisierung wird real (17.–22. April)
Das war die Woche, in der TT-Control vom Hobby-Projekt zum echten SaaS-Produkt wurde.
PROJ-50: Mollie-Integration — Statt Stripe (zu komplex für deutschsprachige Vereine) wurde Mollie als Zahlungsdienstleister integriert. Checkout-Flow, Webhooks für asynchrone Zahlungsbestätigungen, SEPA-Lastschrift und PayPal. Die Mollie-Abo-Verwaltung im Admin-Bereich zeigt Rechnungen, Zahlungsstatus und den nächsten Abrechnungstermin. Alle Test-Zahlungen liefen über Mollie Test-Cards durch.
PROJ-51: Upgrade & Downgrade — Tarifwechsel mit Nutzungsprüfung: Wechselt ein Mandant auf einen kleineren Tarif, prüft das System ob die aktuelle Nutzung die neuen Limits überschreitet. Downgrade-Blockierung mit konkreter Fehlerliste ("3 aktive Turniere über dem Starter-Limit"). Upgrade sofort wirksam, Downgrade zum Ende des Abrechnungszeitraums.
PROJ-52: Grace Period & Nur-Lesen-Modus — Der Ablauf-Lifecycle: Nach Zahlungsausfall 14 Tage Kulanzzeit (Nur-Lesen: bestehende Daten einsehbar, keine Neuerstellung), dann Archivierung, nach 90 Tagen automatische Löschung per Cron-Job. Banner im Dashboard warnt vor ablaufendem Plan.
PROJ-62: PIN-Bestätigung-Toggle — Spieler-PINs sind jetzt optional pro Turnier schaltbar. Viele Vereine brauchen keine Bestätigung durch Spieler — ein einfacher Toggle im Turnier-Setup deaktiviert den gesamten PIN-Flow.
PROJ-63: Superadmin Statistik-Dashboard — Überblick über alle Tenants: Aktive Plans, Umsatz-Projektion, neue Anmeldungen der letzten 30 Tage, Fehler-Logs pro Tenant. Application-Level-Logging mit strukturierten JSON-Logs.
Infrastruktur (April 17–22):
- DEV-Server rescaling CX23 → CX33 (4 GB RAM) wegen Build-OOMs
- Ansible vollständig auf WSL Ubuntu umgestellt (Windows-Pfad-Probleme behoben)
- CrowdSec auf allen drei Umgebungen deployed: Länder-Blocks für CN, RU, KP, IR
- Mollie-Bug in Coolify PATCH-Request behoben (falsche Content-Type-Header)
Erkenntnis 11: Mollie vor Stripe war die richtige Wahl. Mollie hat eine klare DAPI für deutschsprachige Märkte, SEPA-Lastschrift out-of-the-box, und eine bessere Test-Card-Übersicht als Stripe. Einziger Wermutstropfen: Mollie-Iframe-Felder sind in Playwright-Headless nicht befüllbar (PCI Security Model) — UAT-Tests mussten bis zur Mollie-Checkout-Seite testen, nicht darüber hinaus.
Phase 12: Self-Service-Onboarding & E-Mail-Infrastruktur (22.–26. April)
Die größte Infrastruktur-Session des Projekts: Von manuellem Admin-Anlegen zu vollständigem Self-Service.
PROJ-64: Default SMTP & Tenant E-Mail-Alias-Management — Jeder Mandant erhält automatisch einen eigenen E-Mail-Alias auf der Shared-Infrastruktur (vereinsname.noreply@tt-control.de). Mail-in-a-Box (MiaB) verwaltet die Aliases per REST-API. DB-First-Strategie: Zuerst pending-Eintrag in DB, dann MiaB-API-Call, Status wird auf active oder failed gesetzt. Umgebungs-Prefixe: DEV bekommt vereinsname.dev.noreply@..., TEST vereinsname.test.noreply@.... Root-Cause der initialen Fehler: system_settings hatte keine miab_*-Keys — direkt per psql auf DEV/TEST/PROD nachgetragen.
PROJ-65: Spieler-QR-Selbstregistrierung — Spieler können per QR-Code ihren bestehenden Spieler-Datensatz mit einem Account verknüpfen, ohne dass der Admin eingreifen muss. QR-Code wird vom Admin gedruckt und am Tisch ausgelegt.
PROJ-66: Turnier-Check-in & QR-Code-Druck — Anwesenheitsverfolgung für den Turniertag. QR-Codes für alle Spieler als 4×A4-Druckbogen. Verifikation per Scan oder PIN. Check-in-Status in der Spielerliste des Admins sichtbar.
PROJ-67: Self-Service Admin-Signup — Der wichtigste Schritt zur Skalierung: Neue Admins können sich selbst registrieren, ohne dass der Superadmin eingreifen muss. Tenant-Erstellung, E-Mail-Verifikation, Welcome-Screen mit Plan-Auswahl und optionalem Checkout. Pflicht-Checkboxen für AGB und Datenschutz mit DB-Speicherung (Zeitstempel terms_accepted_at, privacy_accepted_at). Fix für SMTP bei Signup: Das neue System nutzt den Default-Tenant-Mailserver bis der Mandant seinen eigenen konfiguriert hat.
PROJ-68: UAT Playwright Regression Suite — Vollständige End-to-End-Testsuite mit Playwright. 35 Tests in 9 Kategorien (Auth, Signup, Settings, Billing, Tournaments, Players, Draw, Match, Display). Die Suite läuft gegen die Staging-Umgebung. Mollie-Tests verifizieren bis zur Checkout-Seite (Mollie Test-Mode-Banner, Betrag, Händlername) und prüfen die Payment-ID in der DB. Stand: 29 passing, 0 failing, 6 skips.
PROJ-69/70: Pricing-Modell v2 + Pricing-Seite v2 — Feature-Gates für alle PROJ-53–67-Features in die Plan-Config integriert. Neue Pricing-Seite mit Plan-Vergleichstabelle, Break-even-Analyse ("Ab X Turnieren amortisiert sich der Club-Plan"), Add-on-Sektion und rollenbasierten Zielgruppen-Karten. Vollständig in DE/EN übersetzt.
PROJ-71: E-Mail-Templates — HTML-Vorlagen mit TT-Control-Logo, WYSIWYG-Editor für Admins, 9 Inhaltstypen (Willkommen, Spielbestätigung, Passwort-Reset, usw.). Globales Layout als shadcn-Card mit Senderfarbe als Akzent.
PROJ-72: Pending Payment Flow — SEPA-Zahlungen sind asynchron — zwischen Checkout und Zahlungseingang können Tage vergehen. Das System setzt den Plan auf pending_payment, zeigt ein Dashboard-Banner, und verifiziert per Cron-Job alle 4 Stunden den Mollie-Status.
PROJ-73: Sentry Error Tracking — Automatische Fehlererfassung mit Tenant-Kontext. Server-side-Errors werden mit tenant_id, Benutzerrolle und URL-Kontext angereichert. Sentry-Performance-Monitoring für Transaktionen länger als 2 Sekunden. Dockerfile angepasst: Chunk-Fix für Sentry Source Maps.
Erkenntnis 12: Self-Service ist kein Feature, es ist eine Grundvoraussetzung. Ohne Self-Service-Signup ist jeder neue Kunde ein manueller Schritt. Mit PROJ-67 kann TT-Control vollständig unbeaufsichtigt wachsen — ein Mandant registriert sich, wählt einen Plan, zahlt, und spielt seinen ersten Turnier — alles ohne Superadmin-Eingriff.
Phase 13: Abrechnung, Compliance & Qualität (23.–27. April)
Die letzten vier Features schließen die Monetarisierungs-Infrastruktur ab.
PROJ-74: Plan-Gate-Enforcement — Die Feature-Gates aus PROJ-69 wurden in alle relevanten API-Routen und UI-Komponenten eingebaut: Abschlussberichte, Urkunden-Templates, Branding-Uploads. Upgrade-Prompts ersetzen direkte Fehlermeldungen wo sinnvoll.
PROJ-75: Rechnungsgenerierung & Rechnungsbox — PDF-Rechnungen nach deutschem Recht: Fortlaufende Rechnungsnummer, Ausstellerdaten, Leistungsdaten, Netto + MwSt. §19 UStG-Toggle (Kleinunternehmer). Korrekturrechnung-Flow. Superadmin-Übersicht aller ausgestellten Rechnungen aller Tenants.
PROJ-76: User-Greeting in der Topbar — "Hi Dirk" mit Rollen-Badge (Admin/Spieler/Betreuer). Superadmin bekommt eine farblich abgehobene Formatierung. Basis für personalisierte Onboarding-Hinweise.
PROJ-77: Promotion-Codes — Superadmin-only Funktion: Gutscheine mit Prozent-Rabatt auf Abonnements, Feature-Freischaltungen (z.B. ein bestimmtes Turnierformat kostenlos), Affiliate-Tracking. Jeder Gutschein ist einzeln in der DB erfasst mit Ablaufdatum, Einlösezeitpunkt und Statistiken (wann ausgegeben, wann eingelöst, auch abgelaufene Einlöseversuche). Ablauf-Lifecycle: Gutscheine werden nach Ablauf noch 6 Monate vorgehalten, dann per Bereinigungsroutine gelöscht. Orchestrierung ausschließlich durch den Superadmin.
i18n-Abschluss (27. April) — Die letzten nicht-übersetzten Seiten wurden internationalisiert: Pricing-Seite (~130 neue Schlüssel, PLAN_META/featureRows/plans in useMemo verschoben damit t() genutzt werden kann) und Display-Tablet-Ansicht (9 neue Schlüssel, DISPLAY_STRINGS-Objekt durch useLocale() ersetzt).
Erkenntnis 13: Rechtliche Anforderungen sind kein Nice-to-Have. AGB-Checkboxen beim Signup, Zeitstempel in der DB, §19-Toggle in Rechnungen, DSGVO-konformer Datenexport, 6-Monate-Haltepflicht für Gutschein-Logs — all das muss von Anfang an mitgedacht werden. Wer es nachrüstet, zahlt doppelt.
Phase 14: Projektpflege & Race-Turnierserie (27. April 2026)
Nach dem intensiven April-Sprint eine kürzere Session mit Fokus auf Qualität und Planung.
Projektpflege: CLAUDE.md hatte noch Überbleibsel aus dem Starter-Kit — "Deploy to Vercel" und einen langen Vercel Best-Practices-Block. Beides wurde durch die korrekten Coolify-Anweisungen ersetzt. Der /update-report-Skill fehlte YAML-Frontmatter und war damit nicht ladefähig — nachgepflegt. Kleinere Tippfehler in Skill-Dateien und CLAUDE.md bereinigt.
Race-Turnierserie geplant (PROJ-78–82): Das nächste große Feature-Paket: Race-Turnierserien sind offizielle Verbands-Turnierserien in 7 deutschen Bundesländern (SH, NDS, BW, Hessen, Rheinland, Thüringen, Bayern) mit zwei algorithmisch unterschiedlichen Systemen — TTVSH Schweizer System (Badeni-Setzung) und MKTT Cup-System (Hälftenbildung + Swiss-Switch). Fünf Specs in einer Session:
- PROJ-78: Grundstruktur — neues
RACE-Format, Anmeldeformular, Teilnehmerlimits, Vereinsregeln - PROJ-79: TTVSH-Algorithmus — Badeni-Setzung mit vollständiger Paarungstabelle (6–19 TN), Sieggruppen, Buchholz + Direktvergleich
- PROJ-80: MKTT Cup-Algorithmus — Hälftenbildung Runde 1, Badeni in Gruppen, Systemwechsel auf Swiss in R5/R6, Feinbuchholz. Bayern als offene Frage markiert (TTT2020-System unbekannt).
- PROJ-81: Spielerdaten-Import — TTLive-Scraping für TTVSH (VCard, PID, Vereinsseite), click-TT XML-Import für MKTT, DSGVO-Caching-Regel
- PROJ-82: Seriewertung — saisonübergreifende Punkte, TTVSH Top-16 Finalqualifikation, MKTT-Punkteschema konfigurierbar
Erkenntnis 14: Gute Specs schreiben sich aus gutem Domain-Wissen. Das Race-Regelwerk war mit konkreten Paarungstabellen, Quellen-PDFs und bekannten Vereins-IDs (TuS Esingen VID=193) sehr präzise vorbereitete. Dadurch konnten 5 vollständige Feature-Specs in einer Session entstehen — inklusive DSGVO-Opt-in-Modell (Consent-Timestamp in der DB), Buchholz-Beispielrechnung und Bayern-Risiko-Flag. Domain-Wissen, das vor der Spec liegt, spart Iterationen beim Coding.
Zahlen (Stand 27. April 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 27. Apr 2026 (38 Tage) |
| Commits | ~723 |
| Features | 82 (PROJ-1 bis PROJ-82) |
| Features deployed | 77 |
| Features geplant | 5 (PROJ-78–82, Race-Turnierserie) |
| Bugfixes | 300+ |
| Seiten/Views | 35+ |
| API-Routen | 110+ |
| DB-Tabellen | 30+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| UAT-Tests | 35 (29 passing, 0 failing, 6 skips) |
| Playwright-Testdateien | 9 |
| Umgebungen | 3 (DEV / TEST / PROD) mit vollständiger Ansible-Automatisierung |
Die wichtigsten Learnings (Ergänzung)
7. SaaS-Infrastruktur ist mehr als Code
Mollie-Webhooks, Sentry-Source-Maps, CrowdSec-Länder-Blocks, Coolify-PATCH-Bugs, Docker-OOMs nach Server-Rescaling — die Infrastruktur-Arbeit dieser Phase war mindestens genauso aufwendig wie das Feature-Coding. Ohne Ansible-Automatisierung und strukturierte Playbooks wäre die dreifache Umgebungsparität nicht zu halten gewesen.
8. Test-Automatisierung nach dem Fact ist teuer, aber unverzichtbar
Die UAT-Suite wurde nach 68 Features aufgebaut — das bedeutete, für jeden Test erst den Iststand zu verstehen, bevor man ihn automatisieren konnte. Besser wäre es, Tests parallel zum Feature zu schreiben. Aber auch nachträglich lohnt es sich: Nach der Suite-Einführung wurden keine Regressions mehr auf PROD beobachtet.
9. Mail-Infrastruktur ist unterschätzt kompliziert
Ein eigener SMTP-Server (Nodemailer + MiaB), verschlüsselte Credentials in der DB (AES-256-GCM), umgebungsspezifische Alias-Prefixe, Fallback auf Default-Tenant-SMTP — was wie eine technische Nebensache klingt, war mehrere Sessions wert. E-Mail ist im Turnierbetrieb nicht optional: Spielerregistrierung, Bestätigungen, Passwort-Reset, Benachrichtigungen.
Phase 14 (Fortsetzung): Echte Dateiformate — Race-Specs auf Grund und Boden (29. April)
In einer weiteren Session wurden vier echte Kompatibilitätsdateien analysiert und alle 5 Race-Specs damit verfeinert:
- spieler_import.csv — MKTT-Import-Format bestätigt:
Nachname;Vorname;Verein;Disziplin;Geburtsjahr;TTR;Geschlecht, Semikolon-getrennt,TTR= Q-TTR - Testturnier.xml — TournamentPortal-Format von
liga.nubestätigt: vollständiges Feld-Mapping aus<person>-Attributen (licence-nr, sex 0/1/2, restricted-data-processing, club-nr) - TournamentPortal.dtd — Das offizielle DTD deckte drei wichtige Erkenntnisse auf:
restricted-data-processingist unser DSGVO-Consent-Flag, bis zu 7 Sätze sind standard-konform,retired-a/retired-b= Aufgabe-Status - Testturnier.ttlive — TTLive-Ergebnis-Exportformat dokumentiert:
;Nachname, Vorname;;Nachname, Vorname;3:1;+8;-3;+6— Satz-Differenzen pro Satz, nicht nur Satzergebnis
Wichtigste neue Erkenntnis: PROJ-81 bekommt zwei Richtungen — nicht nur Import (CSV/XML/TTLive-Scraping), sondern auch Export zurück an click-TT (TournamentPortal-XML mit Spielergebnissen) und an TTLive (.ttlive-Format). Das ist nötig damit Spieler nach dem Race ihre TTR/LivePZ-Aktualisierung bekommen.
Architektur PROJ-78 fertiggestellt: Vollständige URL-Hierarchie (/admin/races, /races/[id]), 5-Tabellen-Schema, API-Routen, Begründung für eigene Tabellen statt Tournament-Erweiterung.
Erkenntnis 15: Echte Beispieldateien sind besser als jede Dokumentation. Vier Dateien haben die Specs präziser gemacht als zehn Stunden Recherche in PDFs. Das DTD-Feld restricted-data-processing hätte sonst als eigenes DSGVO-Feld doppelt implementiert werden müssen — und wäre beim XML-Export inkompatibel geblieben.
Phase 15: Wartung, Sicherheit und Werkzeuge (1. Mai 2026)
Kein neues Turnier-Feature — aber eine Session, die das Fundament besser macht.
Memory-System Bereinigung
Das Projekt-Memory-System hatte sich über 5 Wochen zu einem Archiv aus 26 Session-Logs entwickelt — 84% aller Memory-Dateien waren Sessions-Dumps ohne Retrieval-Wert. Eine systematische Bereinigung brachte das System auf ein nachhaltiges Fundament:
- 26 Session-Logs nach
/memory/archive/verschoben (Wissen nicht verloren, aber aus dem aktiven Index heraus) - Zwei neue thematische Dateien erstellt:
domain_knowledge.md(Tischtennis-Regelwerk, DTD-Format, TTLive-IDs) undopen_questions.md(offene Fragen die externe Bestätigung brauchen) - Memory-Routing-Prinzip formuliert: Memory ist ein Router, kein Archivist. Jedes Wissen soll in die richtige thematische Datei — nicht in eine Session-Dump-Datei.
- Co-Authored-By-Regel korrigiert: Die alte Regel "kein Co-Authored-By" war Vercel-spezifisch und längst überholt. Seit dem Coolify-Wechsel (April 2026) sind Co-Authored-By-Commits problemlos — die Regel wurde entsprechend aktualisiert.
Security-Fix: Passwörter aus Architecture-Seite entfernt
Die Architecture-Seite (/architecture-19050877-tt-control) hatte drei Klartextpasswörter im Quellcode — direkt in der React-Komponente. Da die Seite öffentlich im Git-Repository liegt, waren sie für jeden Leser sichtbar. Fix: Alle drei durch "siehe vault.yml" ersetzt und committed. Die Seite wird in der nächsten Session auf PROD deployed.
Erkenntnis 16: Secrets in React-Komponenten sind ein blinder Fleck. Die Passwörter waren während der Architecture-Seiten-Entwicklung "schnell eingefügt" worden — ein typisches Muster. Werkzeug-Tipp: grep -rn "password\|passwort\|secret" src/ gelegentlich ausführen.
PROJ-83: Optionale Startnummern & Fast-Lane-Abholung
Erweiterung zu PROJ-65/66 (QR-Selbstregistrierung & Check-in). Kernidee: Startnummern sind optional — der Admin aktiviert sie per Toggle pro Turnier. Wenn aktiviert:
- Spieler bekommen automatisch sequentielle Startnummern bei der Auslosung (manuell überschreibbar)
- Neues Datenbankfeld
bib_number_collected(+ Zeitstempel) intournament_players - Fast Lane View: Gefilterter Check-in-Screen für Helfer an der Ausgabestation — zeigt nur Spieler deren Nummer noch nicht abgeholt wurde. Tablet-optimiert.
- Player Portal zeigt dem Spieler seine Startnummer + Abholstatus
Das Feature ist für alle Turnierformate konzipiert, also auch für Race. Spec in features/PROJ-83-startnummern-fastlane.md.
Neue Skills: /end-session und /update-docu
Das Workflow-Ende war bisher unstrukturiert. Zwei neue Skills schließen die Lücke:
- /end-session (Memory-Router): Stellt max. 3 gezielte Fragen nach Überraschungen, nicht-offensichtlichen Entscheidungen und neuen offenen Fragen. Routet Antworten an die richtige Memory-Datei. Fragt am Ende ob /update-report und /update-docu ausgeführt werden sollen. Startet automatisch wenn der Nutzer "Pause", "Schluss" oder "tschüss" sagt.
- /update-docu: Aktualisiert die drei Dokumentationsquellen (Benutzerhandbuch, Architecture-Seite, Install-Seite) nach deployed Features oder Infra-Änderungen.
Erkenntnis 17: Workflow-Ende ist genauso wichtig wie Workflow-Anfang. Das Projekt hatte präzise Skills für Requirements, Architecture, Frontend, Backend, QA und Deploy — aber keinen strukturierten Abschluss. Wissen verschwand im Chat-Verlauf oder landete als Session-Dump ohne zukünftigen Wert. Die neuen Skills erzwingen die Routing-Entscheidung: Geht das nach technical_decisions, domain_knowledge oder open_questions?
Zahlen (Stand 1. Mai 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 1. Mai 2026 (41 Tage) |
| Commits | ~731 |
| Features | 83 (PROJ-1 bis PROJ-83) |
| Features deployed | 77 |
| Features in Bearbeitung | 1 (PROJ-78 Race Grundstruktur) |
| Features geplant | 5 (PROJ-79–83) |
| Bugfixes | 300+ |
| Seiten/Views | 35+ |
| API-Routen | 110+ |
| DB-Tabellen | 30+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| UAT-Tests | 35 (29 passing, 0 failing, 6 skips) |
| Playwright-Testdateien | 9 |
| Umgebungen | 3 (DEV / TEST / PROD) mit vollständiger Ansible-Automatisierung |
Phase 16: PROJ-83 live + PROD-Stabilisierung (2. Mai 2026)
Eine kompakte Session mit einem vollständigen Feature-Zyklus und einem echten PROD-Vorfall.
PROJ-83: Startnummern & Fast Lane — komplett in einer Session
Von der Architektur bis zum Deploy an einem Tag:
- Toggle pro Turnier: Admin aktiviert Startnummern bei der Turniererstellung — sonst kein Overhead
- Auto-Vergabe: Beim Hinzufügen eines Spielers erhält er automatisch die nächste freie Nummer (MAX+1), Unique-Constraint verhindert Duplikate auch bei gleichzeitigen Zugriffen
- Check-in-Erweiterung: Zwei neue Spalten (Startnr. + "Abgeholt"-Toggle) mit optimistischem UI, Stats-Counter zeigt "X von Y Nummern abgeholt"
- Fast Lane: Neue Seite
/checkin/fastlane/— tablet-optimiert, zeigt nur Spieler mit ausstehender Abholung, Touch-optimiert für den Helfer an der Ausgabe - QR-Druck: Startnummer prominent unter dem QR-Code, entfällt bei deaktiviertem Toggle
- Player Portal: Spieler sieht seine Startnummer und Abholstatus
- 1 neues API-Endpoint:
PATCH .../players/[playerId]/bib-numbermit 409-Kollisionsprüfung (nennt den Namen des Inhabers)
PROD-Vorfall nach Deploy: drei überlagerte Probleme
Der erste Test auf PROD scheiterte — aber nicht aus einem Grund, sondern drei gleichzeitig:
- PostgREST kannte die neue Spalte nicht —
ALTER TABLEändert das Schema, aber PostgREST hat einen eigenen Schema-Cache der nicht automatisch neuladen. Fix:NOTIFY pgrst, 'reload schema'via SSH direkt auf dem Server. - Superadmin-Tenant fehlte — der Superadmin-Account hatte eine
tenant_idim JWT die in dertenants-Tabelle nicht existierte. Manuell angelegt: "CoffeeMaster / verband". - Sentry schwieg — der Fehler wurde von der Route abgefangen (
if (error) { return 500 }) und nie geworfen.onRequestErrorin Sentry feuert nur bei unkontrollierten Exceptions. Fix:Sentry.captureException()explizit ergänzt.
Alle drei Probleme lagen hintereinander verborgen — das erste verdeckte das zweite, das zweite verdeckte das dritte.
Erkenntnis 18: PostgREST braucht nach jeder Migration ein explizites Schema-Reload. NOTIFY pgrst, 'reload schema' ist kein optionales Cleanup — es ist ein Pflichtschritt nach jedem ALTER TABLE. Das Ansible-Playbook erledigt das nicht automatisch.
Erkenntnis 19: Sentry erfasst keine caught errors — nur throws. Jede API-Route die Fehler abfängt und als JSON-500 zurückgibt, ist für Sentry unsichtbar. ~15 Routen müssen mit captureException nachgerüstet werden (nächster Sprint).
UX-Fix: Check-in als Tab
Check-in war als Button in der Turnier-Actionleiste versteckt — bei schmalem Browser-Fenster im Overflow-Scroll und für Nutzer nicht auffindbar. Ein eigener Tab (CheckIn mit Icon, direkt neben "Spieler") macht ihn immer sichtbar. Kleiner Eingriff, großer Unterschied im Turniertag-Workflow.
Zahlen (Stand 2. Mai 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 2. Mai 2026 (42 Tage) |
| Commits | ~741 |
| Features | 83 (PROJ-1 bis PROJ-83) |
| Features deployed | 78 |
| Features in Bearbeitung | 1 (PROJ-78 Race-Grundstruktur) |
| Features geplant | 4 (PROJ-79–82) |
| Bugfixes | 310+ |
| Seiten/Views | 36+ |
| API-Routen | 115+ |
| DB-Tabellen | 30+ (+ 5 Race-Tabellen geplant) |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| UAT-Tests | 35 (29 passing, 0 failing, 6 skips) |
| Playwright-Testdateien | 9 |
| Umgebungen | 3 (DEV / TEST / PROD) mit vollständiger Ansible-Automatisierung |
Phase 17: Race-Algorithmen live + Sentry-Vollausbau (2.–5. Mai 2026)
Die intensivste Algorithmus-Session des gesamten Projekts: Zwei vollständige Race-Systeme von Grund auf implementiert, debuggt und auf PROD released.
PROJ-84: Sentry vollständig ausgebaut
Nach dem PROD-Vorfall aus Phase 16 (Sentry schwieg bei caught errors) wurden alle API-Routen systematisch nachgerüstet. Eine neue apiError()-Utility sendet automatisch captureException() bevor sie den 500-Response zurückgibt. 23 API-Routen + die Reset-Password-Formularkomponente wurden damit ausgestattet.
Passwort-Reset: drei Fixes für ein Problem
Der Passwort-Reset-Flow hatte drei unabhängige, hintereinander liegende Fehler:
- PKCE → implicit Flow:
@supabase/ssrauf PKCE-Modus. Der Recovery-Link erzeugte keinencode_verifierim Browser →AuthSessionMissingError. Fix:flowType: "implicit"im Browser-Client. - GoTrue Redirect-Allowlist:
/player/reset-passwordfehlte inADDITIONAL_REDIRECT_URLS. GoTrue leitete stumm auf Homepage um. Fix: URL auf allen 3 Servern eingetragen. - Homepage-Token-Auffang: Für den Fall eines stillen Redirects fängt die Homepage jetzt
#type=recovery-Tokens ab und leitet zu/player/reset-passwordweiter.
Kein einzelner Fix hätte ausgereicht — alle drei mussten gleichzeitig stimmen.
PROJ-78: Race-Grundstruktur deployed
Der Backend-Code war bereits fertig — Deploy war der fehlende Schritt. Migrations, public API, Anmeldeformular. Ab sofort können Admins Race-Turniere anlegen.
PROJ-79: TTVSH Schweizer System
Vollständiger Zyklus von Algorithmus bis Deploy:
- Badeni-Setzung mit exakter TTVSH-Paarungstabelle (6–19 Teilnehmer)
- Sieggruppen-System (Runden 2–5: Stärkster gegen Schwächsten innerhalb gleicher Siege)
- Buchholz + Direktvergleich als Tiebreaker
- QA-Bugfix: nicht-transitiver Direktvergleich.
Array.sort()mit einem paarweisen Direktvergleich-Comparator ist bei Zyklen (A schlägt B, B schlägt C, C schlägt A) non-deterministisch. Fix: Zweiphasiger Ansatz — Phase 1: Sort nach transitiven Keys; Phase 2: Mini-Liga innerhalb gleich stehender Gruppen (Summe der H2H-Siege). Das ist transitiv. - 15 Unit-Tests mit Vitest (alle grün)
PROJ-80: MKTT Cup-System
Das komplexere der beiden Systeme:
- Runde 1: Hälftenbildung — obere gegen untere Hälfte positionsweise
- Runden 2–4: Badeni-Sieggruppen
- Runden 5–6: Systemwechsel auf Swiss — direkter Nachbarvergleich
- Freilos-Phantom-Punkte: Spieler mit Freilos bekommen bei Buchholz, Feinbuchholz und Satzdifferenz virtuelle Punkte damit das Freilos sie nicht benachteiligt (+1 Sieg, +1 Siegpunkt, +3 Sätze)
- Freilos-Vergabe: Scannen der Gesamtrangliste von der Mitte nach unten (niedrigerer Rank = mehr Siege), dann nach oben. Die Mitte des gesamten Felds — nicht der gefilterten Eligible-Pool.
- 17 Unit-Tests MKTT + 6 Tests Buchholz (alle grün)
- Alle 10 QA-Bugs behoben: darunter 3 High-Severity (Vereinsquota-Zählung, Freilos-Vergabe, MKTT-Rundenmodus)
- 38 Unit-Tests gesamt mit Vitest als neue Test-Infrastruktur
PROJ-85: Admin "Passwort vergessen?"
Kleines, nützliches Feature: Admins die ihr Passwort vergessen haben, können es direkt aus dem Login-Dialog zurücksetzen — ohne den Spieler-Bereich zu besuchen. Neue Seite /admin/forgot-password mit Admin-Branding. Der Recovery-Link nutzt den bestehenden GoTrue-Allowlist-Eintrag /player/reset-password?from=admin — der Reset-Formular erkennt den ?from=admin-Parameter und zeigt nach dem Reset den Admin-Login-Link statt den Spieler-Login.
Erkenntnis 20: Algorithmen brauchen Unit-Tests, nicht nur QA. Der Direktvergleich-Bug (Phase 17) wäre bei einem einzelnen Turnier nicht aufgefallen — er zeigt sich nur mit 3+ Spielern in einem Zyklus. Playwright-UAT testet Flows, nicht Algorithmus-Kanten. Vitest füllt diese Lücke: 38 Tests prüfen Badeni-Setzung, Swiss-Switch, Freilos-Vergabe und Buchholz in allen Grenzfällen.
Erkenntnis 21: Race-Algorithmen sind Fachdomäne, keine Implementierungsaufgabe. Die Spezifikation war präzise (TTVSH-DfB, MKTT-Regelwerk), aber die Interpretation brauchte mehrere Iterationen: Wo genau ist "die Mitte" bei ungerader Spielerzahl? Zählt ein Freilos beim Buchholz als Sieg des Freilos-Gebers? Was passiert wenn Spieler A und B identische Tiebreaker-Werte haben UND im H2H Unentschieden stehen (unmöglich im TT, aber algorithmus-seitig abgesichert). Solche Fragen beantworten sich nicht aus der Spec — sie entstehen beim Implementieren.
Zahlen (Stand 5. Mai 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 5. Mai 2026 (45 Tage) |
| Commits | ~767 |
| Features | 85 (PROJ-1 bis PROJ-85) |
| Features deployed | 82 (PROJ-1–80, PROJ-83, PROJ-84) |
| Features implementiert (kein Deploy) | 1 (PROJ-85) |
| Features geplant | 2 (PROJ-81 TTLive-Import, PROJ-82 Seriewertung) |
| Bugfixes | 330+ |
| Unit-Tests | 38 (Vitest, Race-Algorithmen) |
| UAT-Tests | 35 (29 passing, 0 failing, 6 skips) |
| API-Routen | 120+ |
| DB-Tabellen | 32+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Phase 18: Kreisrangliste + UAT-Automatisierung (5.–7. Mai 2026)
Eine neue Veranstaltungsform kommt hinzu — und UAT wird systematisch.
PROJ-85 formal deployt
Das in Phase 17 gebaute "Passwort vergessen?" für Admins ging nach QA und Deploy offiziell live. Kleines Feature, echter Mehrwert: Admins rufen nicht mehr an wenn sie ihr Passwort vergessen haben.
Neue Feature-Domain: Kreisrangliste (PROJ-86–89)
Nach Race kommt Kreisrangliste — ein vollständig anderes Veranstaltungsformat. Kreisranglisten sind regelmäßige Verbandsturniere (nicht Einzel-Events) mit festen Regeln für den KTTV Pinneberg:
- D/H-Rangliste: 1 Qualifikationsturnier + 3 Kreiswertungsranglisten (KWR), Ebenen A–E, Auf-/Abstieg nach je Runde, TTR-relevant
- Senioren-KRTL: 2–3 Turniere, ab 40 Jahre, D+H gemeinsam, nach LivePZ-Stichtag sortiert, NICHT TTR-relevant
Für beide Systeme wurden Feature-Specs geschrieben, ein neuer Skill /tt-ranglistenwart mit dem vollständigen Regelwerk angelegt, und PROJ-86 (Grundstruktur) komplett deployed:
- Neue Entität
kreisranglisten— komplett unabhängig von Turnieren - 9 API-Routen: CRUD + Teilnehmer-Import (CSV + Manuell) + Senioren-Sonderfelder
- UI: Anlegen-Dialog (D/H vs. Senioren, dynamischer LivePZ-Stichtag), Teilnehmerliste, Status-Stepper
PostgreSQL 15-Überraschung: Die Migration enthielt ADD CONSTRAINT IF NOT EXISTS — eine Syntax die in PostgreSQL 14 funktioniert, in PostgreSQL 15 (aktueller Supabase self-hosted Stand) aber nicht existiert. Ansible markierte die Migration als "applied" obwohl SQL-Fehler auftraten (fehlender ON_ERROR_STOP). Der Fehler fiel erst beim UAT auf. Fix: DO-Block mit pg_constraint-Check. Dieses Muster gilt jetzt als Standard für alle künftigen Migrations mit Constraints.
/uat — neuer strukturierter UAT-Skill
Bisher war UAT ein manueller Prozess mit Playwright-Tests die ad-hoc entstanden. Der neue /uat-Skill macht daraus einen vollständigen Zyklus:
- Feature-Spec einlesen → User Stories extrahieren
- UAT-Plan als Tabelle präsentieren → User bestätigt Scope
- Playwright-Tests in
tests/uat/features/proj-XX/schreiben - Story für Story durchführen — nach Bestätigung
- UAT-Report als
UAT/PROJ-XX/UAT-PROJ-XX-YYYY-MM-DD.md+ PDF speichern - Commit auf
develop
In dieser Session wurden drei UATs komplett durchgeführt:
PROJ-77 (Promotion-Codes): 11 PASS, 1 SKIP. Die Tests schlugen zunächst aufgrund falscher Annahmen über API-Feldnamen fehl: body.code.type statt body.type, stats.consumed statt stats.redeemed_total, und Fehlertext auf Deutsch statt Englisch. Nach Korrekturen: 11 PASS.
PROJ-80 (MKTT Cup-System): 6/6 PASS. Besonderer Test: US-2 "Systemwechsel auf Swiss ab Runde 5" wurde vollständig automatisiert — die Tests legen ein Race an, spielen 4 Runden durch und verifizieren den Mode-Wechsel per API. Entdeckt: race_matches hat sowohl round_number als auch round_id — Filterung muss per round_id (UUID) erfolgen, nicht per round_number.
PROJ-86 (Kreisrangliste Grundstruktur): 7/7 PASS nach Migration-Fix und PostgREST Schema-Reload.
Erkenntnis 22: UAT deckt andere Bugs auf als QA. Der PostgreSQL 15-Constraint-Bug und der falsche PostgREST-Schema-Cache (kannte die neue kreisranglisten-Tabelle nicht) wären beim Code-Review nie aufgefallen. Nur der echte Deploy gegen Staging hat sie aufgedeckt. UAT ist nicht Redundanz zu QA — es ist die letzte Verteidigungslinie vor PROD.
Erkenntnis 23: API-Feldnamen-Divergenz zwischen Spec und Implementierung ist ein Muster. In drei Projekten (PROJ-77, PROJ-80) gab es Abweichungen zwischen angenommener und tatsächlicher API-Response-Struktur. Lösung: Playwright-Tests lesen die API-Routen direkt (TypeScript), bevor sie Assertions schreiben — nicht aus der Spec.
Zahlen (Stand 7. Mai 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 7. Mai 2026 (47 Tage) |
| Commits | ~787 |
| Features | 89 (PROJ-1 bis PROJ-89) |
| Features deployed | 86 (PROJ-1–86 + PROJ-83 + PROJ-84 + PROJ-85) |
| Features geplant | 3 (PROJ-87 Check-in, PROJ-88 Spielbetrieb, PROJ-89 Export) |
| Features zurückgestellt | 2 (PROJ-81 TTLive-Import, PROJ-82 Seriewertung) |
| Bugfixes | 340+ |
| Unit-Tests | 38 (Vitest, Race-Algorithmen) |
| UAT-Tests | 60+ (35 Regression + 25 neue Feature-UAT-Tests PROJ-77/80/86) |
| UAT-Reports | 3 (alle GO: PROJ-77, PROJ-80, PROJ-86) |
| API-Routen | 130+ |
| DB-Tabellen | 34+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Phase 19: Kreisrangliste vollständig + Plan-Gate + Spielbetrieb-Erweiterungen (8. Mai 2026)
Die intensivste Einzel-Session seit der Race-Algorithmus-Phase: Fünf Features in einem Tag von Spec bis UAT.
PROJ-87: Check-in & TTR-Gruppeneinteilung
Der zweite Schritt der Kreisranglisten-Kette. Am Tag des Turniers:
- Check-in-Oberfläche: Anwesenheit per Klick markieren, Live-Zähler oben
- TTR-Blöcke: Spieler werden automatisch nach TTR in Gruppen eingeteilt — stärkste zuerst, danach nach LivePZ (Senioren) oder TTR (D/H)
- Drag & Drop: Manuelle Korrekturen per Drag zwischen Gruppen. Admin kann jeden Spieler verschieben ohne die automatische Setzung zu verlieren
- Gruppen freigeben: Ein Button setzt Status auf "gruppenspiele" — ab dann sind Matches berechnbar
9 UAT-Tests, alle GO.
PROJ-88: Spielbetrieb & Bericht
Das Herzstück des Kreisranglisten-Betriebs:
- Jeder-gegen-jeden-Paarungen: Alle Matches einer Gruppe werden automatisch generiert
- Ergebnis-Eintrag: Admin trägt Satz- und Ballergebnisse ein. Zwei Wertungssysteme parallel: D/H (WO-TTVSH-Standard) und Senioren-SKRL (Plus-Punkte → Siege/Niederlagen → Satzdifferenz → Balldifferenz → Direktvergleich)
- Druckbericht: Vollständiger Aushang als druckbares PDF — Gruppe, Kreuztabelle, Platzierungen, Auf-/Abstieg
- Aufgabe & Nichtantreten: Entschuldigte Fehlende werden als erste Absteiger markiert, unentschuldigte können für die Saison gesperrt werden
Qualitätssicherung: 8 Bugs behoben (Begründungsfeld, ex-aequo-Logik, Aushang-Übersicht, Senioren-Tiebreaker). 9 UAT-Tests, alle GO.
PROJ-89: Export — Drei Dateiformate
Die Kreisranglisten-Ergebnisse müssen an externe Systeme weitergegeben werden:
- TournamentPortal-XML (liga.nu): Das offizielle DTD-konforme Format für den Verband-Upload. Vollständige
<person>-Tags mit licence-nr, sex, restricted-data-processing.retired-a/retired-bfür Aufgaben. - .ttlive-Format: Für TTLive-Ergebniserfassung. Das kritische Detail: Satz-Differenzen werden immer aus der Perspektive des linken Spielers berechnet — nicht des Gewinners.
- WYSIWYG-CSV-Generator: Ein vollständiger Konfigurator mit Drag & Drop, Spaltenwahl, Trennzeichen-Einstellung und Vorschau. Konfigurationen werden per localStorage-Profil gespeichert. BOM für Excel.
3 QA-Bugs nachgebessert (Gestrichen-Matches in .ttlive, XML <matches>-Block immer ausgeben, CSV-Feld für Balls). 9 UAT-Tests, alle GO.
PROJ-90: Plan-Gate — Kreisrangliste als Bezahl-Feature
Ab sofort ist die Kreisranglisten-Funktion auf Club- und Verband-Tarif beschränkt:
kreisrangliste: booleaninplans.ts— Free und Starter:false, Club und Verband:true- Die Pricing-Seite zeigt "Kreisrangliste (KRTL/SKRL)" in der Vergleichstabelle und in den Club/Verband-Plan-Karten
- Superadmins umgehen das Gate immer (kein Plan-Check)
- Nicht grandfatherbar, nicht per Promo-Code freischaltbar, kein Add-on — bewusste Entscheidung: nur als Teil des Club-Plans
- 4 Karussell-Screenshots (uebersicht, spielbetrieb, bericht, export) auf Landing-Page integriert
4 UAT-Tests, alle GO.
PROJ-91: Spielbetrieb-Erweiterungen (Phase 1)
Fünf neue Funktionen als Erweiterung des Kreisranglisten-Spielbetriebs:
- Kurzschreib-Notation:
+7 → 11:7,-6 → 6:11,+14 → 16:14(Deuce). Eingabe im Ergebnis-Dialog, onBlur-Expansion - PIN-Toggle: Neue Option beim Anlegen einer Kreisrangliste — "Spieler-PIN-Bestätigung" ein/aus
- Spectator-Seite:
/kreisrangliste/[id]/spectator— öffentlich, kein Login, 15-Sekunden-Auto-Refresh. Zeigt alle Gruppen mit aktueller Rangliste - Referee-Leave:
DELETE /api/table/[tableId]/referee-leave— Schiri schaltet sich aus einem laufenden Match heraus, ohne es zu unterbrechen - Live-Übersicht im Spielbetrieb-Tab: Fortschrittsbalken pro Gruppe (grün=fertig, blau=läuft, grau=nicht gestartet), Zuschauer-Ansicht-Button, geschätzte Restzeit
8 UAT-Tests, alle GO.
Erkenntnis 24: UAT auf veraltetes Staging ist sinnlos. PROJ-90 und PROJ-91 wurden direkt nach PROD (main) deployt — staging war nie aktualisiert worden. UAT lief zunächst gegen ein Staging das diese Features nicht kannte. Fix: develop → staging mergen + Ansible-Migration laufen lassen, dann UAT. Dieses Muster (deploy to PROD → merge to staging → UAT on staging) muss zum Standard werden.
Erkenntnis 25: Pricing-Seiten-Regex /500|Internal Server Error/i ist falsch. maxPhotos: 500 im Club-Plan erscheint als Text "500" in der Vergleichstabelle. Die Standard-Fehler-Assertion muss auf der /pricing-Seite zu "Internal Server Error" (nur String, ohne Zahl) geändert werden.
Zahlen (Stand 8. Mai 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 8. Mai 2026 (48 Tage) |
| Commits | ~817 |
| Features | 91 (PROJ-1 bis PROJ-91) |
| Features deployed | 91 |
| Features zurückgestellt | 2 (PROJ-81 TTLive-Import, PROJ-82 Seriewertung) |
| Bugfixes | 350+ |
| Unit-Tests | 38 (Vitest, Race-Algorithmen) |
| UAT-Tests | 100+ |
| UAT-Reports | 8 (alle GO: PROJ-77, PROJ-80, PROJ-86–91) |
| API-Routen | 140+ |
| DB-Tabellen | 36+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Phase 20: Race-Turnierserie komplett + Kreisrangliste Phase 2 (10. Mai 2026)
In dieser Session wurden die lange zurückgestellten Race-Features fertiggestellt — von der Datenbeschaffung bis zur Serienwertung — und die Kreisrangliste um drei sofort sichtbare UI-Ergänzungen erweitert.
PROJ-81: Race Spielerdaten-Import — vollständig deployed
Drei Pakete in einer Session:
Paket 1: Export-Formate — TournamentPortal-XML-Export (für click-TT TTR-Aktualisierung) und .ttlive-Export (für TTLive LivePZ-Aktualisierung). Das .ttlive-Format exportiert Satz-Differenzen aus Sicht des linken Spielers (A-Perspektive) — nicht des Gewinners. Verifiziert anhand der Testturnier-Quelldatei.
Paket 2: Import-Formate — CSV-Import im
spieler_import.csv-Format (Semikolon-getrennt, 7 Spalten), TournamentPortal-XML-Import mit Multi-Competition-Auswahl, Duplikat-Erkennung (Nachname+Vorname+Verein), Batch-Insert in 50er-Blöcken.Paket 3: TTLive-Scraping — Server-seitiges HTML-Scraping der öffentlichen TTLive-Vereinsseiten.
ttlive_clubs-Tabelle mit 50+ TTVSH-Vereinen vorbelegt (VID-basiert). Rate-Limit 1 Request/Sekunde. DSGVO-Banner im Import-Dialog. Kritischer Bug entdeckt:enforceRateLimit()war nichtasync— dasnew Promise()wurde erstellt aber nie awaited. Das Rate-Limit gab also gar nicht. Fix:async/awaitergänzt.
UAT: 8/8 PASS.
PROJ-82: Race Seriewertung & Finalqualifikation — deployed + ergänzt
Die saisonübergreifende Wertung für Race-Turnierserien:
- Serienkonfiguration: Saison, Verband, Punkteschema als JSON (
{"1": 13, "2": 10, ...}), Qualifikationsschwelle, Teilnahme-Bonus, Erststart-Sonderpunkte - Finalrace-Logik: Races können als Finalrace markiert werden → Multiplier 2.0 (Doppelpunkte, TTVSH-Regelwerk)
- Saisonrangliste: Öffentlich zugänglich ohne Login, Auto-Refresh 60s, Qualifikations-Badge für Top-N
- CSV-Export:
?format=csvan den Standings-Endpoint — BOM-prefixed UTF-8, Semikolon, direkt Excel-kompatibel - Historien-Import: Vergangene Turnierergebnisse können als
.ttlive(Sieg-Rekonstruktion aus Matchdaten) oder TournamentPortal-XML (direkt ausplacement-Attribut) importiert werden, mit Vorschau der berechneten Punkte vor Übernahme
UAT: 8/8 PASS.
PROJ-92 Paket 1: Kreisrangliste Spielbetrieb-Ergänzungen
Drei UI-Ergänzungen ohne Migration:
- PIN-Toggle-UI: Switch im Admin-Header der Kreisranglisten-Detailseite. Optimistisches Update mit Rollback bei Fehler, 2-Sekunden-Häkchen bei Erfolg. Das DB-Feld
pin_bestaetigungwar seit PROJ-91 vorhanden — nur der Toggle fehlte. - Tisch-Status-Panel: Badge-Grid nach dem Gruppenfortschrittsblock. Da
kreisrangliste_matcheskeine eigene Tischnummer-Spalte hat, wird Gruppenummer als Tisch-Proxy genutzt — Grün (fertig), Gelb (laufend), Rot (noch nicht begonnen). - Rote-Karte-Rückgängig: 2-Minuten-Countdown-Banner erscheint wenn ein Match auf
retirement_reason: "rote_karte"gesetzt wird. "Rückgängig"-Button resettet das Match aufoffen. Match-Reset-API-Erweiterung:PATCHmitstatus: "offen"löscht alle Ergebnisfelder vor der Schema-Validierung.
Erkenntnis 26: Viel war schon in PROJ-91 gebaut. Die Analyse vor PROJ-92 zeigte: Kurzschreib-Notation, Fortschrittsbalken mit Restzeit-Schätzung und Live-Übersicht waren bereits vollständig deployed. Was wirklich fehlte, war deutlich kleiner als erwartet. Zuerst coden, dann codieren — aber zuerst den bestehenden Code lesen.
Erkenntnis 27: UAT ohne Global Setup als eigenes Muster etabliert. Da POST /api/admin/tenants intern die MiaB-Alias-API aufruft (PROJ-64), hängt der Playwright-Global-Setup 30 Sekunden wenn MiaB auf staging nicht erreichbar ist. Lösung: playwright.config.proj81.ts — eigene Config ohne Global Setup, Tests loggen sich direkt als Superadmin ein. Für alle Features die nur API-Tests brauchen, ist das jetzt das bevorzugte Muster.
Zahlen (Stand 10. Mai 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 10. Mai 2026 (50 Tage) |
| Commits | ~832 |
| Features | 93 (PROJ-1 bis PROJ-92 Paket 1) |
| Features deployed | 93 |
| Bugfixes | 360+ |
| Unit-Tests | 38 (Vitest, Race-Algorithmen) |
| UAT-Tests | 116+ |
| UAT-Reports | 10 (alle GO: PROJ-77, PROJ-80, PROJ-81, PROJ-82, PROJ-86–92) |
| API-Routen | 150+ |
| DB-Tabellen | 39+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Phase 21: KRL Tablet + DB-Audit (11.–12. Mai 2026)
Eine kompakte aber aufschlussreiche Session: ein neues Feature, ein vollständiges UAT und eine unerwartete Infrastruktur-Entdeckung.
PROJ-92 Paket 2: KRL Tablet-Ergebnis-Eingabe
Der fehlende Baustein für den echten Spielbetrieb an der Kreisrangliste:
- Öffentlicher Tablet-Endpoint:
GET + PATCH /api/.../tablet— keine Admin-Session nötig. Tablets im Venue können Ergebnisse direkt speichern.winner_idwird serverseitig aus den Ballergebnissen berechnet — nie vom Client übernommen. - Tablet-Seite:
/kreisrangliste/[id]/match/[matchId]/tablet?group=[groupId]— dunkles Layout, Satz-für-Satz-Eingabe, Kurzschreib-Notation (+7 → 11:7), "Ergebnis speichern"-Button. - Tisch-Zuweisung:
PATCH /api/.../table-assign— Admin weist einem KRL-Match einen physischen Tisch aus dem Venue-Pool zu. - 📱-Button im Spielbetrieb-Tab: Direktlink zur Tablet-Seite pro Match — öffnet in neuem Tab.
UAT: 8/8 PASS (P92-1 bis P92-8, inkl. öffentlicher Zugriff und Shorthand-Expansion).
Unerwartete Entdeckung: DB-Schema-Audit
Auf Wunsch wurde ein vollständiger Schema-Vergleich aller 3 Umgebungen durchgeführt — und deckte vier historische Lücken auf:
| Lücke | Betroffen |
|---|---|
tournament_photos.tenant_id fehlte |
DEV |
log_settings.updated_by fehlte |
STAGING + PROD |
app_logs.metadata nullable statt NOT NULL |
STAGING + PROD |
FK-Constraints auf app_logs/log_settings fehlten |
STAGING + PROD |
Dazu kam die PROJ-92-Migration selbst: Sie referenzierte venue_tables, aber die Tabelle heißt im Projekt tables. Die Migration schlug auf allen 3 Envs lautlos fehl — Ansible markierte sie trotzdem als "applied". Die venue_table_id-Spalte fehlte überall.
Alle Lücken wurden direkt per SSH + psql behoben, die Migration korrigiert, PostgREST auf allen Servern neu geladen.
Erkenntnis 28: Ansible markiert Migrations als "applied" auch wenn SQL-Fehler auftreten. Es gibt kein ON_ERROR_STOP. Eine ADD COLUMN ... REFERENCES nicht_existierende_tabelle(id) schlägt lautlos fehl — die Spalte fehlt, das Tracking sagt "erledigt". Konsequenz: Nach Migrations die neue Spalten mit FK-Constraints hinzufügen immer manuell auf allen Envs prüfen: SELECT column_name FROM information_schema.columns WHERE table_name='...'. Nie dem Ansible-Output vertrauen.
Erkenntnis 29: Der Staging-Branch muss nach jedem PROD-Deploy mitgezogen werden. Staging war über mehrere Sprints von develop/main abgekoppelt — UAT lief gegen veralteten Code. Fix: nach jedem git push origin main auch develop → staging mergen. Kleiner Schritt, verhindert stundenlange Debugging-Schleifen bei UAT.
Erkenntnis 30: Systematischer DB-Schema-Audit lohnt sich regelmäßig. Nach 90+ Migrations über 50 Tage hatten sich kleine Divergenzen zwischen DEV, STAGING und PROD angesammelt — keine davon war sofort sichtbar, aber zusammen ein latentes Risiko. Ein einmaliger vollständiger Spalten-/FK-Vergleich via information_schema.columns hat alle Lücken aufgedeckt und geschlossen.
Phase 22: TTR-Projektion — DTTB-Formel im System (12. Mai 2026)
PROJ-93 wurde vollständig in einer Session implementiert — von der Datenbankspezifikation bis zum Deploy auf allen 3 Umgebungen. 10 Commits, 14 neue Files/Changes, subagent-gesteuerte Ausführung mit zwei Review-Runden pro Task.
Was gebaut wurde
Zentrale Berechnungsbibliothek: src/lib/ttr.ts — vier reine Funktionen nach DTTB-Standard:
calculateExpected(ttrA, ttrB)→ Erwartetes Ergebnis (0–1) nach Elo-FormelcalculateDelta(ttrA, ttrB, won, kFaktor?)→ ΔTTR als ganze Zahl (K=16 Standard)calculateRunningTotal(matches[])→ kumulierter TagessaldocomputeMatchDelta(...)→ Wrapper mit NULL-Guards (kein TTR, Aufgabe, nicht-relevant)
14 Vitest-Tests decken alle Grenzfälle ab (gleicher TTR, starker Außenseiter, K-Faktor-Skalierung, NULL-Handling).
Drei Backend-Hooks: Match-Ergebnis gespeichert → ΔTTR automatisch berechnet:
- Kreisranglisten (D/H immer, Senioren nie per SKRL §13.1.1)
- Turniere (wenn
ttr_relevant=true) - Streichungen/Reset: ΔTTR wird auf NULL zurückgesetzt
Zwei Frontend-Komponenten:
- TtrTooltip — ℹ-Icon in Match-Zeilen. Mouseover zeigt: Spieler A (TTR): +X → neuer TTR, Spieler B: -X → neuer TTR. Hinweis: "⚠ Projektion — gilt nach DTTB-Berechnungslauf".
- Tagesbilanz-Tabelle im Spieler-Report (
/player/report/[id]): Spiel-für-Spiel ΔTTR + kumulierter Saldo, prominenter Disclaimer-Banner.
Turnier-Einstellungen: Toggle "TTR-relevant" (Default: ein) + K-Faktor-Auswahl (16/24/32) im Anlegen-Formular.
Security-Fix im Review entdeckt
Der automatische Code-Review nach Task 8 fand einen echten Bug (Confidence 85%): Der winner_id-Wert für die ΔTTR-Berechnung wurde aus dem Client-Request (updatePayload) übernommen statt aus dem server-seitig gespeicherten DB-Wert gelesen. Ein Admin hätte durch Falschangabe des Gewinners das Delta manipulieren können. Fix: Nach dem DB-Update den winner_id neu aus der DB lesen.
Erkenntnis 31: Subagent-gesteuerte Entwicklung + obligatorischer Code-Review nach Abschluss ist die richtige Kombination. Die 8 Task-Subagenten haben sauber implementiert — aber erst der abschließende Review-Agent hat den Security-Bug gefunden, der in keinem der Einzeltasks aufgefallen wäre.
Erkenntnis 32: Subagent-Driven Development mit Plan skaliert gut für mittlere Features. 9 Tasks, 9 Subagenten, 10 Commits — kein einziger TypeScript-Fehler im Build, alle 14 Tests grün, alle 3 Envs nach Migration konsistent. Die Qualität der Vorbereitung (Plan mit vollständigem Code) bestimmt den Erfolg der Ausführung.
Zahlen (Stand 12. Mai 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 12. Mai 2026 (52 Tage) |
| Commits | ~850 |
| Features | 94 (PROJ-1 bis PROJ-94 spec'd, PROJ-1–93 deployed) |
| Features deployed | 93 |
| Bugfixes | 370+ |
| Unit-Tests | 52 (38 Race-Algorithmen + 14 TTR-Projektion) |
| UAT-Tests | 124+ |
| UAT-Reports | 11 (alle GO) |
| API-Routen | 158+ |
| DB-Tabellen | 60 |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) — alle schema-konsistent |
Phase 23: Öffentliche Vereinsseite & Online-Voranmeldung (13.–14. Mai 2026)
PROJ-94 bringt TT-Control nach außen: Zum ersten Mal können Spieler ein Turnier finden und sich anmelden, ohne Admin zu sein.
Was gebaut wurde
Öffentliche Vereinsseite unter /verein/[slug] — kein Login nötig. Zeigt alle Turniere mit public_visible=true als Karten. Turnierkarten zeigen ob die Anmeldung noch offen ist.
Online-Anmeldeformular unter /verein/[slug]/turniere/[id]/anmelden — DSGVO-Pflichtfeld inklusive. Spieler geben Name, Verein, Geburtsjahr, TTR und (wenn TTR-relevant) Lizenznummer ein. Validierung mit Zod + react-hook-form.
Vier neue Admin-Felder im Turnier-Setup:
public_visible(Toggle: Turnier auf Vereinsseite zeigen)auto_confirm_registration(Direkt bestätigen oder Warteliste)registration_deadline(Meldeschluss, Standard: Turnierdatum)
Registrierungs-Tab im Admin: Liste aller Online-Anmeldungen mit Status (ausstehend/bestätigt/abgelehnt), Bestätigen- und Ablehnen-Buttons, Ablehnungs-Begründung per Dialog.
Automatische E-Mails: Bei Anmeldung → "Anmeldung eingegangen"-Mail. Bei Bestätigung → Bestätigungsmail mit QR-Code (generiert via qrcode-Paket). Bei Ablehnung → Ablehnungsmail mit Begründung. Alle drei nutzen das PROJ-71 E-Mail-Template-System.
Lizenz-Ampel für Spieler mit licence_nr im Player-Portal: Gelb (Lizenz hinterlegt, nicht verifiziert), Grün (manuell verifiziert durch Admin). Prüfung gegen DTTB-Lizenzformat.
Technische Besonderheit: Ghost-Player-Anlage
Online-angemeldete Spieler werden als "Ghost Player" angelegt — vollständiger DB-Eintrag in der players-Tabelle, aber ohne echten Portal-Account. Zwei Pflichtfelder pin_hash und pin_digest (beide NOT NULL ohne Default) mussten als leerer String initialisiert werden. Dieser Bug wurde erst durch einen 500-Fehler beim UAT sichtbar — 1 Fix nach 9 PASS.
UAT: 10/10 PASS (nach 1 Bugfix).
Erkenntnis 33: Ghost-Player-Konzept ist wartbar, aber bewusst. Spieler die sich online anmelden, haben kein Portal-Passwort. Sie erhalten stattdessen einen QR-Code per E-Mail. Wenn sie sich später ein Konto anlegen wollen, müssen Admin und Spieler den bestehenden Datensatz verknüpfen — das ist PROJ-65 (QR-Selbstregistrierung), das genau dafür gebaut wurde.
Phase 24: Tablet Spielermodus (15. Mai 2026)
PROJ-95 löst ein praktisches Problem: Wenn zwei Spieler ohne Schiedsrichter spielen, ist die Ball-für-Ball-Eingabe unpraktisch. Die neue Spieleransicht erlaubt die Eingabe von Satzergebnissen am Ende jedes Satzes — kompakt mit +N/-N Notation.
Was gebaut wurde
Modus-Auswahl beim Start: Beim ersten Öffnen eines Tablets erscheint ein Auswahlscreen mit zwei großen touch-freundlichen Karten:
- Schiedsrichteransicht — bestehende Ball-für-Ball-Eingabe (unverändert)
- Spieleransicht — neue Satzeingabe-Ansicht
Die Wahl wird in localStorage gespeichert (tablet_mode_${tableId}) und beim nächsten Öffnen automatisch wiederhergestellt. Ein "Modus wechseln"-Button oben rechts setzt die Wahl zurück.
Spieleransicht: Zeigt Spielernamen, aktuellen Satzstand (groß, zentriert), Satzverlauf (11:7 | 8:11 | …) und ein Texteingabefeld für das nächste Satzergebnis.
+N/-N Notation:
+7→ Spieler A gewinnt 11:7 (N ≤ 9: Gewinner = 11)+10→ Spieler A gewinnt 12:10 (N ≥ 10: Deuce-Automatik = N+2)-9→ Spieler A verliert 9:11- Ungültige Eingaben → sofortige Fehlermeldung
Utility-Bibliothek src/lib/parse-set-input.ts mit 16 Vitest-Tests. Reine Funktionen ohne Datenbankzugriff — parseSetInput, setWinner, countSets, setsNeeded, isMatchComplete.
API-Erweiterung: ScoreboardAction um optionale Felder scoreP1Override / scoreP2Override erweitert. Im Spielermodus sind state.pointsP1/P2 immer 0 (kein Ball-für-Ball-Tracking) — daher braucht buildPatchBody die direkten Score-Werte aus der Eingabe.
UAT: 6/6 PASS.
Erkenntnis 34: localStorage-Modus ist gerätespezifisch — das ist korrekt. Tablets haben im Vereinsbetrieb eine feste Zuordnung zu Tischen. Der Modus soll nicht bei jedem Öffnen neu gewählt werden. Die Persistenz per localStorage ist bewusst gerätebezogen, nicht session-bezogen.
Erkenntnis 35: Playwright-Env-Variablen funktionieren im Bash-Tool inline. Die technische Entscheidung "UAT nur via PowerShell" war für WSL-Aufrufe (wsl -d Ubuntu -e bash -c "..."). Im Bash-Tool direkt (STAGING_URL=... npx playwright test) funktioniert das ohne PowerShell-Umweg.
Zahlen (Stand 15. Mai 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 15. Mai 2026 (55 Tage) |
| Commits | ~876 |
| Features | 95 (PROJ-1 bis PROJ-95) |
| Features deployed | 95 |
| Bugfixes | 380+ |
| Unit-Tests | 68 (38 Race + 14 TTR + 16 parseSetInput) |
| UAT-Tests | 136+ |
| UAT-Reports | 13 (alle GO: PROJ-77, PROJ-80–82, PROJ-86–95) |
| API-Routen | 165+ |
| DB-Tabellen | 60+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Phase 25: Tischpool cross-Veranstaltung (16. Mai 2026)
PROJ-96 schließt eine lange offene Lücke: Kreisranglisten und Races liefen bisher neben dem Tischpool — ab heute teilen alle Veranstaltungstypen dieselben Tische.
Was gebaut wurde
Datenbank: Zwei neue Felder — race_matches.table_id (FK auf tables) und require_table_assignment (Boolean, default false) auf tournaments, kreisranglisten und races. Eine einzige idempotente Migration für alle drei Umgebungen.
Tablet-API erweitert: GET /api/table/[tableId] sucht jetzt in drei Quellen:
matches(Turnier-Matches, bestehend)kreisrangliste_matchesviavenue_table_id(neu)race_matchesviatable_id(neu)
Ein einziger QR-Code — ein Tablet — alle Veranstaltungstypen. Priorisierung: in_progress vor spielbereit.
Pool-Dashboard: Die bestehende Venue-Pool-Übersicht zeigt jetzt event_type-Badges pro belegtem Tisch: 🏆 Turnier, 📊 KRL, 🏁 Race. Farbkodierung nach Veranstaltungstyp.
Tisch-Spalten in KRL Spielbetrieb-Tab und Race Runden-Tab: Neuer PoolTablePicker (wiederverwendbare Dropdown-Komponente, grün=frei, rot=belegt) pro Match-Zeile. Admin ordnet Matches direkt aus der Spielbetriebsansicht Tischen zu.
Tischzwang: Backend-Validation verhindert Statustransitionen ohne zugeordneten Tisch wenn require_table_assignment = true. HTTP 422 mit Code TABLE_REQUIRED. Das Feld ist im kreisranglisteUpdateSchema ergänzt — UAT P96-5 war initial ein SKIP weil das Feld fehlte, wurde aber im selben Sprint nachgezogen.
UAT: Initial 3/5 PASS (2 SKIPs wegen fehlender Schema-Freigabe und fehlendem Venue-Pool-Setup). Nach zwei gezielten Fixes im selben Sprint: 5/5 PASS.
Erkenntnis 36: KRL und Race brauchen unterschiedliche Spalten
KRL-Matches nutzen venue_table_id (legacy aus PROJ-92 — ein Verweis auf Pool-Tische), Race-Matches nutzen das neue table_id (direkt auf tables). Die Tablet-API handhabt beide Quellen in separaten Queries. Das ist keine elegante Einheitslösung — aber es respektiert die historische Entwicklung ohne Breaking Change.
Erkenntnis 37: UAT-SKIPs sind keine Bugs — aber sie zeigen Implementierungslücken
P96-3 (Venue-Pool-Badge) war ein SKIP weil der Test-Setup keinen expliziten Venue-Pool anlegt — das ist ein Test-Design-Problem, kein Feature-Bug. P96-5 (Tischzwang) war ein SKIP weil require_table_assignment im KRL-PATCH-Schema fehlte — das ist ein echter Implementierungslücken. Ein gezieltes "warum ist das SKIP?" führte direkt zum Fix.
Zahlen (Stand 16. Mai 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 16. Mai 2026 (56 Tage) |
| Commits | ~887 |
| Features | 96 (PROJ-1 bis PROJ-96) |
| Features deployed | 96 |
| Bugfixes | 390+ |
| Unit-Tests | 68 (38 Race + 14 TTR + 16 parseSetInput) |
| UAT-Tests | 141+ |
| UAT-Reports | 14 (alle GO: PROJ-77, PROJ-80–82, PROJ-86–96) |
| API-Routen | 170+ |
| DB-Tabellen | 60+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Phase 26: AI-Browser-UAT + Bug-Tracking-Session (18. Mai 2026)
Eine Session die zwei Richtungen kombiniert: neues Testing-Werkzeug aufbauen und echte Bugs finden und fixen.
PROJ-97: Browser-Use AI-UAT — vollständige Python-Pipeline
Das bestehende Playwright-UAT-System deckt ~80% der User Stories ab. Die restlichen 20% scheitern an Playwright-Grenzen: Drag & Drop, kontextabhängige UI-Elemente, visuelle Verifikation. Die Lösung: browser-use — ein Python-Agent der natürlichsprachliche Aufgaben in Browseraktionen übersetzt.
Was gebaut wurde:
Eine vollständige Python-Pipeline in tests/uat/browser-use/ (7 Tasks, TDD):
annotate.py— Pillow-basiertes Annotation-Modul: Step-Badge + Beschriftungsleiste auf Screenshotstask_loader.py— YAML-Frontmatter-Parser für Markdown-Aufgabendateiengenerate_story_report.py+templates/story_report.html— Jinja2-HTML-Report mit base64-eingebetteten Screenshots, Traceability-Tabelle, AI-Einschätzung und GO/NO-GO-Badgerunner.py— Hauptorchestrator: lädt Task → startet Agent → annotiert Screenshots → generiert Report- 4 Task-Dateien für bisher übersprungene UAT-Tests (P96-3, P96-5, PROJ-77-US7, PROJ-86-US3)
Erster Testlauf: Der Agent loggte sich erfolgreich auf Staging ein, navigierte durch Kreisranglisten, machte 20 annotierte Screenshots und speicherte einen HTML-Report in UAT/PROJ-96/. Der Report zeigt den vollständigen Ablauf aus Nutzerperspektive — lesbar für nicht-technische Stakeholder.
Erkenntnis 38: Versionswechsel bei Python-Bibliotheken brechen die API komplett. browser-use v0.12 hatte eine vollständig neue API — alle Code-Beispiele für v0.1.x waren ungültig. from browser_use import Browser, BrowserConfig existiert nicht mehr. Stattdessen: from browser_use.agent.service import Agent + from browser_use.llm import ChatAnthropic. Auch langchain_anthropic.ChatAnthropic ist nicht mehr kompatibel — browser-use hat eigene LLM-Klassen. Und: Claude Haiku aktiviert Extended Thinking, was Pydantic-Validierungsfehler im Agent auslöst. Fix: use_thinking=False im Agent-Init.
Erkenntnis 39: Python 3.13 auf Windows kennt keine CA-Zertifikate. Alle HTTPS-Calls zu externen APIs (Anthropic etc.) scheitern mit SSL: CERTIFICATE_VERIFY_FAILED. Fix: certifi-Zertifikate als Umgebungsvariablen setzen — als erstes im Script, vor allen HTTP-Imports. Einmaliger Fix, aber tückisch weil die Fehlermeldung nach einem Network-Timeout statt einem SSL-Problem aussieht.
Bug-Tracking: 6 neue Specs aus User-Review (PROJ-98–103)
Eine systematische Review-Session identifizierte 6 Probleme aus dem echten Betrieb:
| ID | Typ | Thema |
|---|---|---|
| PROJ-98 | Enhancement | KRL Check-in: Tab springt nach jedem Check-in zurück; Bulk-Checkin fehlt |
| PROJ-99 | Feature | Admin kann +N/-N Kurzeingabe nicht nutzen (nur Spieler) |
| PROJ-100 | Feature | TTR-Projektion in KRL-Spielen (D/H) noch nicht integriert |
| PROJ-101 | Bug | Zentrale Tischverwaltung zeigt keine KRL/Race-Matches |
| PROJ-102 | Feature | Spieltort-Autocomplete aus Veranstaltungshistorie |
| PROJ-103 | Feature | Löschen von Races & Ranglisten fehlt |
PROJ-101: Bug-Fix in zwei Zeilen — deployed in einer Session
Die Tischverwaltung (Pool-Dashboard) zeigte keine KRL-Matches. Nach vollständiger Code-Analyse: API und Frontend waren korrekt implementiert. Der Bug lag in einer falschen Filterbedingung — und sie kam an zwei Stellen vor:
- Anzeige-API (
/api/venue-pools/[poolId]/tables): filterte KRL-Matches aufstatus = "offen". Sobald ein Match einem Tisch zugewiesen wird und aufspielbereitwechselt, verschwand es aus der Ansicht. - Konflikt-Check (
/api/.../table-assign): dieselbe falsche Bedingung. Zwei KRL-Matches inspielbereitkonnten denselben Tisch belegen ohne 409-Fehler.
Fix: Je eine Zeile in zwei Dateien. .eq("status", "offen") → .in("status", ["offen", "spielbereit", "in_progress"]). Deployed auf DEV + PROD.
Erkenntnis 40: Occupancy-Filter und Konflikt-Check müssen denselben Status-Filter haben. Wenn ein Event-Typ einen Tisch "belegt", muss diese Definition konsistent in der Anzeige-API UND im Schutz-Mechanismus der Assign-Route gelten. Fehlt einer der beiden → entweder unsichtbarer Tisch oder stille Doppelbelegung.
Zahlen (Stand 18. Mai 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 18. Mai 2026 (58 Tage) |
| Commits | ~913 |
| Features | 103 (PROJ-1 bis PROJ-103 spec'd) |
| Features deployed | 97 (PROJ-1–96 + PROJ-101) |
| Features In Progress | 1 (PROJ-97 browser-use, Pipeline fertig) |
| Features geplant | 5 (PROJ-98–100, PROJ-102–103) |
| Bugfixes | 400+ |
| Unit-Tests | 68 (Race + TTR + parseSetInput) + 25 Python-Tests (browser-use Pipeline) |
| UAT-Tests | 141+ (Playwright) |
| UAT-Reports | 14 (alle GO) |
| API-Routen | 170+ |
| DB-Tabellen | 60+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Phase 27: QA-Audit + PROJ-101 UAT + PROJ-103 vollständig (19. Mai 2026)
Eine Session, die zwei Themen verbindet: konsequentes Nachfassen offener Qualitätsfragen und ein komplett abgeschlossenes Feature in einem Durchgang.
PROJ-101: UAT und nachträglicher QA-Fund
Der PROJ-101-Bug-Fix (Status-Filter in Pool-Dashboard) ging ohne UAT in PROD — das holten wir nach. 5/5 UAT-Tests PASS:
- Pool-API liefert
event_type:"krl"nach KRL-Tischzuweisung - Pool-API liefert
event_type:"race"nach Race-Tischzuweisung - Beidseitiger Konflikt-Schutz KRL↔Race funktioniert korrekt
- Tenant-Isolation (Cross-Tenant-DELETE → 404) sauber
Parallel entdeckte das QA-Code-Audit zwei Dinge die erst durch die Analyse sichtbar wurden:
Befund 1 (überraschend): Der PROJ-101-Fix (status IN ["offen","spielbereit","in_progress"]) war technisch ein No-Op. Das DB-CHECK-Constraint auf kreisrangliste_matches.status erlaubt nur offen|eingetragen|gestrichen — die Status-Werte spielbereit und in_progress existieren im Schema gar nicht. Der ursprüngliche Bug war vermutlich nicht der Filter, sondern fehlende Staging-Testdaten (kein Turnier + KRL in demselben Pool). Der Fix ist trotzdem sinnvoll als vorsorgende Härtung für PROJ-104 (KRL Match Status-Lifecycle) — aber er hat auf dem aktuellen Stand nichts bewirkt. Konsequenz: PROJ-104 (Schema-Erweiterung für spielbereit/in_progress) wurde als eigene Spec angelegt.
Befund 2 (BUG-101-B): Die Race-Route (/api/races/[id]/matches/[mid]) prüfte den KRL-Konflikt noch mit dem alten .eq("status","offen") — asymmetrisch zur KRL-Route die bereits den erweiterten Filter hatte. 1-Zeilen-Fix, deployed.
PROJ-103: Löschen von Races & Kreisranglisten — vollständiger Zyklus
Von Spec über Architecture bis UAT in einer Session.
Überraschung beim Architecture-Check: Beide DELETE-APIs (DELETE /api/kreisranglisten/[id] und DELETE /api/races/[id]) existierten bereits vollständig — mit Tenant-Check und CASCADE-Delete. PROJ-103 war von Anfang an ein reines Frontend-Feature.
Was gebaut wurde:
delete-event-dialog.tsx— generischer AlertDialog für KRL + Race (destructive Style, Serien-Hinweis, Lade-Spinner)- Trash-Icon auf jeder Card in
/admin/kreisranglistenund/admin/races— absolut positioniert,stopPropagationverhindert Navigation - Optimistisches Entfernen aus der Liste nach erfolgreichem DELETE + Toast-Feedback
QA fand zwei Bugs:
loadAuthorisedRacehatte keinen Superadmin-Bypass — hard-kodiertes.eq("tenant_id", tenantId)ohneisSuperAdmin-Guard. Die KRL-Schwesterroute hatte diesen Guard schon korrekt. Spec verlangte: "Superadmin kann jede Veranstaltung löschen". Fix:if (!isSuperAdmin(user)) query = query.eq("tenant_id", tenantId).race_series_idfehlte imRaceListItem-Interface → Serien-Hinweis wurde nie angezeigt. Fix: Interface ergänzt +isInSeries={race.race_series_id != null}im Dialog-Aufruf.
UAT: 4/4 PASS auf staging.
Erkenntnis 41: Superadmin-Bypass in Route-Hilfsfunktionen ist fehleranfällig durch Asymmetrie. Wenn mehrere Routes denselben Basis-Pattern haben (loadAuthorisedX), ist die Wahrscheinlichkeit hoch, dass eine den Superadmin-Guard nicht mitbekommt. Nach jedem QA-Audit: Schwesterouten auf denselben Guard prüfen — nicht nur die getestete Route. Das Pattern if (!isSuperAdmin(user)) query = query.eq("tenant_id", ...) ist der richtige Standard.
Erkenntnis 42: APIs zuerst bauen zahlt sich aus — manchmal überraschend schnell. PROJ-103 war nur Frontend weil die DELETE-APIs bereits aus früheren Sessions existierten. Die Implementierungszeit halbierte sich dadurch.
Zahlen (Stand 19. Mai 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 19. Mai 2026 (59 Tage) |
| Commits | ~923 |
| Features | 104 (PROJ-1 bis PROJ-104 spec'd) |
| Features deployed | 98 (PROJ-1–96 + PROJ-101 + PROJ-103) |
| Features In Progress | 1 (PROJ-97 browser-use) |
| Features geplant | 5 (PROJ-98–100, PROJ-102, PROJ-104) |
| Bugfixes | 410+ |
| Unit-Tests | 68 (Race + TTR + parseSetInput) + 25 Python-Tests (browser-use) |
| UAT-Tests | 150+ (Playwright) |
| UAT-Reports | 16 (alle GO: +PROJ-101, +PROJ-103) |
| API-Routen | 170+ |
| DB-Tabellen | 60+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Phase 28: KRL-Sprint — drei Features in einer Session (23. Mai 2026)
Eine der produktivsten Sessions seit der Race-Algorithmus-Phase: Drei Features von Spec bis UAT komplett abgeschlossen. Dazu ein wichtiger Infrastruktur-Fix und ein Lernmoment beim UAT-Prozess selbst.
PROJ-98: KRL Check-in UX — Tab-Persistenz + Bulk-Checkin
Der häufigste Schmerz im Turnierbetrieb: Nach jedem Check-in sprang die Seite zum ersten Tab zurück. Admins mussten bei 30 Spielern 30 Mal manuell zurück zum Check-in-Tab wechseln.
Was gebaut wurde:
- Tab-Persistenz:
localStoragemerkt sich welchen Tab der Admin zuletzt hatte. Seiten-Reload oder Navigation kehrt immer zum richtigen Tab zurück. - Bulk-Checkin: Checkboxen pro Spieler + "Alle auswählen" + Sammel-Button "X einchecken". Optimistisches UI mit Live-Fortschrittsbalken. Fehlertoast wenn einzelne Check-ins scheitern.
- Schnell und solide: 10/10 QA-Tests PASS ohne Bugs. UAT: 4/4 PASS.
code-review-graph MCP integriert
Das Projekt hat jetzt einen Abhängigkeitsgraphen: code-review-graph MCP verbindet sich mit dem Tree-sitter-Parser und erstellt einen strukturellen Graph des Codebasis. Bei Code-Reviews und Impact-Analysen werden jetzt zuerst Graphabfragen gemacht (detect_changes, get_impact_radius, query_graph) statt Grep/Glob — schneller, token-effizienter, und gibt Kontext den Datei-Scanning nicht liefern kann.
PROJ-104: KRL Match Status-Lifecycle — spielbereit/in_progress
Das fehlende Schema-Fundament aus PROJ-101 nachgeholt. KRL-Matches hatten bisher nur offen|eingetragen|gestrichen — dem Pool-Dashboard fehlten die Status-Übergänge die zeigen, welche Matches tatsächlich aktiv sind.
Was gebaut wurde:
- Zwei neue Status:
spielbereit(Tisch zugewiesen) undin_progress(Tablet hat Match geöffnet) - Tisch-Zuweisung:
PATCH .../table-assignsetzt Match automatisch aufspielbereit. Doppelbelegung desselben Tisches → 409 CONFLICT. - Tablet-GET triggert in_progress: Wenn das Tablet
GET .../tabletaufruft (Match wird auf dem Tablet geöffnet), wechselt der Status aufin_progress. Das Tablet weiß nichts von diesem Übergang — er passiert automatisch. - UAT: 6/6 PASS — inklusive aller Status-Übergänge und Konflikt-Checks.
PROJ-99: Admin +N/-N Kurzeingabe im ErgebnisDialog
Bisher konnten nur Spieler die +7 -9-Kurznotation nutzen (PROJ-95 Spielermodus). Admins mussten Satz-für-Satz in numerische Felder eingeben. PROJ-99 bringt die gleiche Notation in den Admin-Ergebnis-Dialog.
Was gebaut wurde:
- Kurzeingabe-Tab: Standardmäßig aktiv. Ein Texteingabefeld, freie Eingabe der gesamten Spielfolge:
+7 -9 +5 +11. - Live-Vorschau: Während der Eingabe zeigt ein Preview-Block die geparsten Satzergebnisse und den aktuellen Spielstand.
- Zwei-stufige Validierung: Syntax-Fehler (unbekannte Notation) sofort sichtbar beim Tippen. Vollständigkeitsfehler (Bo5 ohne 3 Gewinnsätze) erst beim Speichern.
- Modus-Wechsel: "Satz für Satz"-Tab übernimmt die eingetippten Werte — Satzfelder sind bereits ausgefüllt.
MiaB-Timeout-Fix: UAT-Infrastruktur gehärtet
Ein hartnäckiges Problem im UAT-Prozess behoben: POST /api/admin/tenants hing 30 Sekunden wenn MiaB (Mail-in-a-Box) auf Staging nicht erreichbar war. Playwright Global-Setup brach dadurch komplett ab.
Ursache: fetch() in src/lib/miab.ts hatte keinen Timeout. Der Node.js-Socket wartete bis zum OS-Default (~30s).
Fix: signal: AbortSignal.timeout(5_000) in beiden Fetch-Calls (createMiabAlias, deleteMiabAlias). Die Route hat bereits ein .catch() um den MiaB-Fehler zu ignorieren — aber nur wenn der Fetch überhaupt antwortet oder wirft. Jetzt tut er es.
Nebeneffekt: Für PROJ-99-Tests wurde eine eigene playwright.proj99.config.ts ohne Global-Setup erstellt — Tests brauchen nur den Superadmin-Account und erstellen ihre eigenen KRL-Ressourcen per API.
Lernmoment: Spec-Interpretation ist kritisch
EC-1 in der PROJ-99-Spec lautete ursprünglich: "+7 -9 → 1:1 muss erlaubt sein". Erste UAT-Bewertung: NO-GO, weil das Speichern blockiert wurde. Korrekte Interpretation: "muss erlaubt sein" bezieht sich auf die Parser-Ebene — die Notation +7 -9 darf nicht als Syntaxfehler abgelehnt werden. Ob das Ergebnis gespeichert werden darf, regelt die Vollständigkeitsprüfung — und die sagt korrekt: Bo5 braucht 3 Gewinnsätze, 1:1 ist kein gültiges Endergebnis. UAT-Bewertung korrigiert: 5/5 PASS, GO.
Erkenntnis 43: Zwei-stufige Validierung klar in der Spec benennen. "Muss erlaubt sein" ist mehrdeutig — ist das Parser-Akzeptanz oder freies Speichern? Die Spec wurde nachträglich korrigiert: "Parser akzeptiert die Notation; Vollständigkeitsprüfung beim Speichern blockiert unfertige Ergebnisse." Spec-Formulierungen die zwischen Schritten unterscheiden müssen explizit sagen bei welchem Schritt sie greifen.
Erkenntnis 44: UAT-Analyse bringt mehr als nur Pass/Fail. Der NO-GO-Befund war falsch — aber der Analyse-Prozess war wertvoll. Ein Screenshot des Dialog-Verhaltens nach +7 -9-Eingabe zeigte: kein Alert beim Tippen, Alert erst nach Speichern-Klick. Das war das korrekte Verhalten. Screenshots als Diagnosemittel bei unklaren UAT-Ergebnissen einsetzen.
Phase 29: TTR-Projektion + Tisches-Tab (25. Mai 2026)
Tag 65 — PROJ-100 deployed, PROJ-101 Supplement abgeschlossen, Feature-Ordner reorganisiert, PROJ-105 Spec erstellt.
PROJ-100: TTR-Projektion in KRL-Spielen
Die TTR-Projektion war für normale Turniere bereits vorhanden (PROJ-93). PROJ-100 erweitert sie auf KRL-Spiele: In der Admin-Spielansicht erscheint für jedes D/H-Spiel ein ΔTTR-Tooltip der anzeigt wie sich die TTR-Werte der beiden Spieler verändern würden — bevor das Ergebnis eingetragen ist. Außerdem zeigt die Tagesbilanz für eingeloggte Spieler ihre Gesamtbilanz über alle KRL-Spiele des Tages.
Technisch neu: Der Zugriff auf ttr_relevant musste tief in der KRL-Spiel-API ergänzt werden, weil die KRL-Gruppe keinen direkten Prop dafür hat — die Prop wird stattdessen aus dem KRL-Objekt propagiert. 12 QA-Bugs wurden während des Feature-Builds identifiziert und gefixxt. UAT: 6/6 PASS, GO.
PROJ-101 Supplement: Tisches-Tab in KRL-Detailseite
Der ursprüngliche PROJ-101-UAT (2026-05-19) hatte den neu implementierten "Tische"-Tab in der KRL-Detailseite nicht abgedeckt. Dieses Supplement fügt ihn nach.
Der Tab zeigt das PoolDashboard für den Venue-Pool an dem Ort und Datum der KRL — auch wenn KRL keinen direkten Fremdschlüssel auf venue_table_pools hat. Der Lookup läuft über den Unique-Constraint (tenant_id, location, date). Drei zusätzliche Bugs wurden in dieser Session gefixxt:
- BUG-101-C: Tablet-API filterte nur
offen-Status, KRL-Spiele mitspielbereitoderin_progressblieben unsichtbar - BUG-101-D: Pool-Dashboard färbte
in_progress-Matches nicht orange - BUG-101-E: API-Fehlermeldungen enthielten
tenantIdund rohe DB-Fehlertexte (Info-Leak)
UAT-Ergebnis: 3 PASS, 2 SKIP (US-3 + EC-2 wegen fehlender KRL-Testdaten mit eingecheckten Spielern — technische Limitation, keine Implementierungsfehler).
Staging-Deploy vor UAT — ein wichtiger Lernmoment
EC-1 scheiterte beim ersten UAT-Lauf mit venue_pool_id: undefined. Ursache: staging war 3 Commits hinter develop — der neue API-Code war noch nicht deployed. Lösung: develop → staging mergen vor UAT-Beginn. Die Regel ist schon im Projekt-State dokumentiert, wurde aber in der Hitze des Gefechts nicht geprüft.
Erkenntnis 45: Staging-Branch zuerst prüfen. Vor jedem UAT-Lauf: git log origin/staging..origin/develop prüfen. Wenn Commits vorhanden → zuerst mergen, dann testen.
Feature-Ordner reorganisiert
Ab PROJ-101 liegen alle neuen Features im Ordner features/PROJ-101-110/. Die bisherigen 10er-Blöcke (PROJ-01-10, PROJ-11-20, ..., PROJ-91-100) wurden bereits angelegt. Die Reorganisation hält features/ übersichtlich und verhindert flache Listenprobleme bei 100+ Specs.
PROJ-105: Affiliate-Selbstregistrierung Spec
Die Affiliate-Sektion für normale Tenants (aus PROJ-77 US-7 herausgelöst) hat eine vollständige Feature-Spec bekommen. Browser-use UAT hatte gezeigt dass /admin/promotions für normale Tenants eine 404-Seite liefert. Die Spec beschreibt den vollständigen Flow: Tenant beantragt, Superadmin genehmigt, Code wird auto-erstellt.
Phase 30: Tischzwang-Toggle (29. Mai 2026)
Tag 69 — PROJ-106 vollständig deployed (Requirements → Architecture → Frontend → Backend → QA → UAT → PROD), in einer einzigen Session.
PROJ-106: Tischzwang-Toggle für KRL und Race
PROJ-96 hatte die require_table_assignment-Spalte in der Datenbank angelegt, aber kein Admin-UI dazu gebaut — der Toggle war zwar in der DB-Spec vorhanden, aber für Admins nicht setzbar. Diese Session schließt diese Lücke vollständig.
Admins können jetzt beim Anlegen einer Kreisrangliste oder eines Race den Schalter "Tischzuordnung Pflicht" aktivieren (default: aus). Ist er aktiv, kann kein Match ohne zugeordneten Tisch auf "spielbereit" gesetzt werden. Das automatische Setzen auf "spielbereit" bei Tisch-Zuweisung war bereits durch PROJ-104 implementiert.
Ein Bug wurde dabei nebenbei behoben: Die bisherige Tischzwang-Prüfung im Backend blockierte beim falschen Zeitpunkt (eingetragen statt spielbereit). Das ist jetzt korrigiert.
Lernmoment: UAT-Test-Tenant und Plan-Gates
Die UI-Tests für die neuen Toggle-Checkboxen konnten nicht automatisiert werden — der UAT-Test-Tenant (club plan) wird von /admin/kreisranglisten und /admin/races/new auf das Dashboard umgeleitet, weil die Playwright-Navigation im Test-Kontext durch den Plan-Gate-Check blockiert wird. Kein Bug in der App — aber ein UAT-Muster das wir jetzt kennen.
Die Tests wurden durch Code-Review als "manuell verifiziert" abgezeichnet; die API-Tests (require_table_assignment wird korrekt gespeichert) liefen durch.
Erkenntnis 46: Plan-gated Seiten im UAT-Test-Tenant per API-Test oder Code-Review abdecken. Playwright kann keine Seiten öffnen die einen höheren Plan als "club" erfordern — außer man schaltet den Superadmin explizit auf seinen eigenen Tenant um (POST /api/admin/tenants/switch).
Zahlen (Stand 29. Mai 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 29. Mai 2026 (69 Tage) |
| Commits | ~967 |
| Features | 106 (PROJ-1 bis PROJ-106 spec'd) |
| Features deployed | 106 (PROJ-1–106 vollständig) |
| Features In Progress | 1 (PROJ-97 browser-use) |
| Features geplant | 2 (PROJ-102 Spieltort-Memory, PROJ-105 Affiliate) |
| Bugfixes | 435+ |
| Unit-Tests | 68 (Race + TTR + parseSetInput) + 25 Python-Tests (browser-use) |
| UAT-Tests | 176+ (Playwright) |
| UAT-Reports | 22 (alle GO: +PROJ-106) |
| API-Routen | 175+ |
| DB-Tabellen | 60+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Was kommt als Nächstes?
Priorität 1
- PROJ-105: Affiliate-Selbstregistrierung —
/architecture→/backend+/frontend→/uat
Priorität 2
- PROJ-97 P96-3: Event-Type-Badges im Pool-Dashboard manuell auf staging verifizieren (Turnier mit Venue-Pool öffnen)
- PROJ-102: Spieltort-Memory Autocomplete aus Veranstaltungshistorie
Phase 31: KRL Tisch-Pool direkt anlegen (1. Juni 2026)
Tag 72 — PROJ-107 in einer Session von der Idee bis zu PROD deployed.
PROJ-107: KRL Tisch-Pool direkt anlegen + Tischbelegung-Links
Ein Screenshot aus der laufenden KRL zeigte das Problem: Die "Tischbelegung" im Spielbetrieb-Tab zeigte alle Tische als "BELEGT" (weil noch keine Matches gestartet waren), die Matches selbst zeigten "Keine Tische verfügbar" — und ein direkter Weg einen Pool anzulegen gab es nicht.
PROJ-107 behebt zwei Lücken auf einmal:
Lücke 1 — Pool-Anlage: Der "Tische"-Tab erscheint jetzt immer wenn die KRL einen Ort und ein Datum hat — auch ohne Pool. Wenn noch kein Pool existiert, zeigt der Tab ein einfaches Formular ("Anzahl Tische" + "Pool anlegen"-Button). Ein Klick legt den Pool direkt an, das PoolDashboard erscheint sofort. Kein Umweg mehr über die Tischverwaltung.
Lücke 2 — Tischbelegung: Die Tisch-Badges im Spielbetrieb-Tab zeigen jetzt den echten Status der Pool-Tische (grün = frei, rot = belegt, orange = läuft) statt eine gruppenbasierte Annäherung. Zusätzlich ist jeder Badge klickbar und öffnet die Tablet-URL des Tisches in einem neuen Tab.
UAT-Diagnose: venue_pool_id liegt top-level
Beim ersten UAT-Lauf scheiterten zwei Tests mit venue_pool_id: undefined. Ursache: Die KRL GET-Route gibt { kreisrangliste: {...}, venue_pool_id: "..." } zurück — venue_pool_id liegt als Geschwister-Feld, nicht innerhalb von kreisrangliste{}. Die Tests lasen die falsche Ebene. Nach Korrektur: 4/5 PASS, GO.
Erkenntnis 47: API-Response-Struktur immer direkt aus dem Route-Code lesen, nicht erraten. Besonders bei verschachtelten Responses: mit console.log(body) oder einem direkten Curl-Test verifizieren bevor Tests geschrieben werden.
Phase 32: Tischverwaltung für KRL + Race (2.–3. Juni 2026)
Tag 74 — PROJ-107 Nachbesserungen + PROJ-108 in mehreren Iterationen deployed. Eine komplexe Session mit vielen kleinen Fixes.
PROJ-107 Nachbesserungen
Nach dem ersten Deploy von PROJ-107 zeigten sich drei konkrete Probleme im Livebetrieb:
venue-tables API findet keine Standalone-Pools —
GET /api/admin/venue-tablesfand Pools nur wenn ein Turnier verknüpft war. KRL-only Pools (direkt angelegt) wurden ignoriert. Fix: direkter Pool-Lookup via (tenant_id, location, date).Tisch-Dropdown rot für belegte KRL-Tische — Die Belegungsprüfung in
GET /api/admin/venue-tablesschaute nur inmatches(Turniere). KRL- und Race-Matches belegten Tische, aber das Dropdown zeigte sie als frei. Fix: KRL- und Race-Match-Belegung ergänzt.
Beide Fixes sind deployed — die Tisch-Belegungsanzeige funktioniert jetzt korrekt über alle Veranstaltungstypen.
PROJ-108: Tischverwaltung Venue-Selector
Die Tischverwaltung zeigte bisher "Kein Veranstaltungsort angegeben" für KRL- und Race-Veranstaltungen ohne Turnier. PROJ-108 erweitert den Venue-Selector: er lädt jetzt KRL- und Race-Events parallel zu Turnieren, dedupliziert nach Ort+Datum, und zeigt Event-Typ-Badges (📊 KRL, 🏁 Race).
Zusätzlich wurden KRL-Matches in die Warteschlange der Tischverwaltung eingebaut (Drag-and-Drop Zuweisung), Spieler-Tablet-Zugang über Tisch-Badges, und viele weitere Details.
Offener Bug: Warteschlange zeigt 0 KRL-Matches
Trotz zahlreicher Fixes zeigt die Warteschlange in der Tischverwaltung weiterhin 0 Matches für KRL-Veranstaltungen. Die Tisch-Belegung funktioniert korrekt (Reserviert/Belegt Badges). Das Problem liegt in der kreisrangliste_matches Pending-Query — die Ursache ist noch unklar.
Erkenntnis 48: Wenn eine DB-Query 0 Ergebnisse liefert aber die Belegung funktioniert, liegt das Problem oft nicht im Code sondern in den Filterbedingungen. Nächster Schritt: direkten SQL-Query auf Prod ausführen statt weiterer Code-Fixes.
Weitere Fixes
venue_table_idfehlte im SELECT der KRL-Matches-API → PoolTablePicker zeigte immer "Tisch wählen"- Tablet-Icon aus Match-Zeilen entfernt — Tablet-Zugang nur über Tisch-Badges
- Tablet-Seite zeigt jetzt spielbereite KRL/Race-Matches korrekt an (nicht mehr "Kein aktives Spiel")
Zahlen (Stand 3. Juni 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 3. Jun 2026 (74 Tage) |
| Commits | ~988 |
| Features | 108 (PROJ-1 bis PROJ-108 spec'd) |
| Features deployed | 107 (PROJ-1–107, PROJ-108 In Progress) |
| Features In Progress | 2 (PROJ-97 browser-use, PROJ-108 Warteschlange-Bug) |
| Features geplant | 2 (PROJ-102 Spieltort-Memory, PROJ-105 Affiliate) |
| Bugfixes | 450+ |
| Unit-Tests | 68 (Race + TTR + parseSetInput) + 25 Python-Tests (browser-use) |
| UAT-Tests | 181+ (Playwright) |
| UAT-Reports | 23 |
| API-Routen | 180+ |
| DB-Tabellen | 60+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Was kommt als Nächstes?
Priorität 1
- PROJ-108 Warteschlange: Direkter SQL auf Prod → warum liefert
kreisrangliste_matchesQuery 0? - PROJ-105: Affiliate-Selbstregistrierung —
/architecture→/backend+/frontend→/uat
Priorität 2
- PROJ-97 P96-3: Event-Type-Badges im Pool-Dashboard manuell auf staging verifizieren
- PROJ-102: Spieltort-Memory Autocomplete aus Veranstaltungshistorie
Phase 33: Spieltort-Memory + PROJ-108 Restbugs abgeschlossen (6. Juni 2026)
Tag 77 — PROJ-102 vollständig deployed, PROJ-108 Warteschlange-Bug behoben, drei neue Specs angelegt.
PROJ-108 Warteschlange-Bug — gefunden und behoben
Nach mehreren Code-Fixes in Phase 32 war die Ursache unklar: Die Warteschlange der Tischverwaltung zeigte weiterhin 0 KRL-Matches, obwohl die Belegungsanzeige korrekt funktionierte. Diagnose-Logs (temporäre console.log-Ausgaben) zeigten schließlich: Die Supabase-Query filterte mit .not.in("status", [...]) — aber diese Methode ist in der verwendeten Supabase-Client-Version kein gültiger Filter. Die Matches wurden komplett herausgefiltert, obwohl sie vorhanden waren.
Fix: .not.in(...) durch .in("status", ["offen", "spielbereit", "in_progress"]) ersetzt — identischer logischer Intent, aber korrekte Supabase-API. Ein-Zeilen-Fix nach stundenlanger Suche.
Gleichzeitig ein zweiter Fix: KRL-Matches haben intern kein match_number-Feld (das gibt es nur bei Turnieren) — der Code versuchte trotzdem darauf zuzugreifen und produzierte implizit undefined. Korrektur: auf match_order umgestellt, das tatsächlich existiert.
Erkenntnis 49: Supabase-Filter-Methoden nicht raten — Dokumentation lesen. .not.in() klingt intuitiv, existiert aber nicht als Methode der Supabase-JS-Bibliothek. Ergebnis: kein Fehler, keine Warnung, nur stille 0 Treffer. Bei unerklärten leeren Query-Ergebnissen zuerst die verwendeten Filter-Methoden gegen die offizielle Supabase-Doku prüfen.
PROJ-102: Spieltort-Memory — vollständiger Zyklus
Ein Feature, das seit Phase 26 auf der Warteliste stand, wurde in dieser Session von der Spec bis zu PROD-Deployment vollständig abgeschlossen.
Was gebaut wurde:
VenueAutocomplete-Komponente: Ersetzt das bisherige Freitextfeld für "Spielort" in allen drei Anlegen-Formularen (Turnier, KRL, Race). Nutzt shadcnPopoverAnchor+Input+CommandList— ein Kombo-Pattern das die shadcn-eigene Filterlogik bewusst deaktiviert, weil die Filterung serverseitig stattfindet.GET /api/admin/venues?q=: Neue API die Spielortnamen aus drei Tabellen aggregiert:tournaments.location,kreisranglisten.ortundraces.location. Wichtig: Die KRL-Tabelle heißtort, nichtlocation— eine Falle die nur durch Lesen des Schemas aufgefallen wäre.?-Trigger: Gibt der Admin?ein, öffnet ein Dropdown mit allen bekannten Spielorten. Intern wirdq=?alsq=""behandelt, die API gibt alle zurück. Einfach und effektiv.- In-Memory-Deduplication: Case-insensitive DISTINCT — "Sporthalle Pinneberg" und "sporthalle Pinneberg" werden als ein Eintrag zusammengeführt, wobei die Originalschreibweise erhalten bleibt. Alphabetische Sortierung via
localeCompare("de").
BUG-1: selectionMadeRef-Pattern
Der schwierigste Bug: Tippt der Admin ? um die Liste anzuzeigen und schließt das Popover ohne etwas auszuwählen, bleibt ? im Feld stehen und wird als Spielort gespeichert — was genau die Datenbasis verschmutzt, die PROJ-102 sauber halten soll.
Lösung: selectionMadeRef = useRef(false). Bei handleSelect() wird er auf true gesetzt. Im onOpenChange-Callback: wenn das Popover schließt und selectionMadeRef.current === false und der aktuelle Feldwert ist "?" → Feld wird auf "" geleert. Danach wird der Ref in jedem Fall zurückgesetzt.
Dieses Pattern ist universell für Comboboxen mit "Magic-Trigger-Zeichen" anwendbar.
UAT: 5/5 PASS, 1 SKIP (Pool-Hint-Banner als optional markiert und nicht implementiert). GO. Deployed als v1.102.0-PROJ-102.
Playwright: Cookie-Isolation im UAT
EC-3 (Kein Auth → 401/403) lieferte beim ersten Lauf 403 statt 401 — der Playwright-page-Fixture erbt Cookies aus dem Global Setup (auch wenn keine explizite Session vorliegt, sind Residual-Cookies vorhanden). Für wirklich frische Browser-Kontexte: browser.newContext({ storageState: undefined }) statt den page-Fixture nutzen.
Da beide Status (401 und 403) eine korrekte Zugriffsverweigerung bedeuten, akzeptiert der Test expect([401, 403]).toContain(res.status()) — robuster als auf genau 401 zu bestehen.
Neue Feature-Specs: PROJ-109/110/111
Drei neue Specs wurden in dieser Session angelegt:
| ID | Feature | Kurzbeschreibung |
|---|---|---|
| PROJ-109 | Spielzettel-Druck | 4 Spiele pro A4-Blatt, alle Formate (Turnier/KRL/Race) — für Offline-Ergebniserfassung |
| PROJ-110 | Tablet-Vollausbau KRL+Race | Live-Punkte, Schiedsrichter, Timeout, Satz-Ende für alle Formate |
| PROJ-111 | UAT Smoke Tests Match-Tablet | KRL/Race Tablet-Flow E2E, API-Response-Validierung, Checklisten-Template |
Erkenntnis 50: Specs zuerst schreiben lohnt sich auch ohne sofortige Implementierung. Das explizite Aufschreiben der Anforderungen klärt den Umfang, macht Abhängigkeiten sichtbar und reduziert den Aufwand beim späteren /architecture- und /frontend-Lauf erheblich — weil die schwierigen Fragen schon beantwortet sind.
Zahlen (Stand 6. Juni 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 6. Jun 2026 (77 Tage) |
| Commits | ~1010 |
| Features | 111 (PROJ-1 bis PROJ-111 spec'd) |
| Features deployed | 108 (PROJ-1–107 + PROJ-102) |
| Features In Progress | 1 (PROJ-97 browser-use, PROJ-108 Warteschlange jetzt behoben) |
| Features geplant | 4 (PROJ-105, PROJ-109, PROJ-110, PROJ-111) |
| Bugfixes | 460+ |
| Unit-Tests | 68 (Race + TTR + parseSetInput) + 25 Python-Tests (browser-use) |
| UAT-Tests | 186+ (Playwright) |
| UAT-Reports | 24 (alle GO: +PROJ-102) |
| API-Routen | 181+ |
| DB-Tabellen | 60+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Was kommt als Nächstes?
Priorität 1
- PROJ-105: Affiliate-Selbstregistrierung —
/architecture→/backend+/frontend→/uat - PROJ-109: Spielzettel-Druck — 4 Spiele pro A4, alle Formate
Priorität 2
- PROJ-110: Tablet-Vollausbau KRL+Race
- PROJ-111: UAT Smoke Tests Match-Tablet
Phase 34: UAT-Pipeline-Ausbau + PROJ-108 UAT abgeschlossen (13. Juni 2026)
Tag 84 — PROJ-108 UAT GO, Deployment auf alle 3 Umgebungen. UAT-Infrastruktur erhält visuellen Report-Layer.
UAT-Pipeline: Visueller Report-Layer
Das bestehende UAT-System hatte Playwright-Tests, Markdown-Reports und einen browser-use AI Runner — aber keine einheitliche visuelle Ausgabe mit eingebetteten Screenshots. Drei Ergänzungen schließen die Lücke:
playwright_report_builder.py: Liestreport.json(Stories, Steps, Screenshot-Pfade) und erzeugt ein selbst-enthaltenes HTML mit base64-eingebetteten Playwright-Screenshots. Technische Besonderheit: Pfadauflösung relativ zurreport.json-Datei selbst — Pfade im JSON müssen../../test-results/...lauten, nicht relativ zum Projektroot. Stumme "screenshot not found"-Warnungen waren der erste Hinweis auf dieses Verhalten.- PDF-Generierung:
generate-pdf.jsvia Playwright Chromium rendert das visuelle HTML zu einem portablen PDF. Alle Screenshots bleiben eingebettet — kein Dateibaum, direkter E-Mail-Anhang oder Archiv-Export möglich. - browser-use als visueller Test-Layer: US-4 (Badge-Rendering im Dropdown) wurde als
test.skip()in Playwright markiert und per AI-Agent visuell nachgetestet. Der Agent navigierte zu/admin/tables, öffnete den Venue-Selector und bestätigte: Format "Ort - Datum · Badge" korrekt, 📊 KRL-Emoji gerendert, 10 Tische und 47 Matches in der Warteschlange für den vorausgewählten Veranstaltungsort korrekt geladen.
PROJ-108 UAT — 3 PASS, 1 SKIP (browser-use) — GO
| US | Thema | Ergebnis |
|---|---|---|
| US-1 | KRL-Veranstaltungsort im Venue-Selector sichtbar | ✅ PASS |
| US-2 | Race-Veranstaltungsort im Venue-Selector sichtbar | ✅ PASS |
| US-3 | Gleicher Ort+Datum → kombinierter Eintrag (Deduplizierung) | ✅ PASS |
| US-4 | Event-Typ-Badges visuell korrekt gerendert | ⏭ SKIP → browser-use AI |
Alle kritischen Acceptance Criteria (AC-1 bis AC-4) bestätigt. Deployed auf DEV+TEST+PROD als Frontend-only Deploy ohne Migration. Git-Tag: v1.108.0-PROJ-108.
Playwright-Erkenntnisse: Radix UI Portal und Combobox-Disambiguierung
Die UAT für PROJ-108 brachte zwei wiederverwendbare Muster die jetzt im Projekt-Standard sind:
Radix UI Select Portal: shadcn/ui <Select> rendert Optionen in einem DOM-Portal das erst nach dem Click auf den Trigger im DOM erscheint. page.locator('[role="option"]') vor dem Click → leere Ergebnisliste. Lösung: Trigger klicken, dann auf [role="option"] warten. Retry-Loop (3 Versuche mit Escape-Zwischenschritt) für Zuverlässigkeit wenn Radix das Dropdown durch Focus-Events sofort wieder schließt.
Mehrere Comboboxen auf einer Seite: /admin/tables hat zwei [role="combobox"]-Elemente — den Venue-Selector und den Queue-Status-Filter (aria-label="Status-Filter"). Selektor: [role="combobox"]:not([aria-label="Status-Filter"]) für eindeutige Identifikation.
Race API Kapazitätsgrenzen (TTVSH): max_persons ≤ 2 × num_tables + 1. Mit 4 Tischen liegt das Maximum bei 9 Teilnehmern — 16 würden einen 409 auslösen. UAT-Tests nutzen num_tables: 8, max_persons: 16 als sicheres Setup.
Erkenntnis 51: DOM-Portal-Bibliotheken und Playwright — zuerst öffnen, dann prüfen. Radix UI, Floating UI und ähnliche Bibliotheken rendern Popup-Inhalte in einem separaten Portal (<body>-Ende), das beim Seitenload noch nicht existiert. Jede Playwright-Test-Hilfsfunktion die mit solchen Elementen arbeitet, muss das Öffnen als expliziten Schritt einbauen — kein waitForSelector ohne vorheriges click().
Erkenntnis 52: UAT-Berichte als Single-File HTML skalieren für Stakeholder-Kommunikation. Durch base64-Einbettung aller Screenshots ist jeder visuelle Report portabel — kein Dateipfad-Problem, direkt als E-Mail-Anhang oder Archiv nutzbar. Das Muster lässt sich auf alle zukünftigen UAT-Reports anwenden.
Zahlen (Stand 13. Juni 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 13. Jun 2026 (84 Tage) |
| Commits | ~1017 |
| Features | 111 (PROJ-1 bis PROJ-111 spec'd) |
| Features deployed | 108 (PROJ-1–108) |
| Features geplant | 3 (PROJ-105, PROJ-109, PROJ-110) |
| Bugfixes | 460+ |
| Unit-Tests | 68 (Race + TTR + parseSetInput) + 25 Python-Tests (browser-use) |
| UAT-Tests | 192+ (Playwright + browser-use AI) |
| UAT-Reports | 25 (alle GO: +PROJ-108) |
| API-Routen | 181+ |
| DB-Tabellen | 60+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Was kommt als Nächstes? (Stand 13. Juni 2026)
Priorität 1
- PROJ-105: Affiliate-Selbstregistrierung —
/architecture→/backend+/frontend→/uat - PROJ-109: Spielzettel-Druck — 4 Spiele pro A4, alle Formate
Priorität 2
- PROJ-110: Tablet-Vollausbau KRL+Race
- PROJ-111: UAT Smoke Tests Match-Tablet
Phase 35: Interne Rangliste — ein vollständig neues ELO-System (15. Juni 2026)
Tag 86 — PROJ-112 von Spec bis Deploy auf DEV + TEST in einer Session.
PROJ-112: Was gebaut wurde
Die Interne Rangliste ist kein Turnier und keine Kreisrangliste — sie ist ein eigenständiger Betriebsmodus für den wöchentlichen Trainingstag. Spieler fordern sich gegenseitig heraus, ein internes ELO (iTTR) wird täglich aktualisiert, und eine Wochenpflicht sorgt dafür dass jeder Spieler mindestens einmal pro Woche antritt.
Datenmodell: 6 neue Tabellen — internal_rankings, internal_ranking_members, internal_ranking_matches, internal_ranking_exception_weeks, internal_ranking_notifications, internal_ranking_week_snapshots. Komplett eigenständig, kein Bezug zu Turniertabellen.
Wochenpflicht und Safe-Status: Jeder Spieler muss pro Kalenderwoche (Mo–So) mindestens ein bestätigtes Spiel absolvieren. Wer das getan hat, ist "safe" — kein Gegner kann ihn mehr zwingen. Wer es nicht getan hat, verliert iTTR-Punkte durch den Nacht-Cron. Ausnahmewochen (Ferien, Feiertage) unterbrechen die Pflicht.
Forderungssystem: Spieler fordern andere aus der Mitgliederliste. Ein Match-Lifecycle (pending → accepted → played_pending_confirmation → confirmed). Jeder darf eine Forderung pro Woche ablehnen. Revanche-Anfragen (Rückforderungen nach einer Niederlage) haben eine 14-Tage-Laufzeit und verbrauchen das Ablehner-Token nicht — da sie wochenübergreifend sind.
iTTR-ELO: Standard-Elo-Formel mit K-Faktor. Berechnung in src/lib/internal-ranking/ittr.ts. Nacht-Cron (täglich 02:00) schreibt die aktualisierten iTTR-Werte in die Members-Tabelle, speichert einen Wochen-Snapshot und resettet den Safe-Status jeden Montag. Das DTTB-TTR der Spieler wird nie angefasst — iTTR ist vollständig isoliert in internal_ranking_members.ittr.
Frontend: Spieler-Mobilseite unter /player/interne-rangliste/[id] — Rangtabelle, eigene Challenges verwalten, Ergebnis eintragen, Benachrichtigungen. Responsive Mobile-First Design.
Admin-Seite: Übersicht, Mitgliederverwaltung (Beitritt bestätigen, iTTR manuell setzen), Ausnahmewochen verwalten.
QA: 8 Bugs + 1 Security-Problem
Die QA-Phase deckte ein vollständiges Spektrum auf — von logischen Fehlern bis zu einem echten Security-Problem:
| ID | Typ | Problem |
|---|---|---|
| BUG-1/3 | Logik | Wochenpflicht-Penalty griff falsch: Safe-Status nur per Flag, nicht per tatsächlichem Match in der Vorwoche |
| BUG-2 | Validierung | Inaktive Mitglieder konnten Challenges senden/annehmen/ablehnen |
| BUG-4 | Fairness | Revanche-Decline verbrauchte das wöchentliche Ablehnungstoken — unfair über Wochengrenzen |
| BUG-5 | Notification | 48h-Erinnerungen für pending_confirmation wurden nicht verschickt |
| BUG-6 | UX | Spieler-Rangliste zeigte keinen "Stand: HH:MM"-Zeitstempel |
| BUG-7 | Datenkonsistenz | .maybeSingle() ohne .limit(1) → PGRST116-Fehler bei mehreren Ausnahmewochen |
| BUG-8 | Rate-Limit | Kein Limit auf offene Challenges pro Spieler — Spam möglich |
| SEC-1 | Security | RLS-Policy irnotif_select erlaubte allen Tenant-Usern alle Benachrichtigungen zu lesen |
Erkenntnis 53: .maybeSingle() erfordert .limit(1) davor. Supabase/PostgREST wirft PGRST116 wenn mehr als eine Zeile matcht und .maybeSingle() aufgerufen wird. Das ist kontraintuitiv — man würde erwarten, dass die Funktion schlicht null zurückgibt oder die erste Zeile. Stattdessen: Fehler. Fix ist immer .limit(1).maybeSingle(). Das Pattern gilt für alle zukünftigen single-row Lookups.
Erkenntnis 54: RLS-Policies brauchen beide Filter: tenant_id UND user-spezifischer Filter. Eine RLS-Policy die nur auf tenant_id filtert, erlaubt allen authentifizierten Nutzern des Tenants alle Rows zu sehen. Für personenbezogene Daten (Benachrichtigungen, Spieler-Profil) muss immer eine zweite Bedingung (recipient_player_id = auth.uid() o.ä.) greifen. Die Schwäche wurde durch einen gezielten Security-Audit aufgedeckt — nicht durch normale Tests.
Erkenntnis 55: Wochenpflichts-Timing ist ein Cron-Problem, kein Flag-Problem. Der Safe-Status wird montags zurückgesetzt. Aber ein Spiel, das nach dem Cron-Lauf am Montag gespielt wird, kann das Flag nicht mehr retroaktiv setzen. Lösung: Bei jeder Penalty-Berechnung die tatsächlichen Matches der Vorwoche aus der DB holen (played_at Range) und mit dem Flag ODER-verknüpfen. Das ist korrekter als allein dem Flag zu vertrauen.
Zahlen (Stand 15. Juni 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 15. Jun 2026 (86 Tage) |
| Commits | ~1029 |
| Features | 112 (PROJ-1 bis PROJ-112 spec'd) |
| Features deployed | 109 (PROJ-1–108 PROD + PROJ-112 DEV+TEST, UAT ausstehend) |
| Features geplant | 4 (PROJ-105, PROJ-109, PROJ-110, PROJ-111) |
| Bugfixes | 470+ |
| Unit-Tests | 68 (Race + TTR + parseSetInput) + 25 Python-Tests (browser-use) |
| UAT-Tests | 192+ (Playwright + browser-use AI) |
| UAT-Reports | 25 (alle GO) |
| API-Routen | 195+ |
| DB-Tabellen | 66+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Was kommt als Nächstes? (Stand 15. Juni 2026)
Priorität 1
- PROJ-112 UAT: UAT auf Staging —
/uat PROJ-112 - PROJ-112 PROD-Deploy: Nach UAT GO auf main mergen + Ansible
Priorität 2
- PROJ-105: Affiliate-Selbstregistrierung —
/architecture→/backend+/frontend→/uat - PROJ-109: Spielzettel-Druck — 4 Spiele pro A4, alle Formate
Letzte Aktualisierung: 15. Juni 2026 — Tag 86 des Projekts. ~1029 Commits, 109 Features deployed (inkl. PROJ-112 DEV+TEST), 195+ API-Routen, 68 Unit-Tests + 25 Python-Tests, 192+ UAT-Tests, 3 Produktions-Umgebungen.
Phase 36: PROJ-112 UAT GO + PROD-Deploy (17. Juni 2026)
Tag 88 — PROJ-112 Interne Rangliste vollständig: UAT auf Staging, zwei Test-Infrastruktur-Bugs behoben, PROD-Deploy mit Ansible-Migrationen.
UAT: 11/13 PASS, 2 SKIP — GO
Das UAT für PROJ-112 lief in zwei Durchgängen: Im ersten Durchgang scheiterten US-7 und US-8 mit "Rangliste nicht gefunden" — obwohl die Rangliste auf dem UAT-Test-Tenant angelegt worden war. Im zweiten Durchgang (nach Infrastruktur-Fix, siehe unten) bestanden beide problemlos.
| US | Thema | Ergebnis |
|---|---|---|
| US-1 | Admin legt Rangliste an (Name, Format, Strafe) | ✅ PASS |
| US-2 | Mitglieder-Tab und iTTR-Setzungs-Dialog sichtbar | ✅ PASS |
| US-3 | Admin-Direkteintrag-Dialog im Spiele-Tab | ✅ PASS |
| US-5 | Ausnahmewoche anlegen, erscheint in Übersicht | ✅ PASS |
| US-6 | Einladungsbanner für Spieler im Dashboard | ✅ PASS |
| US-7 | Spieler-Ranking-Seite lädt, Beitritts-UI vorhanden | ✅ PASS |
| US-8 | Spieler-Ranking-Seite zeigt Rang, iTTR, Safe-Status-Elemente | ✅ PASS |
| US-9+10 | Herausfordern-Tab mit Gegner und iTTR (Spieler-Sicht) | ✅ PASS |
| US-11 | Meine-Spiele-Tab zeigt Ablehnen-Button und Token-Mechanik | ✅ PASS |
| US-12 | Spieler kann unbegrenzte Forderungen stellen | ⏭ SKIP (UI-heavy → browser-use AI) |
| US-13 | Verlierer kann Revanche anfordern (14-Tage-Verfall) | ⏭ SKIP (Precondition: abgeschlossenes Spiel nötig) |
| US-14+15 | Meine-Spiele-Tab zeigt Ergebnis-Eintragen und Bestätigungs-UI | ✅ PASS |
| US-16+17 | Ranglisten-Seite und Spielhistorie für eingeloggte Mitglieder | ✅ PASS |
Alle kritischen Acceptance Criteria (AC-1 bis AC-18, AC-21 bis AC-24) bestätigt. Die beiden SKIPs betreffen UI-heavy Flows (Challenge-Interaktion, Revanche-Flow) die einen vollständigen Match-Lifecycle als Precondition benötigen — sinnvoller via browser-use AI als reinem Playwright-Selector-Test.
Infrastruktur-Bug: switchTenant persistiert server-seitig
Der wichtigste Befund dieser Session war kein Applikations-Bug, sondern ein UAT-Infrastruktur-Bug mit hoher Wiederholungsgefahr.
Problem: POST /api/admin/tenants/switch schreibt active_tenant_id in app_metadata des Supabase-User-Records — server-seitig persistent, überlebt Browser-Sessions und Cookie-Resets. Nach dem Global-Setup-Schritt "switchTenant zu UAT-Test-Tenant" verblieb der Superadmin dauerhaft auf dem UAT-Test-Tenant. registerPlayer legt Spieler immer auf DEFAULT_TENANT_ID (00000000-0000-0000-0000-000000000001) an. Wenn der Superadmin auf dem UAT-Test-Tenant ist, finden US-7/US-8 die auf DEFAULT_TENANT_ID angelegte Rangliste nicht → "Rangliste nicht gefunden".
Fix: Vor saveFixtures() in tests/uat/global-setup.ts explizit zurückschalten:
const DEFAULT_TENANT_ID = "00000000-0000-0000-0000-000000000001";
await switchTenant(apiCtx, DEFAULT_TENANT_ID);
Jedes zukünftige Feature das registerPlayer mit Admin-Ressourcen kombiniert, braucht diesen Reset am Ende des Global-Setups.
Erkenntnis 56: switchTenant ist keine Browser-Session-Operation. Die naive Annahme "Playwright löscht Browser-State nach dem Global Setup" ist falsch wenn API-Calls Server-seitig persistente Metadaten schreiben. Bei jeder neuen UAT-Route prüfen: Wird eine server-seitige Persistenz-API genutzt, die nach dem Test zurückgesetzt werden muss?
Playwright Strict-Mode Fix
Ein zweiter Test-Bug: Der Button "Neue Rangliste" war durch ein zuvor ausgeführtes Modal (global-setup hatte bereits eine Rangliste angelegt) zweimal im DOM vorhanden — einmal als geöffnetes Modal, einmal als Button. Playwright Strict Mode wirft in diesem Fall:
Error: locator resolved to 2 elements: getByRole("button", { name: /Neue Rangliste/i })
Fix: .first() setzt den Selektor eindeutig — der erste sichtbare "Neue Rangliste"-Button ist der richtige.
Erkenntnis 57: Playwright Strict Mode und globale Test-Setup-Daten. Wenn Global-Setup Ressourcen anlegt (Ranglisten, Turniere), können deren UI-Artefakte (Modals, Buttons, Labels) in nachfolgenden Tests sichtbar sein. Bei Strict-Mode-Fehlern zuerst prüfen ob Global-Setup-State residuale DOM-Elemente hinterlässt.
PROD-Deploy: Ansible-Migrationen, Coolify, git-Tag
Nach UAT GO: develop → main Merge, Coolify-Webhook PROD-Build (~4 Min), dann zwei neue SQL-Migrationen via Ansible:
20260615_proj112_interne_rangliste.sql— 6 neue Tabellen inkl. RLS-Policies20260616_proj112_rls_hardening.sql— RLS-Härtung nach SEC-1-Fund
Verifikation: curl https://tt-control.de/admin/interne-ranglisten → HTTP 307 (korrekt: Redirect zu Login). Git-Tag v1.112.0-PROJ-112 gesetzt und gepusht.
Zahlen (Stand 17. Juni 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 17. Jun 2026 (88 Tage) |
| Commits | ~1034 |
| Features | 112 (PROJ-1 bis PROJ-112) |
| Features deployed | 109 (PROJ-1–108 PROD + PROJ-112 PROD) |
| Features geplant | 4 (PROJ-105, PROJ-109, PROJ-110, PROJ-111) |
| Bugfixes | 475+ |
| Unit-Tests | 68 (Race + TTR + parseSetInput) + 25 Python-Tests (browser-use) |
| UAT-Tests | 205+ (Playwright + browser-use AI) |
| UAT-Reports | 26 (alle GO: +PROJ-112) |
| API-Routen | 195+ |
| DB-Tabellen | 66+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 3 (DEV / TEST / PROD) |
Was kommt als Nächstes? (Stand 17. Juni 2026)
Priorität 1
- PROJ-105: Affiliate-Selbstregistrierung —
/architecture→/backend+/frontend→/uat - PROJ-109: Spielzettel-Druck — 4 Spiele pro A4, alle Formate
Priorität 2
- PROJ-110: Tablet-Vollausbau KRL+Race
- PROJ-111: UAT Smoke Tests Match-Tablet
Letzte Aktualisierung: 17. Juni 2026 — Tag 88 des Projekts. ~1034 Commits, 109 Features deployed (PROJ-1–108 + PROJ-112 vollständig auf PROD), 195+ API-Routen, 68 Unit-Tests + 25 Python-Tests, 205+ UAT-Tests, 3 Produktions-Umgebungen.
Phase 37: Bulk-Delete & Infrastruktur-Sparmaßnahme (19. Juni 2026)
Eine kurze, gezielte Session zwischen zwei größeren Feature-Sprints.
PROJ-112 Bulk-Delete: Admins können jetzt mehrere Interne Ranglisten gleichzeitig löschen statt einzeln — ein direktes Feedback aus dem laufenden Testbetrieb. Auf staging verifiziert.
DEV-Server deprovisioniert: Der DEV-Server (91.99.101.179) wurde abgeschaltet — nach Monaten produktiven Betriebs zeigte sich, dass die dritte Umgebung kaum eigenständigen Nutzen brachte: Feature-Entwicklung lief längst lokal (localhost:3000), DEV diente nur noch als Zwischenstation vor TEST. Ergebnis: zwei Umgebungen (TEST/PROD) statt drei, ein Server weniger zu warten, Ansible-Playbooks entsprechend vereinfacht (kein --limit dev mehr).
Erkenntnis 58: Nicht jede Umgebung, die einmal sinnvoll war, bleibt es. DEV war beim Multi-Tenancy-Umbau (PROJ-35) unverzichtbar, um riskante Migrationen zuerst zu testen. Mit stabiler Architektur und lokaler Entwicklung als De-facto-DEV wurde die dritte Umgebung zu reiner Kostenlast. Regelmäßig prüfen, ob Infrastruktur noch den Zweck erfüllt, für den sie einst eingerichtet wurde.
Phase 38: Security-Audit & API-Konsistenz-Vereinheitlichung (4.–5. Juli 2026)
Die bisher größte reine Qualitäts- und Sicherheits-Session des Projekts — kein neues Nutzer-Feature, aber die gründlichste Durchsicht der gesamten Codebasis seit dem Start.
Security-Audit: 292 Routen, 14 Cluster, 104 Agenten
Ein mehrstufiger Workflow durchsuchte die komplette API-Fläche nach OWASP-Top-10- und Supabase/Next.js-spezifischen Risiken: fehlende Tenant-Filter, fehlende Rollenprüfungen, Rate-Limit-Lücken, RLS-Schwächen. Ergebnis: 42 Findings, davon 39 auf develop gefixt (u.a. gekaperte Tenant-IDs bei Rollenvergabe, umgehbare Downgrade-Zahlungspflicht, hartcodierte Tenant-IDs auf öffentlichen Routen, fehlende Rate-Limits auf 7 Endpunkten, ungefilterte Nutzereingaben in HTML-E-Mails).
Ein Teil der Session lief autonom, während der Nutzer bei einem Geburtstag war — mit der expliziten Vorgabe, Widersprüche zu sammeln statt sie eigenmächtig aufzulösen. Zwei Kategorien wurden bewusst NICHT autonom angewendet: Änderungen an RLS-Policies und am Authentifizierungs-Flow — beides verlangt laut Projektregeln explizite Nutzer-Freigabe, unabhängig davon, wie dringend der Fix erscheint. Nach Rückkehr wurden die zurückgestellten Findings (SEC-02, SEC-19, SEC-22) in wenigen Minuten gemeinsam entschieden und umgesetzt.
Erkenntnis 59: Autonomie-Grenzen zahlen sich aus, wenn sie vor der Ausführung feststehen. Die Grenze "RLS/Auth-Änderungen brauchen Freigabe" existierte in den Projektregeln bereits, bevor die autonome Session begann — dadurch gab es beim Zurückkommen keine Überraschung, sondern drei klar benannte, gut vorbereitete Entscheidungen.
PROJ-113: API-Konsistenz-Vereinheitlichung
Vier der Audit-Findings (SEC-18, SEC-38, SEC-39, SEC-40) waren keine einzelnen Bugs, sondern ein wiederkehrendes Muster über vermutlich 100+ Routen: kein zentraler Auth-Helfer (Rollenprüfung wurde in jeder Route neu abgeschrieben — Copy-Paste-Fehler hatten bereits reale Lücken verursacht), 401 und 403 uneinheitlich verwendet, Fehlerantworten mal mit, mal ohne Fehlercode, UUID-Parameter mal geprüft, mal nicht.
Aus diesem Muster wurde ein eigenes Feature: erst eine vollständige Bestandsaufnahme aller 264 API-Routen (welche Routen betroffen sind, welche Frontend-Stellen auf welche Fehlerantwort reagieren), dann zwei kleine, zentrale Bausteine (requireAuth(), requireRole()) und dann Cluster für Cluster migrieren — nie alles auf einmal, jeder Cluster mit eigenem Kontrolltest-Zwischenstopp.
Bisher abgeschlossen: superadmin (30 Routen), admin (75 Routen), sowie unerwartet die Next.js-Middleware selbst (src/proxy.ts) — eine zweite, von den API-Routen unabhängige Autorisierungs-Ebene, die erst während der Kontrolle von Cluster 2 auffiel und mit-vereinheitlicht wurde. Zuletzt betreuer/player (19 Routen) — dabei auch ein echter kleiner Fund: Der Endpunkt zur PIN-Bestätigung lieferte bei falscher PIN fälschlich den Statuscode "nicht angemeldet", obwohl der Spieler angemeldet war — nur die PIN war falsch. Korrigiert.
Erkenntnis 60: Eine fehlende gemeinsame Stelle ist gefährlicher als ein einzelner Bug. Copy-Paste-Auth-Code an 100+ Stellen bedeutet: derselbe Fehler kann 100 Mal unabhängig entstehen. Zwei kleine, gut getestete Bausteine schließen diese Fehlerklasse für immer — auch für Routen, die noch gar nicht geschrieben sind.
Erkenntnis 61: Bei einer derart breiten Änderung schützt nur echte Gegenprüfung. Mehrere Runden zeigten dasselbe Muster: Der ausführende Agent meldete "fertig", aber ein unabhängiger Blick auf Typprüfung, Linter und den tatsächlichen Code fand reale Lücken — vergessene Fehlerpfade, ein Frontend-Login-Redirect, der nach der Migration nicht mehr griff. Bei sicherheitsrelevanten Änderungen ersetzt die Selbstauskunft eines Agenten nie die eigene Prüfung.
Nebenbei zwei unabhängige Test-Infrastruktur-Bugs gefunden und behoben (kein Produktcode betroffen): Ein UAT-Test rief eine Hilfsfunktion mit vertauschten Argumenten auf und navigierte dadurch nie zur eigentlich zu testenden Seite; ein anderer Test räumte beim Rollenwechsel nur Cookies auf, nicht aber im Browser verbliebene Session-Reste.
Zahlen (Stand 5. Juli 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 5. Jul 2026 (107 Tage) |
| Commits | ~1083 |
| Features | 113 (PROJ-1 bis PROJ-113, PROJ-113 in Bearbeitung) |
| Features deployed | 109 (unverändert — PROJ-113 noch nicht deployed) |
| Security-Findings (Audit 2026-07-04) | 42 gefunden, 39 gefixt auf develop |
| API-Routen auf zentrale Auth-Helfer migriert | 124 von ~264 (superadmin 30, admin 75, betreuer/player 19) |
| Bugfixes | 480+ |
| Unit-Tests | 68 (Race + TTR + parseSetInput) + 25 Python-Tests (browser-use) |
| UAT-Tests | 205+ (Playwright + browser-use AI) |
| API-Routen | 195+ |
| DB-Tabellen | 66+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 2 (TEST / PROD — DEV am 19.6. deprovisioniert) |
Phase 39: PROJ-113 fertig — Cluster 4–9, QA, TEST- und PROD-Deploy (6. Juli 2026)
Der Rest des größten reinen Qualitäts-Features des Projekts: die verbleibenden sechs Cluster, die Abnahme und der doppelte Rollout.
Cluster 4–9: die letzten ~140 Routen
Nach demselben Muster wie zuvor — migrieren, unabhängig gegenprüfen (tsc/eslint/vitest), dokumentieren — liefen schiedsrichter/table, tournaments, kreisranglisten, races, internal-rankings und ein "Rest"-Cluster durch. Zwei Momente waren dabei lehrreich:
- Der "Rest"-Cluster war kein Rest. Der ursprüngliche Plan sortierte alles außerhalb der acht benannten Cluster pauschal als "nur Formatangleichung" ein. Eine genaue Prüfung zeigte: 16 dieser Routen (Checkout, Tickets, Tischpools, Spielerverwaltung u.a.) hatten echte, bisher nur schlicht nicht zugeordnete Admin-Prüfungen — sie bekamen die volle Behandlung statt nur Kosmetik.
- Ein Agent widersprach der eigenen Vorgabe — zu Recht. Beim Internen-Rangliste-Cluster hatte die Aufgabenstellung eine Route falsch als "Spieler-Aktion" eingestuft. Der ausführende Agent bemerkte anhand des Codes, dass es sich um eine reine Admin-Funktion handelte, hielt sich nicht an die falsche Anweisung und meldete die Korrektur transparent. Das Vertrauen in "erst nachdenken, dann handeln" statt blinder Befehlsbefolgung hat sich hier ausgezahlt.
- Eine echte Sicherheits-Nuance wurde bewahrt, nicht eingeebnet. Eine Rangliste-Route prüfte bislang nicht nur die Rolle, sondern zusätzlich ob der Admin zum richtigen Mandanten gehört. Der naive zentrale Rollen-Helfer allein hätte diese Mandanten-Prüfung verloren und damit eine Mandanten-übergreifende Zugriffslücke geöffnet. Auch das wurde erkannt und bewusst nicht vereinheitlicht.
QA: READY
Eine Gesamtabnahme über alle neun Cluster (rund 264 API-Routen) ergab null kritische, hohe oder mittlere Befunde — nur drei kleinere Kosmetik-Funde, zwei davon direkt behoben (ein Login-Redirect, der bei abgelaufener Sitzung noch die alte Fehlerbehandlung nutzte, und ein falscher Fehlercode).
Deploy: TEST, dann PROD
Bei einem derart breiten Eingriff (praktisch jede API-Route der Anwendung) folgte der Rollout dem vorsichtigen Weg: zuerst nach TEST, dort die komplette Kontrolltest-Suite plus eine eigens geschriebene Prüfung aller neun Cluster, dann erst nach PROD. Dabei fiel auf: 65 Commits — der komplette Sicherheits-Audit vom 4. Juli plus alle neun Cluster — waren nie zu GitHub hochgeladen worden, sie lagen nur lokal. Vor dem eigentlichen Deploy musste das erst nachgeholt werden.
Die eigens für PROJ-113 geschriebene Kontrolltest-Suite prüfte alle sechs Kern-Zusicherungen des Features direkt am lebenden System: anonyme Anfragen werden überall gleich abgewiesen, falsche Rollen bekommen die richtige Fehlerantwort, jede Fehlerantwort hat ein einheitliches Format, ungültige IDs führen nicht zu rohen Datenbankfehlern, abgelaufene Sitzungen leiten zum Login statt eine Fehlermeldung zu zeigen, und alle vier Nutzerrollen (Admin, Spieler, Betreuer, Schiedsrichter) erreichen ihr jeweiliges Dashboard fehlerfrei. Für die letzte Prüfung musste für die Schiedsrichter-Rolle sogar der komplette echte Weg durchlaufen werden — Selbstregistrierung, Admin-Freigabe, Login —, weil die vorhandenen Testkonten dafür nicht ausreichten.
Erkenntnis 62: Ein "kein Deploy während der Migration"-Vorsatz braucht einen expliziten Nachhol-Schritt. Die bewusste Entscheidung, PROJ-113 ausschließlich lokal zu entwickeln und erst am Ende zu veröffentlichen, war richtig — aber sie führte dazu, dass sich unbemerkt 65 ungesicherte Commits ansammelten. Bei mehrtägigen Feature-Branches lohnt sich ein Zwischenstand-Push auf GitHub, auch ohne Auto-Deploy, allein als Sicherung.
Erkenntnis 63: Ein eigener Härtetest für ein reines Backend-Feature lohnt sich. PROJ-113 hat keine Oberfläche — trotzdem wurde eine dedizierte Testdatei geschrieben, die genau die sechs Versprechen des Features direkt gegen die Live-Umgebung prüft, statt sich nur auf die Kontrolltests bereits bestehender Features zu verlassen. Das deckte zwei kleinere, vorbestehende Lücken in der Test-Infrastruktur auf (Betreuer- und Schiedsrichter-Login-Wege hatten sich geändert, ohne dass die Testhilfen nachgezogen wurden) — reine Testschulden, kein Produktfehler.
Zahlen (Stand 6. Juli 2026)
| Metrik | Wert |
|---|---|
| Zeitraum | 21. Mär — 6. Jul 2026 (108 Tage) |
| Commits | ~1104 |
| Features | 113 (PROJ-1 bis PROJ-113, alle deployed) |
| Features deployed | 110 (PROJ-1–108 + PROJ-112 + PROJ-113, alle auf PROD) |
| Security-Findings (Audit 2026-07-04) | 42 gefunden, 42 gefixt — vollständig abgeschlossen |
| API-Routen auf zentrale Auth-Helfer migriert | ~264 von ~264 — alle Cluster fertig |
| Bugfixes | 485+ |
| Unit-Tests | 68 (Race + TTR + parseSetInput) + 25 Python-Tests (browser-use) |
| UAT-Tests | 210+ (Playwright + browser-use AI) |
| API-Routen | 195+ |
| DB-Tabellen | 66+ |
| Rollen | 6 (Super-Admin, Admin, Spieler, Betreuer, Schiedsrichter, Spectator) |
| Umgebungen | 2 (TEST / PROD — DEV am 19.6. deprovisioniert) |
Phase 40: PROJ-105 Affiliate-Selbstregistrierung — kompletter Feature-Zyklus an einem Tag (6. Juli 2026)
Nach dem großen Aufräum-Feature PROJ-113 wieder ein klassisches, nutzersichtbares Feature — und diesmal lief der komplette Zyklus (Architektur, Frontend, Backend, QA, zwei Deploys, UAT) ohne Unterbrechung an einem Tag durch.
Die Lücke, die PROJ-77 offen gelassen hatte
PROJ-77 hatte im April das komplette Affiliate-Programm gebaut — Codes, Empfehlungs-Tracking, die Anzeige-Karte in den Admin-Einstellungen. Was fehlte: Ein Verein musste vom Superadmin erst "entdeckt" und manuell mit einem Code ausgestattet werden. PROJ-105 schließt genau diese Lücke mit einem strukturierten Antrag-Workflow: Der Verein beantragt selbst über eine neue Seite, der Superadmin sieht alle Anträge in einer Übersicht und genehmigt oder lehnt ab, und bei Genehmigung entsteht der Code vollautomatisch — ohne dass der bestehende PROJ-77-Code angefasst werden musste.
QA fand fünf kleine, aber lehrreiche Lücken
Die Qualitätsprüfung fand keinen einzigen kritischen Fehler, aber fünf kleinere Ungereimtheiten, die alle noch vor dem Deploy behoben wurden:
- Ein Code mit "unbegrenzten Einlösungen" zeigte in der Oberfläche fälschlich "0 von 1.000.000 Einlösungen" — ein rein kosmetischer, aber irreführender Anzeige-Fehler.
- Ein genehmigter, aber abgelaufener Code zeigte dem Verein weiterhin "wir prüfen deine Anfrage" statt eines Ablauf-Hinweises mit der Möglichkeit, erneut zu beantragen.
- Drei weitere kleine Lücken: ein fehlender Schutz gegen einen zweiten aktiven Code über einen direkten API-Aufruf, ein "verwaister" Code falls die Genehmigung auf halbem Weg fehlschlägt, und ein Erklärtext ohne konkrete Zahlen zu Belohnung und Laufzeit.
Die Kontrolltest-Suite fand die Fehler in sich selbst, nicht im Produkt
Beim Schreiben der eigenen Testreihe für PROJ-105 gab es eine Besonderheit: Antragsteller und Genehmiger sind hier derselbe Nutzer (der Superadmin testet als Verein UND genehmigt selbst) — der Test musste also den Zustand vor jedem Durchlauf sauber zurücksetzen. Der erste Versuch, einen bestehenden Code zu entfernen, schlug fehl, weil die zugrundeliegende Schnittstelle das dafür genutzte Kommando gar nicht kennt — sie erwartet stattdessen ein "Code deaktivieren"-Kommando. Nach der Korrektur bestätigte der Test sogar, dass ein eingebauter Schutzmechanismus genau richtig funktioniert: Ein zweiter Antrag bei bereits vorhandenem Code wurde korrekt abgewiesen.
Erkenntnis 64: Ein voller Feature-Zyklus an einem Tag ist machbar, wenn die Vorarbeit stimmt. PROJ-105 nutzte fast ausschließlich Bausteine aus PROJ-77 (Codes), PROJ-50 (Zahlungen) und PROJ-71 (E-Mail-Vorlagen) — echt neu war nur eine kleine Tabelle und der Antrags-Workflow selbst. Je mehr ein neues Feature auf bereits bewährten Bausteinen aufbaut, desto realistischer wird "Idee bis Produktion an einem Tag".
In Zahlen (Stand 6. Juli 2026)
| Kennzahl | Wert |
|---|---|
| Zeitraum | 21. Mär — 6. Jul 2026 (108 Tage) |
| Commits | ~1113 |
| Features | 113 (PROJ-1 bis PROJ-113, alle deployed) |
| Features deployed | 111 (alle auf PROD) |
| Neue DB-Tabelle | affiliate_requests (mit RLS) |
| Neue API-Routen | 6 |
Phase 41: PROJ-109 Spielzettel-Druck & Admin-Ergebniseingabe — Wiederverwendung statt Neubau (6. Juli 2026)
Direkt im Anschluss an PROJ-105 folgte ein weiteres Feature am selben Tag — diesmal mit einer Architekturphase, die sich als wichtiger herausstellte als gedacht.
Die Architektur-Recherche fand zwei Drittel des Features bereits fertig vor
Die Spec verlangte sechs User Stories: Spielzettel drucken für Turnier, Kreisrangliste und Race (je 4 pro A4-Blatt), und Admin-Ergebniseingabe für alle drei Formate. Beim Architektur-Rundgang zeigte sich: Die Ergebniseingabe existierte für alle drei Formate bereits (aus PROJ-88, PROJ-99 und dem bestehenden Turnier-Dialog), und das 4-pro-A4-Druck-Layout gab es schon aus PROJ-66 (Check-in-QR-Druck) als fertiges Vorbild. Übrig blieb der eigentliche Kern: ein gemeinsamer Druck-Endpunkt für alle drei Spielformate statt drei getrennter. Diese Erkenntnis wurde dem Nutzer vor der Umsetzung vorgelegt und bestätigt, statt einfach drauflos zu bauen — dadurch entfiel eine separate Frontend-Phase komplett.
QA fand einen Druck-Layout-Fehler, der nur bei ungewöhnlichen Satzformaten auftrat
Von drei gefundenen Low-Bugs war einer optisch interessant: Bei Best-of-13-Sätzen (13 mögliche Sätze in der Turnierregel) sprengte die Satz-Tabelle die feste Kachelhöhe eines Viertel-A4-Zettels und wurde abgeschnitten. Der Fix war ein kompakterer Tabellenstil (kleinere Zeilenhöhe und Schriftgröße), der nur bei mehr als 7 Sätzen aktiviert wird — Standardformate wie Best-of-5 blieben unverändert.
Der UAT-Testaufbau brauchte mehr Detektivarbeit als das Feature selbst
Um den KRL-Druckzweig mit echten Daten zu testen, musste der Test eine Kreisrangliste samt Gruppe und Spielen anlegen. Das offenbarte eine dreifach nicht-offensichtliche Reihenfolge in der bestehenden API: Gruppen dürfen nur ausgelost werden, solange der Status noch nicht auf "Gruppenspiele" gesetzt ist (danach kommt ein Sperr-Fehler), das Auslosen selbst braucht einen Pflicht-Parameter (Gruppengröße) den man leicht vergisst, und die Auslosungs-Antwort selbst enthält gar keine Gruppen-IDs — die müssen separat abgefragt werden. Drei fehlgeschlagene Testläufe in Folge, bis ein eigens geschriebener Diagnose-Test (statt eines rohen Skripts) den echten Ablauf sichtbar machte. Am Ende bestand der komplette Test mit einem sauber gedruckten Zettel-Layout: sechs Spielpaarungen, zwei A4-Seiten, alle Felder korrekt.
Erkenntnis 65: Architektur-Recherche vor dem Bauen zahlt sich am meisten aus, wenn man ihr auch traut. Die Kürzung von zwei Dritteln des ursprünglichen Aufwands (keine neue Ergebniseingabe, keine neue Druck-Vorlage) entstand nicht durch Abkürzen, sondern durch gründliches Nachsehen was schon da ist — und das Ergebnis dem Nutzer transparent vorzulegen statt es stillschweigend anzunehmen.
Erkenntnis 66: Ein rohes Debug-Skript außerhalb der Testumgebung ist selten die schnellere Abkürzung. Der erste Versuch, das KRL-Setup-Problem mit einem eigenständigen Node-Skript zu diagnostizieren, scheiterte an Modul- und Auth-Problemen, die in der echten Playwright-Umgebung gar nicht auftreten. Ein regulärer, aber temporärer Playwright-Test mit Konsolen-Ausgaben brachte die Antwort in einem Durchlauf.
In Zahlen (Stand 6. Juli 2026)
| Kennzahl | Wert |
|---|---|
| Zeitraum | 21. Mär — 6. Jul 2026 (108 Tage) |
| Commits | ~1121 |
| Features | 112 Einträge (PROJ-1 bis PROJ-113, einige superseded/in Arbeit) |
| Features deployed | 106 (alle auf PROD) |
| Neue API-Routen | 1 (gemeinsamer Druck-Endpunkt für 3 Formate) |
| QA-Bugs gefunden/gefixt | 3 (0 Critical/High/Medium, 3 Low) |
| Neue Unit-Tests | 5 (Druck-Endpunkt-Regressionstests) |
Phase 42: Tablet-Vollausbau, automatisierte Rauchtests und Walkover — drei Features in zwei Tagen (11.–12. Juli 2026)
Drei vollständige Feature-Zyklen (Architektur → Backend → QA → UAT → TEST- und PROD-Deploy) an zwei aufeinanderfolgenden Tagen: PROJ-110 (Tablet-Vollausbau für Kreisrangliste und Race — bisher nur Turniere konnten live gescored werden), PROJ-111 (automatisierte Rauchtests, die künftig jeden Staging-Deploy absichern) und PROJ-114 (Walkover-Erfassung und satzübergreifendes Rückgängig, entstanden aus zwei QA-Bugs von PROJ-110).
QA-Bugs, die zu groß für einen Nachbesserungs-Commit waren, wurden zu einem eigenen Feature
Beim QA-Durchlauf von PROJ-110 fielen zwei Bugs auf, die keine einfachen Fixes waren: Aufgabe/Verletzung mitten im Spiel wurde gar nicht unterstützt, und ein versehentlich beendeter Satz ließ sich nicht mehr rückgängig machen. Statt sie als Bugfix-Commits in PROJ-110 hineinzuquetschen, wurden sie als eigene Spec (PROJ-114) mit eigenen User Stories und eigener Regelgrundlage behandelt. Vor der Umsetzung klärte der /dttb-rules-Skill die tatsächliche DTTB-Regel: Bei Aufgabe bleibt der Punktestand des laufenden Satzes erhalten, nur die noch fehlenden Sätze werden 11:0 gewertet — der ganze Satz auf 0 zu setzen wäre schlicht falsch gewesen.
Ein Fallstrick tauchte zweimal auf und wurde beim zweiten Mal präziser gelöst
Beim PROD-Deploy von PROJ-110 zeigte die Homepage tagelang scheinbar noch den alten Stand — tatsächlich lag es am Next.js-ISR-Cache (Cache-Control: s-maxage=31536000), der eine gecachte Version ausliefert, egal wie oft neu deployed wird. Die Lösung: einen dynamischen API-Endpunkt statt der Startseite abfragen. Beim PROD-Deploy von PROJ-114 zeigte sich, dass auch das nicht ausreicht — ein neuer API-Endpunkt, der im alten Build noch gar nicht existiert, liefert trotzdem eine gültige (generische Next.js-404-Seite als) Antwort mit brauchbarem HTTP-Status. Erst die Prüfung auf die tatsächlich erwartete JSON-Struktur der neuen Route bestätigt zuverlässig, dass der neue Code wirklich läuft.
Der automatisierte Rauchtest fand echte Bugs, bevor ein Mensch sie hätte finden können
PROJ-111 baute eine GitHub-Actions-Pipeline, die nach jedem Staging-Deploy automatisch den Tablet-Flow für alle drei Spielformate durchklickt. Schon beim ersten echten Lauf gegen Staging fielen zwei Bugs auf, die keine Code-Review vorher gefunden hatte: Neu angelegte Races starten im Status "draft" und sind für die Tisch-Warteschlange unsichtbar (die Auslosung hebt nur "open" auf "running", nie "draft"), und automatisch angelegte Turnier-Tische mit generischem Namen wie "Tisch 1" kollidierten mit gleichnamigen Tischen anderer Turniere auf demselben Server.
Ein einziges Wort im Zugriffsberechtigungsnamen legte die Testpipeline lahm
Nach dem ersten CI-Lauf des neuen Rauchtests kam ein leerer statt ein maskierter Wert für eines von drei GitHub Secrets zurück — die anderen beiden waren korrekt als *** verdeckt. GitHub Actions löst einen falsch benannten secrets.X-Verweis lautlos zu einem leeren String auf, statt einen Fehler zu werfen. Der Secret-Name im Workflow und der Name im GitHub-Projekt unterschieden sich um ein Wort.
Ein irreführender Doppelgänger im Testcode versteckte den eigentlichen Fehler
Beim UAT von PROJ-114 blockierte ein unsichtbares Overlay wiederholt den Klick auf den Rückgängig-Button — Ursache war ein Button mit dem gleichen Bedienungshinweis ("Timer stoppen"), der zweimal auf der Seite existierte: einmal im blockierenden Vollbild-Overlay, einmal harmlos in der ständig sichtbaren Mittelspalte. Der Testhelfer suchte ohne Eingrenzung nach diesem Hinweis, traf auf beide Treffer und scheiterte lautlos an einer Playwright-eigenen Mehrdeutigkeits-Regel — sein eigenes Fehler-Abfangen (.catch(() => false)) schluckte diesen Fehler und meldete fälschlich "Button nicht gefunden" statt "zwei Treffer, welchen meinst du?". Erst die gezielte Eingrenzung auf das eigentliche Overlay-Element behob es dauerhaft — und der Fix landete im gemeinsam genutzten Testhelfer, wovon alle künftigen UAT-Läufe profitieren.
Erkenntnis 67: Ein QA-Bug ist nicht automatisch ein Bugfix-Commit. Wenn ein gefundenes Problem eine eigene Regelgrundlage, eigene User Stories und eigene Edge Cases braucht, verdient es eine eigene Spec — auch wenn es aus einem anderen Feature entstanden ist.
Erkenntnis 68: Ein Deploy-Readiness-Check ist nur so gut wie sein am wenigsten wahrscheinliches Fehlbild. "HTTP-Status ist nicht 000" klingt nach einer sauberen Prüfung, bis man merkt, dass auch eine falsche generische Fehlerseite diese Bedingung erfüllt. Die robuste Prüfung fragt nach der tatsächlich erwarteten Antwortform, nicht nur nach "irgendeine Antwort kam an".
Erkenntnis 69: Automatisierte Tests finden am meisten, wenn sie gegen echte, geteilte Infrastruktur laufen. Die Race-Status- und Tisch-Namens-Bugs waren beide Zustände, die nur auf dem echten, von mehreren Turnieren gleichzeitig genutzten Staging-Server auftreten — eine isolierte lokale Testumgebung hätte sie nicht gezeigt.
In Zahlen (Stand 12. Juli 2026)
| Kennzahl | Wert |
|---|---|
| Zeitraum | 21. Mär — 12. Jul 2026 (114 Tage) |
| Commits | ~1142 |
| Features | 113 Einträge (PROJ-1 bis PROJ-114, einige superseded) |
| Features deployed | 109 (alle auf PROD) |
| Neue API-Routen | 2 (Walkover-Endpunkt für alle 3 Formate, gemeinsames Smoke-Test-Fixture-Modul) |
| QA-Bugs gefunden/gefixt | 6 (0 Critical, 0 High, 4 Medium, 2 Low) über 3 Features |
| Neue Unit-/E2E-Tests | 7 Unit-Tests (Walkover-Satzlogik) + 4 Playwright-Smoke-Tests + 6 UAT-Tests |
| Neue CI-Automatisierung | 1 (GitHub-Actions-Rauchtest nach jedem Staging-Deploy) |
Was kommt als Nächstes? (Stand 12. Juli 2026)
Siehe docs/PRD.md — Roadmap ab PROJ-115.
Letzte Aktualisierung: 12. Juli 2026 — Tag 114 des Projekts. ~1142 Commits, 109 Features deployed (alle auf PROD, inkl. PROJ-110 + PROJ-111 + PROJ-114), 2 Produktions-Umgebungen (TEST/PROD).
