docs: massive restructuring of documentation, development guides and agent playbooks

This commit is contained in:
2026-06-15 12:54:38 +02:00
parent e4988b4397
commit ce63303b2c
686 changed files with 45423 additions and 319 deletions
@@ -0,0 +1,170 @@
---
type: Frontend Guideline
status: APPROVED
owner: 🖌️ UI/UX Designer
reviewed_by: 🎨 Frontend Expert
last_update: 2026-04-03
related:
- docs/06_Frontend/Navigation_V3_Screen-Baum_und_Back-Stack.md
- docs/04_Agents/Roadmaps/UIUX_Roadmap.md
---
# Entscheidungsrichtlinie — Editier-Formulare
Ziel: Klare, implementierbare Regeln, wann wir ein Edit als AlertDialog, als dedizierten Fullscreen oder als Sliding Panel (Modal Bottom/Side Sheet) umsetzen. Grundlage: Navigation V3, HighDensity Desktop UX, OfflineFirst.
Leitsatz: Kontext bewahren, kognitive Last minimieren, sichere Eingaben (Undo/Cancel) bevorzugen.
---
## TL;DR — Entscheidungsmatrix
- AlertDialog (modal, klein)
- Felder: ≤ 3 (einfache Felder: Text, Toggle, Single Select)
- Keine abhängigen Validierungen/AsyncLaden
- Sofortige Aktion, geringe Tragweite, seltene Nutzung
- Dauer des Vorgangs < 20 s
- BackStack: kein Eintrag (Schließen kehrt zum UrsprungsScreen)
- Sliding Panel (Side/Bottom Sheet)
- Felder: 38, mittlere Komplexität, evtl. Sektionen
- Kontext muss sichtbar bleiben (Liste/Detail im Hintergrund relevant)
- Leichte Abhängigkeiten erlaubt (z. B. abhängige Selects), klare InlineValidierung
- Dauer 2060 s
- BackStack: kein Eintrag; Reopen über denselben Trigger
- Fullscreen Edit (eigener Screen)
- Felder: > 8 oder mehrere Sektionen/Steps
- Starke Abhängigkeiten, Validierungen, asynchrone Daten (z. B. Lookup Reiter/Pferd)
- Kritische Aktion mit größerer Tragweite (z. B. Stammdatenänderung)
- Benötigt volle TastaturFokusstrecke, Hotkeys, Review/Preview
- Dauer > 60 s
- BackStack: eigener Eintrag; Zurück führt sicher zurück, UnsavedChangesGuard aktiv
Hinweis: Auf Desktop bevorzugen wir Side Sheet (rechts) als „KontextErhalt bei mittlerer Komplexität“.
---
## Do/Dont (Auszug)
- Dont: Mehr als 3 Eingabefelder in AlertDialogs stapeln → führt zu Scrollen/Übersehen.
- Do: Kurze Bestätigungen/Umbenennen/Flag toggeln als Dialog lösen.
- Dont: AsyncSuchfelder im Dialog (z. B. ReiterLookup) → Side Sheet oder Fullscreen.
- Do: UnsavedChanges vor Navigation abfangen (ConfirmDialog, kein StackEintrag für Modal/Sheet; V3Regel).
---
## Beispiel A — Reiter bearbeiten (Side Sheet empfohlen)
Kontext: Edit von Stammdaten, typ. 47 Felder (Name, Lizenzklasse, Verein, Notizen, aktiv). Liste/Detail im Blick behalten (Suche, Auswahl vergleichen).
ASCIIWireframe (Desktop, Right Side Sheet ~420520 px):
```
┌──────────────────────────── AppContent (z. B. ReiterListe) ────────────────────────────┐┌───────────── Side Sheet ─────────────┐
│ Suche [__________] Filter [v] ▢ ✕ ││ Reiter bearbeiten ✕ │
│───────────────────────────────────────────────────────────────────────────────────────││──────────────────────────────────────│
│ ▸ Mayer, Anna | Lizenz B | Verein X ││ Name [ Anna Mayer ] │
│ ▸ König, Tom | Lizenz A | Verein Y ││ Lizenzklasse [ A v ] │
│ ▸ … ││ Verein [ Suche… 🔎 ] │
│ ││ Aktiv [ ✓ ] │
│ ││ Notizen [ ............. ] │
│ ││ │
│ ││ ← Abbrechen Speichern → │
└─────────────────────────────────────────────────────────────────────────────────────┘└──────────────────────────────────────┘
```
Interaktion:
- Öffnen: Button „Bearbeiten“ in Zeile/Karte → Side Sheet schiebt von rechts ein.
- Validierung inline; VereinLookup als SuchDropDown (debounced, OfflineCache).
- Schließen: ESC/✕/Abbrechen; bei Änderungen ConfirmDialog (UnsavedChangesGuard).
---
## Beispiel B — Pferd bearbeiten (Fullscreen empfohlen)
Kontext: Mehr Felder und Abhängigkeiten (Name, Jahrgang, Geschlecht, Rasse, Besitzer/Verein, RegistrierNrn., Notizen). Teilweise Lookups, ggf. Bild.
ASCIIWireframe (Dedicated Screen):
```
TopBar: ← Pferd bearbeiten [Abbrechen] [Speichern]
Breadcrumb: Reiter & Pferde > Pferd.Detail > Edit
┌─ MeldestelleCard: Stammdaten ───────────────────────────────────────────────────────┐
│ Name [ ....................... ] Jahrgang [ 20__ ] Geschlecht [v] │
│ Rasse [ ....................... ] Farbe [ ..... ] │
│ RegistrierNr. [ ....................... ] ChipNr. [ ..... ] │
└──────────────────────────────────────────────────────────────────────────────────────┘
┌─ MeldestelleCard: Zuordnung ────────────────────────────────────────────────────────┐
│ Besitzer [ Suche Person/Verein 🔎 ] Verein [ Suche Verein 🔎 ] │
│ Lizenzzuordnung [ None v ] (InfoBadge zu Regeln) │
└──────────────────────────────────────────────────────────────────────────────────────┘
┌─ Notizen ────────────────────────────────────────────────────────────────────────────┐
│ [ Mehrzeiliges Textfeld ………………………………………… ] │
└──────────────────────────────────────────────────────────────────────────────────────┘
```
Interaktion:
- Eigener Screen mit vollem TastaturSupport, FokusReihenfolge, Hotkeys (Ctrl+S speichert).
- UnsavedChangesGuard auf Back/Navi, Validierung vor Speichern, Fehler an Feldern.
---
## Komponenten & States (für Frontend)
- Reusable: `MeldestelleCard`, `SectionHeader`, `FormRow`, `PrimaryButton`, `SecondaryButton`, `StatusBadge`.
- Validation: Inline (per Field), Summary oben optional; Disabled Save wenn kritisch.
- Loading: PerField Spinner bei Lookups; Gesamtscreen nie blockieren.
- Accessibility: TabOrder, sichtbare FokusRinge, Labels neben/über Feldern (Dichte abhängig).
---
## Navigation & BackStack (V3Konformität)
- Dialog/Sheet: Kein eigener StackEintrag; Schließen führt zum UrsprungsScreen zurück.
- Fullscreen: Eigener StackEintrag. DeepLink baut synthetische Eltern auf (siehe V3).
- Vor Navigation prüfen: UnsavedChanges → ConfirmDialog.
---
## Entscheidung als Regel (pseudocode)
```
if fieldCount <= 3 and complexity == low and noAsyncLookups:
use AlertDialog
elif 3 < fieldCount <= 8 and complexity in {low, medium} and contextRelevant:
use SideSheet (preferred on Desktop)
else:
use FullscreenEdit
```
---
## ✅ Freigabe & Verbindlichkeit
| Kriterium | Entscheidung |
|--------------------|---------------------------------------------------------------------------------------------------------------------------------------|
| **Status** | **APPROVED** — verbindliche Design-Richtlinie ab 2026-04-03 |
| **Reviewed by** | 🎨 Frontend Expert (bestätigt durch Implementierung von `ReiterProfilEditDialog` und `PferdProfilEditDialog` gemäß Side-Sheet-Muster) |
| **Gültig für** | Alle Edit-Formulare im Meldestelle-Desktop-MVP |
| **Ausnahmen** | Müssen explizit im jeweiligen Feature-Ticket begründet und dokumentiert werden |
| **Nächste Review** | Bei Einführung der Web-App / PWA (andere Interaktionsparadigmen möglich) |
### Anwendung auf bestehende Screens (Mapping)
| Screen / Entity | Felder (ca.) | Async-Lookup | Empfehlung | Umgesetzt |
|-------------------|--------------|---------------|-------------|------------------------------------------------|
| Veranstalter Edit | 45 | Nein | Side Sheet | ✅ (Dialog) |
| Reiter Edit | 57 | Ja (Verein) | Side Sheet | ✅ |
| Pferd Edit | 810 | Ja (Besitzer) | Fullscreen | ✅ (Dialog — Migration zu Fullscreen empfohlen) |
| Verein Edit | 34 | Nein | Side Sheet | ⬜ offen |
| Funktionär Edit | 34 | Nein | Side Sheet | ⬜ offen |
| Bewerb Edit | 68 | Ja | Side Sheet | ⬜ offen |
| Turnier Edit | 45 | Nein | Side Sheet | ⬜ offen |
| Abteilung Edit | 34 | Nein | AlertDialog | ⬜ offen |
> **Hinweis Pferd:** Der bestehende `PferdProfilEditDialog` überschreitet mit 810 Feldern und Async-Lookups die
> Dialog-Grenze. Migration zu Fullscreen-Edit ist für Sprint C-1 vorgesehen.
@@ -0,0 +1,284 @@
---
type: Frontend Guideline
status: APPROVED
owner: 🖌️ UI/UX Designer
last_update: 2026-04-03
related:
- docs/06_Frontend/Guidelines/Editier-Formulare_Dialog-vs-Fullscreen_v1.md
- docs/04_Agents/Roadmaps/UIUX_Roadmap.md
- docs/04_Agents/Roadmaps/Frontend_Roadmap.md
---
# Empty States — Design-Spezifikation & Richtlinie
> **Ziel:** Konsistente, hilfreiche Leer-Zustände für alle Listenansichten im Meldestelle-Desktop.
> **Leitsatz:** Ein leerer Zustand ist kein Fehler — er ist eine Einladung zur ersten Aktion.
---
## 1. Warum Empty States wichtig sind
- **Erster Start / Onboarding:** Neue Nutzer sehen leere Listen — ohne Hinweis wirkt die App kaputt.
- **Nach Filterung:** Kein Treffer bei Suche/Filter muss klar kommuniziert werden (≠ Ladefehler).
- **Nach Löschung:** Letzte Entität gelöscht → leere Liste braucht Orientierung.
- **Vertrauen:** Klare Kommunikation verhindert Unsicherheit ("Ist das ein Bug?").
---
## 2. Empty-State-Typen
| Typ | Auslöser | Primäre Aktion |
|------------------|-------------------------------------------------|-----------------------------|
| **`EMPTY_LIST`** | Liste ist leer (noch keine Daten angelegt) | CTA: Ersten Eintrag anlegen |
| **`NO_RESULTS`** | Suche/Filter liefert keine Treffer | CTA: Filter zurücksetzen |
| **`ERROR`** | Ladefehler (Netzwerk, Backend nicht erreichbar) | CTA: Erneut versuchen |
| **`LOADING`** | Daten werden geladen (Skeleton/Spinner) | Kein CTA |
> `LOADING` wird durch `MsLoadingIndicator` abgedeckt — kein separater Empty State nötig.
---
## 3. Visuelle Anatomie — `MsEmptyState`
```
┌─────────────────────────────────────────────────────────┐
│ │
│ [ Icon / 48dp ] │
│ │
│ Titel (MaterialTheme.titleMedium) │
│ │
│ Beschreibung (MaterialTheme.bodyMedium, zentriert, │
│ max. 2 Zeilen, gedimmt: onSurface 60%) │
│ │
│ [ Primärer CTA-Button ] (optional) │
│ │
└─────────────────────────────────────────────────────────┘
```
### Maße & Abstände
| Element | Wert |
|-----------------------------|---------------------------------------------------|
| Icon-Größe | 48 dp |
| Icon-Farbe | `MaterialTheme.colorScheme.onSurface` @ 38% Alpha |
| Abstand Icon→Titel | 16 dp |
| Abstand Titel→Beschreibung | 8 dp |
| Abstand Beschreibung→Button | 24 dp |
| Max. Breite Textblock | 360 dp (zentriert) |
| Padding gesamt | 32 dp rundum |
### Typografie
| Element | Style |
|--------------|-------------------------------------------------------|
| Titel | `MaterialTheme.typography.titleMedium` |
| Beschreibung | `MaterialTheme.typography.bodyMedium`, Alpha 60% |
| Button | Standard `MsButton` (outlined für sekundäre Aktionen) |
---
## 4. Icon-Konzept
Wir verwenden **Material Symbols (Outlined)** — konsistent mit dem restlichen Design-System.
Kein Custom-Illustration-Set für MVP (zu aufwändig, zu wenig Mehrwert bei High-Density-Desktop).
| Screen / Kontext | Icon | Begründung |
|--------------------------|----------------------------------|-----------------------------|
| Veranstalter-Liste | `person_off` | Kein Veranstalter vorhanden |
| Veranstaltungs-Liste | `event_busy` | Keine Veranstaltung |
| Turnier-Liste | `emoji_events` (durchgestrichen) | Kein Turnier |
| Bewerb-Liste | `list_alt` | Keine Bewerbe |
| Nennungs-Liste | `assignment_ind` | Keine Nennungen |
| Reiter-Liste | `person_search` | Kein Reiter gefunden |
| Pferde-Liste | `search_off` | Kein Pferd gefunden |
| Verein-Liste | `group_off` | Kein Verein |
| Funktionär-Liste | `badge` | Kein Funktionär |
| Abteilungs-Startliste | `format_list_numbered` | Keine Starter |
| Abteilungs-Ergebnisliste | `leaderboard` | Keine Ergebnisse |
| Suche / Filter (allg.) | `search_off` | Kein Treffer |
| Fehler (allg.) | `cloud_off` | Verbindungsfehler |
---
## 5. Texte je Screen
### Allgemeine Regel
- **Titel:** Kurz, präzise, max. 5 Wörter. Keine Ausrufezeichen.
- **Beschreibung:** Erklärt den Zustand UND gibt Handlungshinweis. Max. 2 Sätze.
- **CTA:** Verb + Objekt. Klar und direkt.
### Texte je Kontext
#### Veranstalter
| Typ | Titel | Beschreibung | CTA |
|--------------|----------------------------|---------------------------------------------------------|----------------------|
| `EMPTY_LIST` | Noch kein Veranstalter | Lege den ersten Veranstalter an, um loszulegen. | Veranstalter anlegen |
| `NO_RESULTS` | Kein Veranstalter gefunden | Kein Eintrag passt zur Suche. Passe den Suchbegriff an. | Suche zurücksetzen |
| `ERROR` | Laden fehlgeschlagen | Veranstalter konnten nicht geladen werden. | Erneut versuchen |
#### Veranstaltungen
| Typ | Titel | Beschreibung | CTA |
|--------------|------------------------------|-----------------------------------------------------------|-----------------------|
| `EMPTY_LIST` | Noch keine Veranstaltung | Erstelle die erste Veranstaltung für diesen Veranstalter. | Veranstaltung anlegen |
| `NO_RESULTS` | Keine Veranstaltung gefunden | Kein Treffer für den gewählten Filter. | Filter zurücksetzen |
| `ERROR` | Laden fehlgeschlagen | Veranstaltungen konnten nicht geladen werden. | Erneut versuchen |
#### Turniere
| Typ | Titel | Beschreibung | CTA |
|--------------|-----------------------|-------------------------------------------------------|---------------------|
| `EMPTY_LIST` | Noch kein Turnier | Füge das erste Turnier zu dieser Veranstaltung hinzu. | Turnier anlegen |
| `NO_RESULTS` | Kein Turnier gefunden | Kein Turnier entspricht dem aktuellen Filter. | Filter zurücksetzen |
| `ERROR` | Laden fehlgeschlagen | Turniere konnten nicht geladen werden. | Erneut versuchen |
#### Bewerbe
| Typ | Titel | Beschreibung | CTA |
|--------------|----------------------|-----------------------------------------------|---------------------|
| `EMPTY_LIST` | Noch kein Bewerb | Lege den ersten Bewerb für dieses Turnier an. | Bewerb anlegen |
| `NO_RESULTS` | Kein Bewerb gefunden | Kein Bewerb entspricht dem aktuellen Filter. | Filter zurücksetzen |
| `ERROR` | Laden fehlgeschlagen | Bewerbe konnten nicht geladen werden. | Erneut versuchen |
#### Nennungen
| Typ | Titel | Beschreibung | CTA |
|--------------|------------------------|-----------------------------------------------------------|---------------------|
| `EMPTY_LIST` | Noch keine Nennung | Es wurden noch keine Nennungen für diesen Bewerb erfasst. | Nennung erfassen |
| `NO_RESULTS` | Keine Nennung gefunden | Kein Treffer für den gewählten Filter oder die Suche. | Filter zurücksetzen |
| `ERROR` | Laden fehlgeschlagen | Nennungen konnten nicht geladen werden. | Erneut versuchen |
#### Reiter
| Typ | Titel | Beschreibung | CTA |
|--------------|----------------------|-------------------------------------------------------|--------------------|
| `EMPTY_LIST` | Noch kein Reiter | Importiere Stammdaten oder lege den ersten Reiter an. | Reiter anlegen |
| `NO_RESULTS` | Kein Reiter gefunden | Kein Reiter entspricht dem Suchbegriff. | Suche zurücksetzen |
| `ERROR` | Laden fehlgeschlagen | Reiter konnten nicht geladen werden. | Erneut versuchen |
#### Pferde
| Typ | Titel | Beschreibung | CTA |
|--------------|----------------------|-----------------------------------------------------|--------------------|
| `EMPTY_LIST` | Noch kein Pferd | Importiere Stammdaten oder lege das erste Pferd an. | Pferd anlegen |
| `NO_RESULTS` | Kein Pferd gefunden | Kein Pferd entspricht dem Suchbegriff. | Suche zurücksetzen |
| `ERROR` | Laden fehlgeschlagen | Pferde konnten nicht geladen werden. | Erneut versuchen |
#### Vereine
| Typ | Titel | Beschreibung | CTA |
|--------------|----------------------|-----------------------------------------|--------------------|
| `EMPTY_LIST` | Noch kein Verein | Lege den ersten Verein an. | Verein anlegen |
| `NO_RESULTS` | Kein Verein gefunden | Kein Verein entspricht dem Suchbegriff. | Suche zurücksetzen |
| `ERROR` | Laden fehlgeschlagen | Vereine konnten nicht geladen werden. | Erneut versuchen |
#### Funktionäre
| Typ | Titel | Beschreibung | CTA |
|--------------|--------------------------|---------------------------------------------|--------------------|
| `EMPTY_LIST` | Noch kein Funktionär | Lege den ersten Funktionär an. | Funktionär anlegen |
| `NO_RESULTS` | Kein Funktionär gefunden | Kein Funktionär entspricht dem Suchbegriff. | Suche zurücksetzen |
| `ERROR` | Laden fehlgeschlagen | Funktionäre konnten nicht geladen werden. | Erneut versuchen |
#### Abteilungs-Startliste
| Typ | Titel | Beschreibung | CTA |
|--------------|----------------------|-----------------------------------------------------------|------------------|
| `EMPTY_LIST` | Keine Starter | Für diese Abteilung wurden noch keine Starter zugewiesen. | Nennungen prüfen |
| `ERROR` | Laden fehlgeschlagen | Startliste konnte nicht geladen werden. | Erneut versuchen |
#### Abteilungs-Ergebnisliste
| Typ | Titel | Beschreibung | CTA |
|--------------|----------------------|-----------------------------------------------------------|------------------|
| `EMPTY_LIST` | Keine Ergebnisse | Für diese Abteilung wurden noch keine Ergebnisse erfasst. | — |
| `ERROR` | Laden fehlgeschlagen | Ergebnisliste konnte nicht geladen werden. | Erneut versuchen |
---
## 6. Composable-Spezifikation — `MsEmptyState`
Ablageort:
`frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsEmptyState.kt`
### API
```kotlin
/**
* Einheitlicher Empty-State für alle Listenansichten.
*
* @param icon Material-Symbol (ImageVector) passend zum Kontext.
* @param title Kurzer Titel (max. 5 Wörter).
* @param description Erklärung + Handlungshinweis (max. 2 Sätze). Optional.
* @param actionLabel Text des primären CTA-Buttons. Null = kein Button.
* @param onAction Callback für den CTA-Button.
* @param modifier Modifier für äußeres Layout.
*/
@Composable
fun MsEmptyState(
icon: ImageVector,
title: String,
description: String? = null,
actionLabel: String? = null,
onAction: (() -> Unit)? = null,
modifier: Modifier = Modifier
)
```
### Verhalten
- Zentriert (horizontal + vertikal) im verfügbaren Raum.
- Icon gedimmt (38% Alpha), Beschreibung gedimmt (60% Alpha).
- Button nur anzeigen wenn `actionLabel != null && onAction != null`.
- Button-Stil: `OutlinedButton` (sekundäre Aktion, nicht zu dominant).
- Bei `ERROR`-Typ: Icon `cloud_off`, Button als `FilledButton` (Retry ist primäre Aktion).
### Verwendungsbeispiel
```kotlin
// In einer LazyColumn / Column wenn items.isEmpty():
if (state.reiter.isEmpty() && !state.isLoading) {
MsEmptyState(
icon = Icons.Outlined.PersonSearch,
title = "Noch kein Reiter",
description = "Importiere Stammdaten oder lege den ersten Reiter an.",
actionLabel = "Reiter anlegen",
onAction = { onIntent(ReiterIntent.OpenCreateDialog) }
)
}
```
---
## 7. Implementierungs-Reihenfolge (Sprint C-1)
| Priorität | Screen | Typ(en) |
|-----------|--------------------------|----------------------------|
| 1 | Veranstalter-Auswahl | `EMPTY_LIST`, `NO_RESULTS` |
| 2 | Veranstaltungs-Übersicht | `EMPTY_LIST`, `NO_RESULTS` |
| 3 | Turnier-Bewerbe-Tab | `EMPTY_LIST` |
| 4 | Turnier-Nennungen-Tab | `EMPTY_LIST`, `NO_RESULTS` |
| 5 | Reiter-Liste | `EMPTY_LIST`, `NO_RESULTS` |
| 6 | Pferde-Liste | `EMPTY_LIST`, `NO_RESULTS` |
| 7 | Verein-Liste | `EMPTY_LIST`, `NO_RESULTS` |
| 8 | Funktionär-Liste | `EMPTY_LIST`, `NO_RESULTS` |
| 9 | Abteilungs-Startliste | `EMPTY_LIST` |
| 10 | Abteilungs-Ergebnisliste | `EMPTY_LIST` |
| Alle | Alle Listen | `ERROR` (einheitlich) |
> **Hinweis:** `ERROR`-State wird einmalig als Default-Variante in `MsEmptyState` implementiert
> und kann überall mit `type = EmptyStateType.ERROR` aufgerufen werden.
---
## 8. Abgrenzung zu anderen Komponenten
| Situation | Komponente |
|-------------------------------|-----------------------|
| Daten werden geladen | `MsLoadingIndicator` |
| Liste leer / kein Treffer | `MsEmptyState` |
| Kritischer Fehler (App-Ebene) | Eigener Error-Screen |
| Inline-Validierungsfehler | `MsValidationWrapper` |
| Status einer Entität | `MsStatusBadge` |