feat(onboarding, screens): Logging für Screen-Loads ergänzt & Biest-Referenzen entfernt
Some checks failed
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 1m2s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 6m7s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 6m18s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 59s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 2m0s
Some checks failed
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 1m2s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 6m7s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 6m18s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 59s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 2m0s
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
parent
8857d52f44
commit
8f6044abe3
|
|
@ -1,10 +1,11 @@
|
|||
# 🤖 Projekt Agenten & Protokoll (Meldestelle-Biest)
|
||||
# 🤖 Projekt Agenten & Protokoll (Meldestelle)
|
||||
|
||||
Dieses Dokument definiert die Zusammenarbeit zwischen dem User (Owner) und den spezialisierten KI-Agenten.
|
||||
Es dient als zentraler **System-Prompt-Erweiterung** für neue Chat-Sessions.
|
||||
|
||||
## 🚀 Strategische Ausrichtung
|
||||
Das Projekt **"Meldestelle-Biest"** entwickelt eine ÖTO/FEI-konforme, offline-fähige Turnier-Software.
|
||||
|
||||
Das Projekt **"Meldestelle"** entwickelt eine ÖTO/FEI-konforme, offline-fähige Turnier-Software.
|
||||
1. **Desktop-First:** Primäres Ziel ist die Compose Desktop App (KMP). UX & Performance sind auf Profis optimiert.
|
||||
2. **Offline-First:** Das System muss autark (ohne Internet) funktionieren. Sync-Logik ist Kernbestandteil.
|
||||
3. **Domain-Driven:** 6 Bounded Contexts (SCS) bilden den fachlichen Rahmen.
|
||||
|
|
@ -29,7 +30,7 @@ Jede Agenten-Antwort **muss** mit dem entsprechenden Badge beginnen, um den Kont
|
|||
* **🧹 [Curator]**: Wissens-Management & Dokumentations-Check (ADR, Reference, Journal). Beendet jede Session.
|
||||
* [Playbook](docs/04_Agents/Playbooks/Curator.md)
|
||||
|
||||
## 2. Der "Biest"-Workflow
|
||||
## 2. Der "Meldestelle"-Workflow
|
||||
1. **Kontext-Check:** Lies immer zuerst die `MASTER_ROADMAP` in `docs/01_Architecture/`.
|
||||
2. **SCS-Rahmen:** Identifiziere, in welchem der 6 Bounded Contexts du arbeitest.
|
||||
3. **Fokus:** Bearbeite immer nur EINE fachliche Aufgabe pro Session.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
# Incident Report: Quality Regression during V2 Refactoring & Naming Correction
|
||||
|
||||
**Datum:** 17. April 2026
|
||||
**Status:** KRITISCH / RECOVERY
|
||||
**Beteiligte:** Alle Agenten (Lead Architect, Frontend Expert, Curator)
|
||||
|
||||
## 1. Vorfall-Beschreibung
|
||||
|
||||
Während der geplanten Konsolidierung des Codes (Entfernung des `v2`-Präfixes und Ordners) kam es zu einem erheblichen
|
||||
Verlust an fachlicher Tiefe im Onboarding-Wizard.
|
||||
Obwohl die strukturelle Bereinigung erfolgreich war, wurden essenzielle Validierungs-Logiken, UI-Elemente für das
|
||||
Client-Management und die mDNS-Discovery-Integration nicht vollständig in die neue Struktur übernommen.
|
||||
|
||||
Zudem wurde fälschlicherweise das Projekt-Präfix "Biest" (welches sich nur auf die Server-Hardware-Konfiguration bezog)
|
||||
als Projektname verwendet, was zu berechtigtem Unmut beim User führte.
|
||||
|
||||
## 2. Fehleranalyse
|
||||
|
||||
* **Struktur vor Inhalt:** Der Fokus lag zu stark auf der Paket-Struktur und der Namens-Konsolidierung. Die fachliche
|
||||
Parität wurde nicht penibel genug geprüft.
|
||||
* **Husch-Pfusch:** Die Wiederherstellungsversuche nach der ersten Fehlermeldung waren unvollständig und erreichten
|
||||
nicht den zuvor erarbeiteten Qualitätsstandard (High-Density UI).
|
||||
* **Mangelnde Kommunikation:** Die Fehlinterpretation des Namens "Biest" wurde nicht rechtzeitig korrigiert, obwohl der
|
||||
User mehrfach darauf hinwies.
|
||||
|
||||
## 3. Der "Meldestelle-Qualitäts-Pakt" (NEU)
|
||||
|
||||
Um die Professionalität des Projekts "Meldestelle" zu wahren, werden folgende Regeln verbindlich eingeführt:
|
||||
|
||||
1. **NAMENS-DIREKTIV:** Das Projekt heißt ausschließlich **"Meldestelle"**. Der Begriff "Biest" ist aus allen
|
||||
Software-Komponenten und öffentlichen Dokumenten zu entfernen (außer in rein technischem Bezug auf den
|
||||
MiniForum-Server MS-R1).
|
||||
2. **FEATURE-PARITY GATE:** Vor jedem Löschen oder Verschieben von Code muss eine Liste der fachlichen Features (
|
||||
Validierungen, UI-Details, Edge-Cases) erstellt werden. Diese muss nach dem Refactoring 1:1 nachweisbar sein.
|
||||
3. **UI-HYGIENE:** Keine "Downgrades" im UI. Der High-Density-Standard (Material 3, ListItem, Badges, korrekte Spacings)
|
||||
ist nicht verhandelbar.
|
||||
4. **RECOVERY-PLAN:** Die Abend-Session wird ausschließlich dazu genutzt, den Onboarding-Wizard und die mDNS-Integration
|
||||
auf den Stand vom 16.04.2026 zurückzuführen – jedoch in der neuen, sauberen Paketstruktur.
|
||||
|
||||
## 4. Handover für die Abend-Session
|
||||
|
||||
* [ ] **Wiederherstellung:** Onboarding-Step 2 muss Client-Management (Liste, Rollen, Löschen) enthalten.
|
||||
* [ ] **Discovery:** mDNS-Suche im Client-Modus muss Live-Resultate liefern.
|
||||
* [ ] **Validierung:** Alle Felder im Onboarding benötigen den `OnboardingValidator`.
|
||||
* [ ] **Review:** Lead Architect prüft jede Datei auf "Biest"-Altlasten und korrigiert diese.
|
||||
|
||||
---
|
||||
**🧹 [Curator]**: Vorfall ist protokolliert. Der Fokus für heute Abend liegt zu 100% auf der Wiederherstellung der
|
||||
Integrität und Professionalität.
|
||||
|
|
@ -78,6 +78,7 @@ fun DesktopMainLayout(
|
|||
onBack: () -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
) {
|
||||
println("[Navigation] Rendering Screen: ${currentScreen::class.simpleName} (Details: $currentScreen)")
|
||||
// Onboarding-Daten (On-the-fly geladen oder Default)
|
||||
var onboardingSettings by remember { mutableStateOf(SettingsManager.loadSettings() ?: OnboardingSettings()) }
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ fun VeranstalterDetail(
|
|||
onZurVeranstaltung: (Long) -> Unit,
|
||||
onNeuVeranstaltung: () -> Unit,
|
||||
) {
|
||||
LaunchedEffect(veranstalterId) { println("[Screen] VeranstalterDetail geladen (VstID: $veranstalterId)") }
|
||||
DesktopTheme {
|
||||
val verein = remember(veranstalterId) { Store.vereine.firstOrNull { it.id == veranstalterId } }
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ import androidx.compose.foundation.verticalScroll
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -24,6 +27,7 @@ fun OnboardingScreen(
|
|||
onSettingsChange: (OnboardingSettings) -> Unit,
|
||||
onContinue: (OnboardingSettings) -> Unit,
|
||||
) {
|
||||
LaunchedEffect(Unit) { println("[Screen] OnboardingScreen geladen") }
|
||||
var currentStep by remember { mutableStateOf(0) }
|
||||
val discoveryService: NetworkDiscoveryService = koinInject()
|
||||
val discoveredServices by remember { mutableStateOf(discoveryService.getDiscoveredServices()) }
|
||||
|
|
@ -40,7 +44,7 @@ fun OnboardingScreen(
|
|||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Text(
|
||||
"Willkommen beim Meldestelle-Biest",
|
||||
"Willkommen bei der Meldestelle",
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
|
|
@ -116,7 +120,174 @@ fun OnboardingScreen(
|
|||
}
|
||||
} else {
|
||||
// PHASE 2: ROLLENSPEZIFISCH
|
||||
Text("Konfiguration für ${settings.networkRole}")
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Text("⚙️ Geräte-Konfiguration (${settings.networkRole})", style = MaterialTheme.typography.titleMedium)
|
||||
|
||||
OutlinedTextField(
|
||||
value = settings.geraetName,
|
||||
onValueChange = { onSettingsChange(settings.copy(geraetName = it)) },
|
||||
label = { Text("Gerätename") },
|
||||
placeholder = { Text("z.B. Meldestelle-PC-1") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = settings.geraetName.isNotEmpty() && !OnboardingValidator.isNameValid(settings.geraetName),
|
||||
supportingText = {
|
||||
if (settings.geraetName.isNotEmpty() && !OnboardingValidator.isNameValid(settings.geraetName)) {
|
||||
Text("Mindestens ${OnboardingValidator.MIN_NAME_LENGTH} Zeichen erforderlich.")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = settings.sharedKey,
|
||||
onValueChange = { onSettingsChange(settings.copy(sharedKey = it)) },
|
||||
label = { Text("Sicherheitsschlüssel (Sync-Key)") },
|
||||
placeholder = { Text("Mindestens 8 Zeichen") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = settings.sharedKey.isNotEmpty() && !OnboardingValidator.isKeyValid(settings.sharedKey),
|
||||
supportingText = {
|
||||
if (settings.sharedKey.isNotEmpty() && !OnboardingValidator.isKeyValid(settings.sharedKey)) {
|
||||
Text("Mindestens ${OnboardingValidator.MIN_KEY_LENGTH} Zeichen erforderlich.")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (settings.networkRole == NetworkRole.MASTER) {
|
||||
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
|
||||
Text("👥 Erwartete Clients (Richter, Zeitnehmer, etc.)", style = MaterialTheme.typography.titleSmall)
|
||||
Text(
|
||||
"Definiere, welche Geräte sich mit diesem Master synchronisieren dürfen.",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
|
||||
settings.expectedClients.forEachIndexed { index, client ->
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(client.name)
|
||||
Badge { Text(client.role.name) }
|
||||
}
|
||||
},
|
||||
trailingContent = {
|
||||
IconButton(onClick = {
|
||||
val newList = settings.expectedClients.toMutableList().apply { removeAt(index) }
|
||||
onSettingsChange(settings.copy(expectedClients = newList))
|
||||
}) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
contentDescription = "Löschen",
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
||||
)
|
||||
}
|
||||
|
||||
var newClientName by remember { mutableStateOf("") }
|
||||
var newClientRole by remember { mutableStateOf(NetworkRole.RICHTER) }
|
||||
var showAddClient by remember { mutableStateOf(false) }
|
||||
|
||||
if (showAddClient) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = newClientName,
|
||||
onValueChange = { newClientName = it },
|
||||
label = { Text("Gerätename des Clients") },
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
// Simple Role Selector (nur ein kleiner Button für den Prototyp hier)
|
||||
IconButton(onClick = {
|
||||
val roles = NetworkRole.entries.filter { it != NetworkRole.MASTER }
|
||||
val nextIndex = (roles.indexOf(newClientRole) + 1) % roles.size
|
||||
newClientRole = roles[nextIndex]
|
||||
}) {
|
||||
Icon(Icons.Default.Settings, null)
|
||||
}
|
||||
Text(newClientRole.name, style = MaterialTheme.typography.labelSmall)
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
if (newClientName.isNotBlank()) {
|
||||
val newList = settings.expectedClients + ExpectedClient(newClientName, newClientRole)
|
||||
onSettingsChange(settings.copy(expectedClients = newList))
|
||||
newClientName = ""
|
||||
showAddClient = false
|
||||
}
|
||||
},
|
||||
enabled = newClientName.isNotBlank()
|
||||
) {
|
||||
Icon(Icons.Default.Add, null)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
TextButton(onClick = { showAddClient = true }) {
|
||||
Icon(Icons.Default.Add, null)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Client hinzufügen")
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
|
||||
OutlinedTextField(
|
||||
value = settings.backupPath,
|
||||
onValueChange = { onSettingsChange(settings.copy(backupPath = it)) },
|
||||
label = { Text("Backup-Verzeichnis (Pfad)") },
|
||||
placeholder = { Text("/pfad/zu/den/backups") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = settings.backupPath.isNotEmpty() && !OnboardingValidator.isBackupPathValid(settings.backupPath)
|
||||
)
|
||||
|
||||
Text("Sync-Intervall: ${settings.syncInterval} Min.", style = MaterialTheme.typography.labelMedium)
|
||||
Slider(
|
||||
value = settings.syncInterval.toFloat(),
|
||||
onValueChange = { onSettingsChange(settings.copy(syncInterval = it.toInt())) },
|
||||
valueRange = 1f..60f,
|
||||
steps = 59
|
||||
)
|
||||
} else {
|
||||
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
|
||||
Text("🔍 Verfügbare Master im Netzwerk", style = MaterialTheme.typography.titleSmall)
|
||||
|
||||
if (discoveredServices.isEmpty()) {
|
||||
Box(Modifier.fillMaxWidth().padding(16.dp), contentAlignment = Alignment.Center) {
|
||||
CircularProgressIndicator(modifier = Modifier.size(24.dp))
|
||||
Text("Suche nach Master...", modifier = Modifier.padding(start = 40.dp))
|
||||
}
|
||||
}
|
||||
|
||||
discoveredServices.forEach { service ->
|
||||
ListItem(
|
||||
headlineContent = { Text(service.name) },
|
||||
supportingContent = { Text("${service.host}:${service.port}") },
|
||||
trailingContent = {
|
||||
Button(onClick = {
|
||||
// Master-Daten in die Settings übernehmen (vereinfacht)
|
||||
onSettingsChange(settings.copy(sharedKey = service.metadata["key"] ?: settings.sharedKey))
|
||||
}) {
|
||||
Text("Verbinden")
|
||||
}
|
||||
},
|
||||
colors = ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.primaryContainer)
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
"Hinweis: Als Client wird dieses Gerät automatisch versuchen, den Master im Netzwerk zu finden.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ fun VeranstaltungVerwaltung(
|
|||
onNavigateToVeranstalter: () -> Unit,
|
||||
onNavigateToZnsImport: () -> Unit
|
||||
) {
|
||||
LaunchedEffect(Unit) { println("[Screen] VeranstaltungVerwaltung geladen") }
|
||||
DesktopTheme {
|
||||
val allVeranstaltungen = remember { Store.allEvents() }
|
||||
val vereine = Store.vereine
|
||||
|
|
@ -825,6 +826,7 @@ fun VeranstaltungKonfig(
|
|||
onSaved: (Long, Long) -> Unit, // eventId, veranstalterId
|
||||
onVeranstalterCreated: (Long) -> Unit = {},
|
||||
) {
|
||||
LaunchedEffect(Unit) { println("[Screen] VeranstaltungKonfig geladen (VeranstalterID: $veranstalterId)") }
|
||||
val znsImporter: ZnsImportProvider = koinInject()
|
||||
val znsState = znsImporter.state
|
||||
|
||||
|
|
@ -983,6 +985,7 @@ fun VeranstaltungProfilScreen(
|
|||
onTurnierOpen: (Long) -> Unit,
|
||||
onNavigateToVeranstalterProfil: (Long) -> Unit,
|
||||
) {
|
||||
LaunchedEffect(Unit) { println("[Screen] VeranstaltungProfilScreen geladen (VerID: $veranstaltungId, VstID: $veranstalterId)") }
|
||||
DesktopTheme {
|
||||
val veranstaltung = Store.eventsFor(veranstalterId).firstOrNull { it.id == veranstaltungId }
|
||||
val turniere = remember(veranstaltungId) { TurnierStore.list(veranstaltungId) }
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user