# 🎨 [Frontend Expert] — Schritt-für-Schritt Roadmap > **Stand:** 3. April 2026 > **Rolle:** KMP, Compose Desktop, State-Management, MVVM/UDF, Backend-Anbindung --- ## 🔴 Sprint A — Sofort (diese Woche) - [x] **A-1** | ViewModel-Architektur definieren und Referenz-Implementierung umsetzen - [x] MVVM mit UDF (Unidirectional Data Flow) als verbindliches Muster festlegen - [x] `Intent`- und `State`-Klassen-Struktur definieren (Vorlage für alle anderen ViewModels) - [x] `VeranstalterViewModel` als vollständige Referenz-Implementierung umsetzen - [x] `State`-Klasse definieren - [x] `Intent`-Klasse (Sealed Class) definieren - [x] Business-Logik aus Composables herausziehen (keine `StoreV2`-Aufrufe mehr direkt in `onSaved`) - [x] Lokalen `remember`-State durch ViewModel-State ersetzen - [x] Ergebnis als Muster-Dokument in `docs/06_Frontend/` ablegen Referenzen: - docs/06_Frontend/MVVM_UDF_Pattern.md (Regeln, Vorlage, Referenz-Code) - frontend/features/veranstalter-feature/src/commonMain/.../VeranstalterViewModel.kt - frontend/features/veranstalter-feature/src/jvmMain/kotlin/at/mocode/frontend/features/veranstalter/data/remote/DefaultVeranstalterRepository.kt - frontend/features/veranstalter-feature/src/jvmMain/.../VeranstalterAuswahlScreen.kt (nutzt ViewModel/Intents) - [x] **A-2** | Abteilungs-Logik im Bewerb-Dialog berücksichtigen - [x] Dialog enthält Abteilungs-Auswahl als Teil des „Bewerb anlegen“-Flows (im selben Modal) - [x] CSN-C-NEU: Automatischer Vorschlag der Pflicht-Teilung mit 4 Abteilungen: - [x] Ohne Lizenz · R1 - [x] Ohne Lizenz · R2+ - [x] Mit Lizenz · R1 - [x] Mit Lizenz · R2+ - [x] Beim Auto-Vorschlag Default-Setzung des Abteilungs-Typs auf `SEPARATE_SIEGEREHRUNG` - [x] Manuelle Umschaltung des Abteilungs-Typs möglich: `SEPARATE_SIEGEREHRUNG` oder `ORGANISATORISCH` - [x] UX: Bei erkanntem Typ „CSN-C-NEU“ wird ein AssistChip „Pflicht-Teilung vorgeschlagen“ angezeigt Akzeptanzkriterien: - [x] Der „Bewerb anlegen“-Dialog zeigt ein Eingabefeld „Bewerbs-Typ“ und eine Auswahl für den Abteilungs-Typ (zwei Chips) - [x] Bei Eingabe „CSN-C-NEU“ wird automatisch die oben definierte 4er-Teilung in der Abteilungs-Liste angezeigt - [x] Die Auto-Teilung kann angezeigt werden, ohne dass der Dialog neu geöffnet werden muss (Live-Reaktion auf Eingabe) - [x] Der gesetzte Abteilungs-Typ ist im State sichtbar und wird vom Dialog korrekt reflektiert - [x] Kein Vorschlag für andere Typen; Liste bleibt leer bis manuell hinzugefügt/implementiert (aktuell out-of-scope) Referenzen (konkret): - frontend/features/turnier-feature/src/commonMain/kotlin/at/mocode/turnier/feature/presentation/BewerbAnlegenViewModel.kt - `BewerbAnlegenState`, `BewerbAnlegenIntent`, `applySuggestion()` (Auto-Vorschlag + Default-AbteilungsTyp) - frontend/features/turnier-feature/src/jvmMain/kotlin/at/mocode/turnier/feature/presentation/TurnierBewerbeTab.kt - `BewerbAnlegenDialog(...)`: Eingabe „Bewerbs-Typ“, AssistChip, Auswahl Abteilungs-Typ, Anzeige der vorgeschlagenen Abteilungen --- ## 🟠 Sprint B — Kurzfristig (nächste Woche) - [x] **B-1** | ViewModels für alle V3-Screens umsetzen - [x] `TurnierViewModel` - [x] `BewerbViewModel` (inkl. Abteilungs-Logik via Dialog-VM) - [x] `PferdProfilViewModel` - [x] `ReiterProfilViewModel` - [x] `VereinsViewModel` - [x] `FunktionaerViewModel` - [x] `AbteilungViewModel` (Startliste, Ergebnisse) - [ ] **B-2** | Ktor-Clients und Repositories für Backend-Anbindung vorbereiten (V3-ready) - [x] KMP-Ktor-Client zentral konfigurieren (BaseURL, Auth, Timeout, JSON, Logging) - [x] BaseURL per `PlatformConfig.resolveApiBaseUrl()` (SSoT; JS: `globalThis.API_BASE_URL`/`window.location.origin`, JVM: `.env`/Systemprop) → frontend/core/network - [x] Auth: Bearer Token über Interceptor; Token-Quelle: core/auth (`AuthApiClient`) bzw. Session-Store → Header `Authorization: Bearer ` - [x] Timeouts: connect = 5s, request = 15s, socket = 30s (prod); dev je 2× höher; Retry-Policy max 2 Versuche bei 5xx/Network - [x] JSON: `kotlinx.serialization` mit `ignoreUnknownKeys=true`, `explicitNulls=false`, `coerceInputValues=true` - [x] Logging: `LogLevel.HEADERS` in dev, `LogLevel.NONE` in prod; PII nie loggen - [x] Engines: JVM=CIO, JS=fetch (ktor-client-js), WASM=js (vorbereitet) - [ ] Repository-Schnittstellen je Domäne definieren (Mock ↔ Real austauschbar) - [ ] Pakete/Orte (commonMain): - [x] `at.mocode.frontend.features.veranstalter.domain.VeranstalterRepository` - [x] `at.mocode.turnier.feature.domain.TurnierRepository` - [ ] `at.mocode.turnier.feature.domain.BewerbRepository` - [ ] `at.mocode.turnier.feature.domain.AbteilungRepository` - [ ] Operationen V3-Minimum: `list`, `getById`, `create`, `update`, `delete` (suspend) - [ ] Rückgabetypen: Domain-Modelle (nicht DTOs); Fehler als `Either` oder `Result` (einheitlich festlegen) - [ ] HTTP-Clients + DTOs + Mapper (jvmMain/jsMain) - [x] DTOs pro Feature in `.../data/remote/dto` mit `@Serializable` (Veranstalter) - [x] Mapper: `Dto ↔ Domain` in `.../data/mapper` (reine Funktionen) (Veranstalter) - [x] Client-Implementierungen in `.../data/remote/Default*Repository` mit Ktor (Veranstalter) - [x] Fehlerbehandlung: Mapping HTTP 401→`AuthError.Expired`, 403→`AuthError.Forbidden`, 404→`NotFound`, 409→`Conflict`, 5xx→`ServerError` - [ ] Koin-DI-Module - [x] `core/network`: `HttpClient`-Factory als `single { provideHttpClient(env) }` - [ ] Feature-Module binden `Repository`-Interfaces auf Default-Impl - [ ] `AuthApiClient` (core/auth) integrieren, Token-Provider injizierbar (z. B. `() -> String?`) - [ ] Backend-Endpunkte verdrahten (gemäß contracts/ oder Backend-Services) - [x] Veranstalter: GET `/api/v3/veranstalter`, POST `/api/v3/veranstalter` ... - [ ] Turniere: GET `/api/v3/turniere`, ... - [ ] Bewerbe: GET `/api/v3/turniere/{id}/bewerbe`, ... - [ ] Abteilungen: GET `/api/v3/bewerbe/{id}/abteilungen`, ... - [ ] Versionierung: Präfix `/api/v3` zentral in `ApiRoutes` - [ ] Migration: `StoreV2` schrittweise ablösen - [ ] ViewModels von `StoreV2` auf Repositories umschalten (Feature für Feature) - [ ] Parallelbetrieb per Toggle: `useRealBackend=true/false` (Konfig/DI) - [ ] Entfernen von `StoreV2`, sobald Feature vollständig migriert und stabil - [ ] Qualität & DX - [ ] Akzeptanztests per Fake-Server (Mock Engine) gegen Repos (happy + error paths) - [ ] Network-Error-UX: Einheitliche Fehlermeldungen/Retry in ViewModels (UDF) - [ ] Dokumentation in `docs/06_Frontend/Networking.md` (Beispiele, Guidelines) Referenzen (bestehend): - frontend/core/network/src/commonMain/.../PlatformConfig.kt (expect) und js/jvm actuals - frontend/core/auth/src/commonMain/.../AuthApiClient.kt (Keycloak/PKCE, Token-Erhalt) - frontend/core/network/build.gradle.kts (Ktor- und Engine-Dependencies) - frontend/core/network/src/commonMain/.../NetworkModule.kt (HttpClient-Setup, Retry/Timeout, Token-Inject) - frontend/features/veranstalter-feature/src/jvmMain/kotlin/at/mocode/frontend/features/veranstalter/data/remote/DefaultVeranstalterRepository.kt Akzeptanzkriterien (B-2 abgeschlossen): - [x] `HttpClient`-Factory vorhanden, konfiguriert und via Koin injizierbar - [x] Repository-Interfaces existieren in commonMain, mit Domain-Typen und suspend-APIs (Veranstalter, Turnier vorbereitet) - [x] Mindestens `VeranstalterRepository` nutzt echten Backend-Client und liefert Daten - [x] Fehler werden einheitlich modelliert und bis ins ViewModel propagiert - [x] Ein Feature-ViewModel (z. B. Veranstalter) läuft ohne `StoreV2` - [ ] **B-3** | Validierungs-Live-Feedback in Edit-Dialogen - [ ] Spezifikation von 📜 Rulebook Expert (Sprint A-5) als Basis nutzen - [ ] OEPS-Nummer: Inline-Validierung beim Tippen - [ ] FEI-ID: Inline-Validierung beim Tippen - [ ] Lizenzklasse × Bewerbs-Klasse: Warnung wenn nicht erlaubt - [ ] Altersklasse Pferd: Warnung wenn nicht kompatibel - [ ] **B-4** | Kassa-Screen: Veranstaltungs-Kassa implementieren - [ ] Gesamt-Saldo-Ansicht (Salden aus allen Turnieren der Veranstaltung) - [ ] Turnier-übergreifender Zahlvorgang (eine Zahlung, mehrere Rechnungen) - [ ] Rechnungsvorschau je Turnier --- ## 🟡 Sprint C — Mittelfristig (in 2 Wochen) - [ ] **C-1** | Mock-Store (`StoreV2`) vollständig ablösen - [ ] Alle verbleibenden `StoreV2`-Referenzen durch echte Repositories ersetzen - [ ] `StoreV2` nach vollständiger Ablösung entfernen oder als `@Deprecated` markieren - [ ] **C-2** | LAN-Sync-UI vorbereiten (nach ADR von Architect) - [ ] Verbindungsstatus-Anzeige (Online/Offline/LAN) - [ ] Sync-Trigger manuell und automatisch > ⏸️ **USB-Stick Fallback (Export/Import UI)** — Separate Besprechung zu einem späteren Zeitpunkt --- ## 📌 Abhängigkeiten | Warte auf | Von wem | |----------------------------------|--------------------| | Domänen-Modell final (Abteilung) | 🏗️ Architect | | CRUD-Endpunkte | 👷 Backend | | Validierungs-Spezifikation | 📜 Rulebook Expert | | Wireframes Edit-Formulare | 🖌️ UI/UX Designer | | Meine Aufgabe | Ermöglicht wem | |--------------------------|-----------------------------------------------| | ViewModel-Referenz (A-1) | Alle anderen ViewModels folgen diesem Muster | | Ktor-Repositories (B-2) | Ablösung von StoreV2, echte Daten im Frontend |