diff --git a/docs/01_Architecture/MASTER_ROADMAP.md b/docs/01_Architecture/MASTER_ROADMAP.md index 3f2c0663..8de4ec30 100644 --- a/docs/01_Architecture/MASTER_ROADMAP.md +++ b/docs/01_Architecture/MASTER_ROADMAP.md @@ -2,12 +2,12 @@ type: Roadmap status: ACTIVE owner: Lead Architect -last_update: 2026-04-29 +last_update: 2026-04-30 --- # MASTER ROADMAP: Meldestelle -🏗️ **[Lead Architect]** | 29. April 2026 +🏗️ **[Lead Architect]** | 30. April 2026 **Strategisches Ziel:** Entwicklung einer ÖTO-konformen, offline-fähigen Turnier-Meldestelle als Compose Desktop App (KMP). @@ -17,7 +17,7 @@ Vollständige Self-Hosted Infrastruktur (Gitea, Pangolin, Zora). Datensouveräni - Desktop-App ist der primäre Client (Compose Desktop, KMP) — „Desktop-First“ gilt für UX und Architektur. - Offline-First Betrieb mit lokaler Persistenz und opportunistischer Synchronisation. -**Aktueller technischer Stand (30.03.2026):** +**Aktueller technischer Stand (30.04.2026):** * **Infrastruktur:** ✅ "Zora" (MS-R1, ARM64) ist live. Gitea & Registry laufen. * **Networking:** ✅ Pangolin Tunnel ersetzt Cloudflare. * **CI/CD:** ✅ Gitea Actions mit ARM64-Runner (VM 102) aktiv. Docker-Publish Pipeline grün. @@ -75,23 +75,19 @@ und über definierte Schnittstellen kommunizieren. Fokus: Physische Implementierung der Turnier-Hierarchie und technisches Onboarding. - // Meilenstein 0 wird auf "In Arbeit" zurückgesetzt, da Web-Shell Korrekturen nötig sind - // Meilenstein 0: Technische Geräte-Initialisierung (Prio 1) 🚧 IN ARBEIT (UI KORREKTUREN WEB) - // [x] App-Icons (PNG/ICO): Implementiert (Fix für Build-Fehler). - // [x] Docker-Fix: "services must be a mapping" behoben (dc-gui.yaml). - // [x] Chat-Funktion (Desktop): MVP implementiert (Navigation & UI). - // ... (Rest bleibt wie besprochen) +### MEILENSTEIN 0: Technische Geräte-Initialisierung (Prio 1) 🚧 IN ARBEIT (STABILISIERUNG) *Ziel: Ein stabiles, offline-fähiges technisches Fundament für die Desktop-App.* -* [x] **OS-Pfad-Auflösung:** Implementiert (Wartet auf Hardware-Test). -* [x] **Netzwerk-Interface-Binding:** Fix: Explizite IP-Bindung für JmDNS implementiert. -* [x] **Geführte Discovery ("Radar-Modus"):** Verbessert: UI mit Interface-Status-Indikatoren. -* [x] **Plan-USB Integration (UI):** Implementiert (Wartet auf Hardware-Test). -* [x] **Offline-Lizenzierung (Konzept):** Dokumentiert (ADR-0026). -* [x] **UX-Optimierung:** Implementiert (Wartet auf Hardware-Test). -* [x] **Plan-USB Implementierung:** Delta-Logik & AES-Export (Wartet auf Hardware-Test). -* [ ] **PoC Verifikation:** 🔴 OFFEN (Hardware-Test durch User erforderlich). +* [x] **App-Icons (PNG/ICO):** Implementiert (Fix für Build-Fehler). +* [x] **Docker-Fix:** "services must be a mapping" behoben (dc-gui.yaml). +* [x] **Chat-Funktion (Desktop):** MVP implementiert (Navigation & UI). +* [x] **Geführte Discovery ("Zero-Config"):** Master-Namen statt IPs, "Wait-State" für Clients. +* [x] **Native FileDialogs:** Stabile Pfad-Auswahl für Plan-USB auf allen Systemen. +* [x] **Handshake-Feedback:** Visuelle Signalisierung des Verbindungsstatus (Grün/Rot). +* [x] **Client-Konfiguration:** Master kann nun Clients in der UI hinzufügen und bearbeiten. +* [x] **Master-UX:** Konfiguration beim Start nicht mehr zwangsgesperrt. +* [ ] **PoC Verifikation:** 🔴 **FEHLGESCHLAGEN** (Hardware-Test durch User nicht erfolgreich - Analyse für Abend-Session erforderlich). ### MEILENSTEIN 1: Die Basis-Hierarchie (Prio 1) ⚪ GEPLANT diff --git a/docs/99_Journal/2026-04-30_Master-UX-Fix.md b/docs/99_Journal/2026-04-30_Master-UX-Fix.md new file mode 100644 index 00000000..2e11a79f --- /dev/null +++ b/docs/99_Journal/2026-04-30_Master-UX-Fix.md @@ -0,0 +1,18 @@ +🏗️ **[Curator Journal]** +Datum: 30. April 2026 + +# 🧹 Session-Abschluss: Master-UX & Client-Konfiguration + +## 🚀 Highlights +- **Master-Freiheit:** Die Konfiguration ist beim Start des Wizards nicht mehr zwangsgesperrt. Der Master kann nun alle Einstellungen (Name, Key, Interfaces) in Ruhe prüfen, bevor er finalisiert. +- **Client-Management:** Der Master kann nun "erwartete Clients" direkt in der UI hinzufügen, umbenennen und deren Rollen (Richter, Zeitnehmer, etc.) anpassen. +- **Dynamische Listen:** Fehler behoben, bei dem nach dem Löschen von Clients keine neuen mehr hinzugefügt werden konnten. + +## 🛠️ Technische Details +- **ViewModel-Fix:** `isLocked` im `DeviceInitializationViewModel` wird nun initial auf `false` gesetzt. +- **UI-Implementierung:** `DeviceInitializationConfig.jvm.kt` nutzt nun `MsTextField` und `FilterChip` innerhalb der Client-Liste für direkte Bearbeitung. +- **Rollen-Filter:** Der Master kann sich selbst nicht als "erwarteten Client" hinzufügen (Filter auf `NetworkRole.entries`). + +## 📅 Ausblick +- Abschluss von **Meilenstein 0** nach erfolgreichem Hardware-Test. +- Start von **Meilenstein 1 (Basis-Hierarchie & Persistenz)**. diff --git a/docs/99_Journal/2026-04-30_POC-Abschluss-Fehlgeschlagen.md b/docs/99_Journal/2026-04-30_POC-Abschluss-Fehlgeschlagen.md new file mode 100644 index 00000000..ea9910fd --- /dev/null +++ b/docs/99_Journal/2026-04-30_POC-Abschluss-Fehlgeschlagen.md @@ -0,0 +1,24 @@ +🏗️ **[Curator Journal]** +Datum: 30. April 2026 (Abschluss-Update) + +# 🧹 Session-Abschluss: POC-Status & Kritische Analyse + +## 🚀 Implementierte Änderungen (Zusammenfassung) +- **Zero-Config & UI-Fixes:** mDNS-Discovery mit sprechenden Namen, "Wait-State" für Clients und native Dateidialoge integriert. +- **Master-UX Optimierung:** Die Konfiguration im Wizard ist nun beim Start entsperrt; Master können erwartete Clients hinzufügen/bearbeiten. +- **Fehlerbehebung:** Kompilierfehler im `DeviceInitializationViewModel` (JVM/Common-Abstraktion) und Docker-Compose YAML-Struktur korrigiert. + +## 🔴 Aktueller Status: POC FEHLGESCHLAGEN +Trotz der technischen Umsetzungen meldet der User, dass der POC auf der Hardware weiterhin nicht funktioniert. +- **Feedback:** "Es funktioniert noch immer nicht!" +- **Konsequenz:** Die Session wird zur Dokumentation beendet. Eine tiefergehende Fehleranalyse (Netzwerk-Traces, Log-Inspektion) ist für die Abend-Session zwingend erforderlich. + +## 📋 Checkliste für die Abend-Session (Analyse-Fokus) +1. **mDNS Sichtbarkeit:** Warum finden sich Master und Client trotz "Zero-Config" nicht zuverlässig? (Mögliche Firewall-Themen oder Interface-Binding-Priorität). +2. **Handshake-Logik:** Verbleibt der Client im "Wait-State" oder schlägt der Verbindungsversuch aktiv fehl? +3. **UI-State Persistence:** Werden die Master-Einstellungen (Name, Key) korrekt für den mDNS-Broadcast übernommen? +4. **Log-Analyse:** Prüfung der App-Logs auf dem Zielsystem (falls verfügbar). + +## 📅 Nächste Schritte +- Start der Abend-Session mit Fokus auf **Debugging der Netzwerk-Discovery**. +- Verifikation der `init_device.aes` Erstellung bei manuellem Durchlauf des Masters. diff --git a/frontend/features/device-initialization/src/commonMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationViewModel.kt b/frontend/features/device-initialization/src/commonMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationViewModel.kt index 187386fd..0bfd3f71 100644 --- a/frontend/features/device-initialization/src/commonMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationViewModel.kt +++ b/frontend/features/device-initialization/src/commonMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationViewModel.kt @@ -35,7 +35,7 @@ class DeviceInitializationViewModel( _uiState.update { it.copy( settings = existingSettings, - isLocked = existingSettings.isConfigured + isLocked = false // Immer offen für Bearbeitung beim Start des Wizards ) } } @@ -133,10 +133,11 @@ class DeviceInitializationViewModel( _uiState.update { it.copy(showRoleChangeWarning = false, pendingRole = null) } } - fun addExpectedClient(name: String, role: NetworkRole) { - println("[DeviceInit] Erwarteter Client hinzugefügt: $name ($role)") + fun addExpectedClient() { + val name = "Neuer Client ${uiState.value.settings.expectedClients.size + 1}" + println("[DeviceInit] Erwarteter Client hinzugefügt: $name") updateSettings { - it.copy(expectedClients = it.expectedClients + ExpectedClient(name, role)) + it.copy(expectedClients = it.expectedClients + ExpectedClient(name, NetworkRole.RICHTER)) } } diff --git a/frontend/features/device-initialization/src/jvmMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationConfig.jvm.kt b/frontend/features/device-initialization/src/jvmMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationConfig.jvm.kt index 3f2f0687..9ced6d6c 100644 --- a/frontend/features/device-initialization/src/jvmMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationConfig.jvm.kt +++ b/frontend/features/device-initialization/src/jvmMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationConfig.jvm.kt @@ -236,7 +236,7 @@ actual fun DeviceInitializationConfig( HorizontalDivider(Modifier.padding(vertical = 8.dp)) Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { Text("👥 Erwartete Clients", style = MaterialTheme.typography.titleSmall) - TextButton(onClick = { /* Add Client Dialog */ }) { + TextButton(onClick = { viewModel.addExpectedClient() }) { Icon(Icons.Default.Add, null, Modifier.size(18.dp)) Spacer(Modifier.width(4.dp)) Text("Hinzufügen") @@ -245,15 +245,46 @@ actual fun DeviceInitializationConfig( settings.expectedClients.forEachIndexed { index, client -> ListItem( - headlineContent = { Text(client.name, fontWeight = FontWeight.Medium) }, - supportingContent = { Text(client.role.name, style = MaterialTheme.typography.labelSmall) }, + headlineContent = { + MsTextField( + value = client.name, + onValueChange = { newName -> + viewModel.updateSettings { s -> + val newList = s.expectedClients.toMutableList() + newList[index] = newList[index].copy(name = newName) + s.copy(expectedClients = newList) + } + }, + label = null, + compact = true, + modifier = Modifier.fillMaxWidth() + ) + }, + supportingContent = { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + NetworkRole.entries.filter { it != NetworkRole.MASTER }.forEach { role -> + val isSelected = client.role == role + FilterChip( + selected = isSelected, + onClick = { + viewModel.updateSettings { s -> + val newList = s.expectedClients.toMutableList() + newList[index] = newList[index].copy(role = role) + s.copy(expectedClients = newList) + } + }, + label = { Text(role.name, style = MaterialTheme.typography.labelSmall) } + ) + } + } + }, trailingContent = { IconButton(onClick = { viewModel.removeExpectedClient(index) }) { Icon(Icons.Default.Delete, null, tint = MaterialTheme.colorScheme.error, modifier = Modifier.size(20.dp)) } }, colors = ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)), - modifier = Modifier.padding(vertical = 2.dp) + modifier = Modifier.padding(vertical = 4.dp) ) } }