TT-ControlTurniersystem

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_id in app_metadata
  • Public-Tournament-Route filtert auf Tenant
  • Table-Routen: Hardcoded DEFAULT_TENANT_ID durch 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.nu bestä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-processing ist 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) und open_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) in tournament_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-number mit 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:

  1. PostgREST kannte die neue Spalte nichtALTER 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.
  2. Superadmin-Tenant fehlte — der Superadmin-Account hatte eine tenant_id im JWT die in der tenants-Tabelle nicht existierte. Manuell angelegt: "CoffeeMaster / verband".
  3. Sentry schwieg — der Fehler wurde von der Route abgefangen (if (error) { return 500 }) und nie geworfen. onRequestError in 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:

  1. PKCE → implicit Flow: @supabase/ssr auf PKCE-Modus. Der Recovery-Link erzeugte keinen code_verifier im Browser → AuthSessionMissingError. Fix: flowType: "implicit" im Browser-Client.
  2. GoTrue Redirect-Allowlist: /player/reset-password fehlte in ADDITIONAL_REDIRECT_URLS. GoTrue leitete stumm auf Homepage um. Fix: URL auf allen 3 Servern eingetragen.
  3. Homepage-Token-Auffang: Für den Fall eines stillen Redirects fängt die Homepage jetzt #type=recovery-Tokens ab und leitet zu /player/reset-password weiter.

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:

  1. Feature-Spec einlesen → User Stories extrahieren
  2. UAT-Plan als Tabelle präsentieren → User bestätigt Scope
  3. Playwright-Tests in tests/uat/features/proj-XX/ schreiben
  4. Story für Story durchführen — nach Bestätigung
  5. UAT-Report als UAT/PROJ-XX/UAT-PROJ-XX-YYYY-MM-DD.md + PDF speichern
  6. 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-b fü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: boolean in plans.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 nicht async — das new Promise() wurde erstellt aber nie awaited. Das Rate-Limit gab also gar nicht. Fix: async/await ergä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=csv an 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 aus placement-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_bestaetigung war seit PROJ-91 vorhanden — nur der Toggle fehlte.
  • Tisch-Status-Panel: Badge-Grid nach dem Gruppenfortschrittsblock. Da kreisrangliste_matches keine 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 auf offen. Match-Reset-API-Erweiterung: PATCH mit status: "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_id wird 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-Formel
  • calculateDelta(ttrA, ttrB, won, kFaktor?) → ΔTTR als ganze Zahl (K=16 Standard)
  • calculateRunningTotal(matches[]) → kumulierter Tagessaldo
  • computeMatchDelta(...) → 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:

  1. matches (Turnier-Matches, bestehend)
  2. kreisrangliste_matches via venue_table_id (neu)
  3. race_matches via table_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 Screenshots
  • task_loader.py — YAML-Frontmatter-Parser für Markdown-Aufgabendateien
  • generate_story_report.py + templates/story_report.html — Jinja2-HTML-Report mit base64-eingebetteten Screenshots, Traceability-Tabelle, AI-Einschätzung und GO/NO-GO-Badge
  • runner.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:

  1. Anzeige-API (/api/venue-pools/[poolId]/tables): filterte KRL-Matches auf status = "offen". Sobald ein Match einem Tisch zugewiesen wird und auf spielbereit wechselt, verschwand es aus der Ansicht.
  2. Konflikt-Check (/api/.../table-assign): dieselbe falsche Bedingung. Zwei KRL-Matches in spielbereit konnten 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/kreisranglisten und /admin/races — absolut positioniert, stopPropagation verhindert Navigation
  • Optimistisches Entfernen aus der Liste nach erfolgreichem DELETE + Toast-Feedback

QA fand zwei Bugs:

  1. loadAuthorisedRace hatte keinen Superadmin-Bypass — hard-kodiertes .eq("tenant_id", tenantId) ohne isSuperAdmin-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).
  2. race_series_id fehlte im RaceListItem-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: localStorage merkt 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) und in_progress (Tablet hat Match geöffnet)
  • Tisch-Zuweisung: PATCH .../table-assign setzt Match automatisch auf spielbereit. Doppelbelegung desselben Tisches → 409 CONFLICT.
  • Tablet-GET triggert in_progress: Wenn das Tablet GET .../tablet aufruft (Match wird auf dem Tablet geöffnet), wechselt der Status auf in_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 mit spielbereit oder in_progress blieben unsichtbar
  • BUG-101-D: Pool-Dashboard färbte in_progress-Matches nicht orange
  • BUG-101-E: API-Fehlermeldungen enthielten tenantId und 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.

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:

  1. venue-tables API findet keine Standalone-PoolsGET /api/admin/venue-tables fand Pools nur wenn ein Turnier verknüpft war. KRL-only Pools (direkt angelegt) wurden ignoriert. Fix: direkter Pool-Lookup via (tenant_id, location, date).

  2. Tisch-Dropdown rot für belegte KRL-Tische — Die Belegungsprüfung in GET /api/admin/venue-tables schaute nur in matches (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_id fehlte 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_matches Query 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

Letzte Aktualisierung: 3. Juni 2026 — Tag 74 des Projekts. ~988 Commits, 107 Features deployed, 180+ API-Routen, 68 Unit-Tests + 25 Python-Tests, 181+ UAT-Tests, 3 Produktions-Umgebungen. ~976 Commits, 107 Features deployed, 175+ API-Routen, 68 Unit-Tests + 25 Python-Tests, 181+ UAT-Tests, 3 Produktions-Umgebungen.