Fix 4 Bugs aus systematischem Tester-Durchgang (bugs.md)
Race Condition bei LAN-Port-Konflikt-Prüfung (Generation-Zähler), Firewall-Titel widersprach sich im Einfach-Modus, 2 fehlende EN-Übersetzungen, Health-Check- Herzschlag ignorierte laufende Wizard-/Experte-Schreibvorgänge. Außerdem M7/M9 im README/HANDOFF auf fertig aktualisiert (Modusschalter+Experte-Zweig liefen bereits über die Multi-LAN/WLAN-Live-Tests). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
# bugs.md
|
||||
|
||||
Ergebnis eines systematischen Tester-/Senior-Dev-Durchgangs (2026-09-17) gegen den echten
|
||||
hAP lite Testrouter (192.168.88.1, RouterOS 7.24.4). Hinweis zur Methode: die App-UI selbst
|
||||
konnte nicht geklickt werden (keine macOS-UI-Automatisierung verfügbar) — stattdessen
|
||||
Code-Audit jedes Feature-Bereichs, Router-Ground-Truth per SSH gegenprüft (`/export terse`,
|
||||
Live-Konfiguration), und wo möglich per Skript verifiziert statt geraten (siehe je Eintrag).
|
||||
Router-Zustand zum Testzeitpunkt: nahezu Werkszustand, nur die WLAN-Testkonfiguration aus
|
||||
einer früheren Session vorhanden.
|
||||
|
||||
**Status:** `offen` | `postponed` | `fixed`
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-17
|
||||
|
||||
### 1. Race Condition bei Port-Konflikt-Prüfung im LAN-Schritt
|
||||
**Status:** fixed (Build grün, noch nicht live gegenreproduziert)
|
||||
**Confidence:** hoch (Logikfehler direkt im Code nachvollzogen, nicht live reproduziert)
|
||||
|
||||
`SetupViewModel.checkPortConflict(for:)` (`SetupViewModel.swift:146`) liest `config.interfaceName`
|
||||
synchron beim Aufruf und startet dann einen `Task`, der das Ergebnis nach dem `await` unbedingt in
|
||||
`lanPortConflicts[configID]` schreibt — ohne Generation-Counter oder Abbruch des vorherigen `Task`.
|
||||
|
||||
Der Aufruf passiert an zwei Stellen für dieselbe `configID`: `.onAppear` (bei jedem Erscheinen des
|
||||
LAN-Schritts) und `.onChange(of: config.interfaceName)` (bei jeder Port-Auswahl). Wechselt der
|
||||
Nutzer den Port zügig zweimal hintereinander (oder wechselt ihn, bevor die `onAppear`-Prüfung des
|
||||
vorherigen Ports fertig ist), können zwei `Task`s parallel laufen. Der zuerst gestartete, aber
|
||||
zuletzt fertige Task überschreibt das Ergebnis des neueren mit dem Stand des alten Ports — "last
|
||||
response wins" statt "last request wins".
|
||||
|
||||
**Konkretes Fehlerbild:** Nutzer wählt Port A (hat Konflikt, z.B. Bridge-Mitgliedschaft), App prüft
|
||||
noch, Nutzer wechselt schnell zu Port B (frei). Kommt Port As Prüfergebnis später zurück als Port
|
||||
Bs, zeigt die App fälschlich eine Konflikt-Warnung für den inzwischen ausgewählten, tatsächlich
|
||||
freien Port B — oder, im umgekehrten Fall, verschluckt eine echte Warnung für einen Port, der
|
||||
tatsächlich bereits belegt ist, sodass "Weiter" freigeschaltet wird, obwohl der gewählte Port beim
|
||||
Anwenden unbemerkt vorhandene Konfiguration überschreibt.
|
||||
|
||||
**Fix:** Generation-Zähler `portConflictRequestGeneration: [LanDhcpConfig.ID: Int]` ergänzt, bei
|
||||
jedem `checkPortConflict(for:)`-Aufruf hochgezählt; ein abgeschlossener `Task` schreibt sein
|
||||
Ergebnis nur, wenn seine Generation beim Abschluss noch die aktuellste ist — ein überholter Task
|
||||
verwirft sein Ergebnis stillschweigend statt es zu übernehmen.
|
||||
|
||||
### 2. "Firewall (optional)"-Titel widerspricht sich selbst im Einfach-Modus
|
||||
**Status:** fixed (Build grün, noch nicht live gegenreproduziert)
|
||||
**Confidence:** hoch (direkt im Code sichtbar)
|
||||
|
||||
`FirewallStepView.swift:63`: `.navigationTitle(... "Firewall (optional)" ...)` ist fest, unabhängig
|
||||
vom Modus. Im Einfach-Modus zeigt derselbe Screen aber den Text "Firewall-Grundschutz ist im
|
||||
einfachen Modus immer aktiv." (Zeile 20) — der Titel behauptet "optional", der Inhalt sagt "immer
|
||||
an". Für Experte-Modus stimmt der Titel (dort gibt es den Toggle). Kleiner, aber sofort sichtbarer
|
||||
Text-Widerspruch für jeden, der im Einfach-Modus durch den Wizard geht.
|
||||
|
||||
**Fix:** Titel modusabhängig gemacht — `viewModel.mode == .expert ? "Firewall (optional)" : "Firewall"`, neuer Key `"Firewall"` in `L10n.swift` ergänzt.
|
||||
|
||||
### 3. Fehlende Englisch-Übersetzungen (sichtbar im EN-UI)
|
||||
**Status:** fixed (skriptgeprüft: beide Keys jetzt in `L10n.swift`, verbleibende zwei fehlende
|
||||
Keys "OK"/"Revision" bewusst nicht ergänzt — identisch in beiden Sprachen, kein sichtbarer Effekt)
|
||||
**Confidence:** hoch (skriptgeprüft: alle `L10n.t(...)`-Aufrufstellen gegen `L10n.swift`s
|
||||
Übersetzungs-Dictionary abgeglichen — `L10n.t` fällt bei fehlendem Key auf den deutschen
|
||||
Originaltext zurück, siehe `L10n.swift:12-15`)
|
||||
|
||||
Zwei Stellen zeigen im Sprachmodus Englisch weiterhin deutschen Text:
|
||||
- `Features/Devices/DevicesView.swift:335` — `"Netzwerk-Test fehlgeschlagen"` (Fehlermeldungstitel)
|
||||
- `Features/Overview/OverviewView.swift:350` — `"Fokus-Ansicht schließen"` (Tooltip auf dem
|
||||
Schließen-Button der Fokus-Ansicht)
|
||||
|
||||
(Ein dritter fehlender Key, `"Revision"`, ist praktisch nicht sichtbar, da das Wort in beiden
|
||||
Sprachen identisch ist — nicht extra gelistet.)
|
||||
|
||||
**Fix-Ansatz:** Beide Strings in `L10n.swift`s `translations`-Dictionary ergänzen (`"Netzwerk-Test
|
||||
fehlgeschlagen": "Network test failed"`, `"Fokus-Ansicht schließen": "Close focus view"`).
|
||||
|
||||
### 4. Health-Check-Heartbeat ignoriert laufende Wizard-/Experte-Schreibvorgänge
|
||||
**Status:** fixed (Build grün, noch nicht live gegenreproduziert)
|
||||
|
||||
Fix: `ConnectionService.beginWrite()`/`endWrite()` (Zähler `activeWriteCount`) ergänzt,
|
||||
`checkConnectionHealthAndReconnectIfNeeded()` prüft jetzt zusätzlich `activeWriteCount == 0`.
|
||||
`SetupViewModel.apply()` und `ExpertViewModel.saveEditingItem()`/`confirmRemoval()` klammern ihren
|
||||
Task-Body jetzt mit `beginWrite()`/`defer { endWrite() }`.
|
||||
**Confidence:** mittel (Logiklücke im Code nachvollzogen; nicht live reproduziert — REST läuft über
|
||||
unabhängige HTTP-Requests und dürfte robust sein, SSH-Exec-Kanäle sind laut SSH-Protokoll
|
||||
grundsätzlich nebenläufig nutzbar, siehe `SSHTransport.swift`/`RestTransport.swift` — insofern kein
|
||||
Daten-Korruptionsrisiko, aber eine echte Lücke in der Ablaufsteuerung)
|
||||
|
||||
`ConnectionService.checkConnectionHealthAndReconnectIfNeeded()` (`ConnectionService.swift:273`)
|
||||
läuft alle 10 Sekunden (`healthCheckInterval`) und prüft nur `!isReconnecting` — nicht, ob gerade
|
||||
ein `SetupViewModel.apply()` oder `ExpertViewModel.saveEditingItem()` in Arbeit ist. Der hAP-lite-
|
||||
Testrouter ist sehr schwach (MIPS 24Kc, 1 Kern, 650MHz, im Test bereits 54% CPU-Last im Leerlauf
|
||||
gemessen). Ein mehrere Sekunden dauernder Apply (z.B. Bonding, mehrere Firewall-Regeln) kann
|
||||
zeitlich mit dem Heartbeat kollidieren; schlägt der Heartbeat unter Last mit Timeout fehl, während
|
||||
der eigentliche Apply eigentlich noch normal durchläuft, zeigt die App fälschlich den
|
||||
"Wiederverbinden…"-Banner an, obwohl gar keine echte Verbindungsunterbrechung vorliegt — und
|
||||
`reconnectLoop` könnte im ungünstigsten Fall sogar `finishConnecting` mit einer neuen
|
||||
Transport-Instanz auslösen, während der ursprüngliche Apply noch auf der alten weiterläuft.
|
||||
|
||||
**Fix-Ansatz:** Einfachster Schutz: `SetupViewModel.isApplying` / `ExpertViewModel.isApplying` (oder
|
||||
ein neuer gemeinsamer "isWriting"-Zähler auf `ConnectionService`) zusätzlich zur Guard-Bedingung in
|
||||
`checkConnectionHealthAndReconnectIfNeeded()` prüfen, damit der Heartbeat während eines aktiven
|
||||
Schreibvorgangs aussetzt.
|
||||
|
||||
---
|
||||
|
||||
## Noch nicht geprüft / außerhalb dieses Durchgangs
|
||||
|
||||
- UI-Interaktion selbst (Klickpfade, Darstellung) — nicht automatisierbar, siehe Hinweis oben.
|
||||
- PPPoE-Client — laut Nutzer am Testrouter nicht testbar (hängt hinter einem weiteren
|
||||
konfigurierten Router, kein direkter ISP-Uplink). Kein neuer Fund, nur zur Vollständigkeit.
|
||||
- Restliche Tabs/Bereiche (Übersicht-Diagramm-Interaktion, Sicherungen, Mode-Taste, Settings) wurden
|
||||
im Code überflogen, ohne konkreten neuen Fund über die bereits in `found.md`/`HANDOFF.md`
|
||||
dokumentierten Punkte hinaus.
|
||||
Reference in New Issue
Block a user