Files
RouterOS/HANDOFF.md
T
KayandClaude Sonnet 5 58c9ed5481 Bug 25/26: geleerte Felder blieben bestehen, Route-Bearbeiten scheiterte an nur-lesbarem Feld
Beide Bugs in ExpertViewModel.pendingCommand, betreffen Experte-Tab
direkt (nicht nur den neuen Übersicht-Bearbeiten-Weg):

- Bug 25: ein geleertes Textfeld (z.B. Kommentar löschen) wurde beim
  Speichern aus den Argumenten gefiltert statt explizit als "" gesendet
  — RouterOS' `set` ändert nur übergebene Parameter, ein weggelassener
  bleibt unangetastet statt geleert. Fix: Feld bleibt im Argument-Set,
  wenn es vorher einen Wert hatte; RouterOSCommand's SSH-Zeilen-Rendering
  gibt einen leeren Wert jetzt als `""` statt als nacktes `feld=` aus.
- Bug 26: eine Route bearbeiten (z.B. nur Kommentar ändern) scheiterte
  mit "bad parameter immediate-gw" — dieses von RouterOS mitgelieferte,
  nur lesbare/berechnete Feld landete unkuratiert in den freien
  "Weiteren Parametern" und wurde bei jedem Speichern blind
  mitgeschickt. Fix: ein unkuratiertes Feld wird nur noch gesendet, wenn
  sein Wert sich gegenüber dem ursprünglich geladenen Item tatsächlich
  geändert hat.

54 Unit-Tests grün (neue ExpertViewModelTests + eine Ergänzung in
RouterOSCommandBuilderTests).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YDmUd93KxsYGr2kLTotWnG
2026-09-15 13:05:02 +02:00

66 KiB
Raw Blame History

RouterOS Assistant — Handoff

Stand: App-Icon fertig, alle Kern-Features (M1M6) gebaut und mindestens teilweise gegen zwei echte physische Mikrotik-Testgeräte verifiziert (ein generisches Gerät für M1M4, ein hEX-Router für M5/M6 — dabei ein kritischer Parser-Bug gefunden und gefixt, siehe unten). Danach noch mehrere Nutzer-Wünsche umgesetzt: Tooltips, wählbarer Backup-Ordner, Schnell-Backup im Verbinden-Tab, "Werkseinstellungen wiederherstellen", eigenes App-Icon. Anschließend M9 (Einfach/Experte-Modusschalter im Wizard) und M10 (Experte-Tab: kuratiertes Formular für einen großen Teil von RouterOS plus generischer "eigener Menüpfad"-Zugriff für den Rest) — beide live gegen den hEX-Testrouter verifiziert, sieben Bugs dabei gefunden und gefixt (Bug 713). Alles bis hierhin committed (c9ecd3a).

Neu in dieser Session: drei weitere Tabs/Features. M11 "Übersicht" zeigt die komplette aktuelle Router-Konfiguration als Diagramm (Interfaces → IP-Adressen → DHCP/Pools → Routen → Firewall/NAT, mit echten, aus RouterOS' eigenen Referenzfeldern abgeleiteten Verbindungslinien, farblich nach Verbindungsart getrennt, Hover/Klick hebt Zusammenhänge hervor). M12 "Geräte" ist ein LAN-Scanner (DHCP-Leases + ARP + Bridge-Host-Tabelle, gruppiert als eine Tabelle pro physischem Port) mit Rechtsklick-Aktion "Feste IP zuweisen" (RouterOS' "Make Static", per /ip dhcp-server lease make-static) sowie ihr Gegenstück "Feste Zuweisung entfernen" (RouterOS kennt kein make-dynamic, nur remove — vorher per Recherche-Agent gegen die offizielle Doku geprüft statt geraten). M13 Backup-Wiederherstellung im Sicherungen-Tab: .rsc-Backup per SFTP hochladen, dann RouterOS' dokumentierter reset-configuration+run-after-reset-Restore-Weg, mit Modell-Abgleich vor dem Bestätigungsdialog (Brick-Schutz bei falschem Routermodell) und automatischem Login-Erhalt (siehe Bug 18). Alle drei Tabs/Features live gegen den hEX-Testrouter verifiziert, dabei M12 (vier Bugs, 1417) und M13 (ein besonders ernster Bug, 18: erster Testlauf sperrte den Router komplett aus, nur per Hardware-Reset behebbar) beide zäher als erwartet, am Ende aber jeweils vom Nutzer selbst bestätigt ("das funktioniert jetzt super" / "das hat sofort funktioniert" / "gut, das hat funktioniert").

Danach, gleiche Session, M14 "Update-Check": Verbinden-Tab zeigt jetzt volle Routerboard-Daten (Modell/Revision/Seriennummer/Firmware, aus /system routerboard) und einen RouterOS-Software-Update-Check (/system package update: Kanal/installierte/neueste Version/Status, "Jetzt prüfen", "Update installieren"), plus "Firmware aktualisieren" für die Routerboard-Bootloader-Firmware und einen "Jetzt neu starten"- Button danach — alle vier Aktionen live bestätigt, inkl. der zuvor unsicheren Frage, ob /system routerboard upgrades normalerweise interaktive Bestätigung den nicht-interaktiven SSH-Weg dieser App blockiert (tut es nicht, lief sauber durch). Dabei ein weiterer Bug im generischen Lese-Pfad gefunden (Bug 20, siehe unten). Anschließend ein Design-Durchgang: Verbinden-Detailseite/Sicherungen/Geräte liefen auf nackten Lists ohne Rahmen — umgestellt auf Form+.formStyle(.grouped), denselben nativen macOS-"Karten"-Look, den Wizard und Experte-Tab schon die ganze Zeit hatten. Dark Mode auf Nachfrage geprüft: funktioniert ohne Codeänderung (keine .preferredColorScheme-Überschreibung, keine feste Farbwerte, alles adaptive System-Farben) — vom Nutzer live bestätigt. Release-Build unter /Applications/RouterOS Assistant.app ist auf aktuellem Stand.

Danach, neue Session: M8 komplett live verifiziert — erst manuell, dann über den Wizard selbst. Bug 21 aus der Vorsession (WAN-DHCP- Client/PPPoE-Apply nicht idempotent) blieb bestätigt gefixt. Isolation zuerst manuell per SSH an ether5 nachgebaut (eigenes Subnetz, eigener DHCP-Server, /interface list-basierte Forward-Drop-Regel) — dabei drei reine Konfigurationsfehler der Reihe nach gefunden und behoben (Adresse versehentlich als /32 bzw. .0-Netzadresse statt Host-Adresse eingetragen; ether5 fehlte in der defconf-Interface-Liste "LAN", wodurch DNS-Anfragen an den Router selbst blockiert wurden, obwohl DHCP/Routing/Internet normal liefen). Isolation danach beidseitig bestätigt (iMac ↔ Laptop erreichen sich nicht, Internet für beide weiterhin ja).

Anschließend derselbe Test über den App-Wizard (Experte-Modus, ether4) wiederholt, um die Abnahme-Bedingung aus der Vorsession ("kompletter Wizard-Durchlauf") tatsächlich zu erfüllen — dabei drei echte App-Bugs gefunden und gefixt, alle auf denselben "Wizard wurde nie gegen einen bereits konfigurierten Router bzw. mit bereits gebrückten Ports erneut ausgeführt" blinden Fleck zurückzuführen:

  • Bug 22: DhcpServerCommandBuilder fügte ein neues LAN-/VLAN- Interface nie der defconf-Interface-Liste "LAN" hinzu — derselbe DNS-Bug wie oben, jetzt als App-Bug bestätigt. Fix: automatisch /interface list add name=LAN + /interface list member add list=LAN interface=<name>, beide toleriert falls schon vorhanden.
  • Bug 23: ein voller Wizard-Durchlauf gegen einen bereits konfigurierten Router brach beim zweiten Befehl ab (/ip address add address=192.168.88.1/24 interface=bridge — "already have such address"), weil nur WAN-DHCP-Client/PPPoE idempotent behandelt wurden (Bug 21). Fix: SetupViewModel. applyIdempotently erweitert — /ip pool//ip dhcp-server/ /ip dhcp-server network werden bei Duplikat jetzt als .set (gematcht auf name/name/address) erneut versucht, /ip address bei exaktem Duplikat als bereits erledigt behandelt (ein Interface darf mehrere Adressen halten, ein .set nach "interface" träfe sonst potenziell die falsche).
  • Bug 24: LAN-/VLAN-Schritt löste ein gewähltes physisches Interface nie aus einer bestehenden Bridge — auf Werks-Routern sind ether25 ab Werk gebridged. Als eigenes isoliertes Netz konfiguriert blieb das Interface Bridge-"Slave", RouterOS verwarf die generierten Isolationsregeln selbst als ungültig ("in/out-interface matcher not possible when interface is slave - use master instead"), live an ether4 bestätigt. Fix: DhcpServerCommandBuilder stellt jetzt /interface bridge port remove [find interface=<name>] voran (übersprungen für "bridge" selbst), toleriert als bereits erledigt falls nie gebridged.

Nach allen drei Fixes lief der komplette App-Wizard-Durchlauf für ether4 fehlerfrei durch (inkl. Neuanwendung der bereits vorhandenen bridge/ether1-Konfiguration), Isolation + DNS + Internet vom Nutzer am echten Gerät bestätigt. Alle 52 Unit-Tests grün. M8 damit als einziger Milestone bisher sowohl manuell als auch über die App selbst live verifiziert.

Ziel

Native macOS-App (SwiftUI), die Laien per geführtem Interview-Wizard durch die häufigsten Mikrotik-RouterOS-Konfigurationen führt: Internet-Anschluss, Heimnetzwerk/DHCP, zusätzliche VLAN-Netzwerke, WLAN, Firewall-Grundschutz, plus Backup/Wiederherstellung. Vollständiger ursprünglicher Plan/Kontext: ~/.claude/plans/purrfect-puzzling-bunny.md (seither an vielen Stellen weiterentwickelt, siehe unten).

Setup / Bauen

cd RouterOS
xcodegen generate                 # erzeugt RouterOSAssistant.xcodeproj (nicht in git)
open RouterOSAssistant.xcodeproj  # oder direkt per xcodebuild

# Debug (Xcode Cmd+R empfohlen für manuelle/Live-Tests, siehe Gatekeeper-Hinweis unten)
xcodebuild -project RouterOSAssistant.xcodeproj -scheme RouterOSAssistant \
  -destination 'platform=macOS' build

# Nur kompilieren, nicht ausführen (umgeht den Gatekeeper-Hang, siehe unten)
xcodebuild -project RouterOSAssistant.xcodeproj -scheme RouterOSAssistant \
  -destination 'platform=macOS' build-for-testing

# Release + Deploy nach /Applications
xcodebuild -project RouterOSAssistant.xcodeproj -scheme RouterOSAssistant \
  -configuration Release build
rm -rf "/Applications/RouterOS Assistant.app"
cp -R "<DerivedData>/Build/Products/Release/RouterOSAssistant.app" \
  "/Applications/RouterOS Assistant.app"
killall Dock   # Icon-Cache auffrischen, falls sich nur das Icon geändert hat

xcodegen ist via Homebrew installiert. Projekt-Struktur/Targets/Package- Abhängigkeiten stehen in project.yml, das ist die Quelle der Wahrheit — nicht das generierte .xcodeproj von Hand editieren.

Git: Remote origin zeigt auf eine selbst gehostete Gitea-Instanz (git@192.168.178.222:kay/RouterOS.git). .xcodeproj, DerivedData, .build, Firmware-Dateien (*.npk, *.cpgz) und Router-Backups (*.rsc, /Backups/) sind gitignored.

Architektur

RouterOSAssistant/
  App/RouterOSAssistantApp.swift       — 3 Tabs, teilen sich EINE ConnectionService-Instanz
  Core/
    Models/
      RouterOSCommand.swift            — eine Änderung, drei Operationen (.add/.set/.remove), zwei Renderer (CLI-Zeile / REST-JSON)
      WanConfig.swift, LanDhcpConfig.swift, VlanEntry.swift, WifiNetworkConfig.swift, FirewallConfig.swift  — bauen je RouterOSCommand-Listen
      DhcpServerCommandBuilder.swift   — geteilte "Adresse+Pool+Server+Netzwerk"-Logik (LAN + VLAN)
      RouterOSModels.swift             — Credentials, DeviceInfo, Interface, RouterOSError
      RouterOSMenuItem.swift           — generisches Item (id + [String:String] fields) für JEDEN RouterOS-Menüpfad (M10)
      RouterOSSchema.swift             — RouterOSFieldSchema/RouterOSMenuSchema/RouterOSMenuCategory: kuratiertes Formular-Schema fürs Experte-Tool (M10)
      RouterOSSchemaCatalog.swift      — die eigentliche Menü-Liste: Firewall-Familie voll kuratiert, Rest teils kuratiert/teils generisch (M10)
      OverviewGraph.swift              — OverviewNode/OverviewEdge/OverviewEdgeKind/DevicePortGroup: reine Datenmodelle fürs Übersicht- und Geräte-Diagramm (M11/M12)
      LanDevice.swift                  — ein LAN-Gerät (DHCP-Lease + ARP + Bridge-Host verschmolzen), fürs Geräte-Tab (M12)
    Networking/
      RouterOSTransport.swift          — Protocol: connect/fetchDeviceInfo/fetchInterfaces/fetchFirewallRuleCounts/fetchMenuItems/fetchFieldValues/apply/disconnect
      RestTransport.swift              — REST-API (RouterOS ≥7.1), Zertifikats-TOFU, .set via GET+PATCH (findItemID), .remove via GET+DELETE, fetchMenuItems (Array- oder Singleton-Objekt), fetchFieldValues (Query-Filter, unverifiziert)
      SSHTransport.swift               — SSH-Fallback via Citadel, CLI-Text-Parsing, resetToFactoryDefaults(), eigene Hostkey-TOFU, fetchMenuItems (generisch, inkl. Singleton-Fallback), fetchFieldValues (`:foreach`+`find`+`get`, M12/Bug 14+17), uploadScript (SFTP, M13), applyRestoreScript (reset-configuration+run-after-reset, M13)
      RouterOSCliParser.swift          — parst `/system resource print`, `/interface print terse`, und generisch jeden Menüpfad (parseGenericItems/parseSingletonItem, M10)
      CertificateTrustStore.swift / CertificateFingerprint.swift  — TOFU für REST-Zertifikate
      SSHHostKeyTrustStore.swift / SSHHostKeyFingerprint.swift    — TOFU für SSH-Hostkeys (M7)
    Services/
      ConnectionService.swift          — zentraler App-State: REST-zuerst-SSH-Fallback, hält credentials/interfaces/deviceInfo, plus generisches fetchMenuItems
      BackupService.swift              — Config-Export (`/export terse`) über eigene SSH-Verbindung, wählbarer Zielordner, restoreBackup (M13: SFTP-Upload + reset-configuration+run-after-reset + Login-Erhalt), backupModel (Modell-Header-Parsing)
      FactoryResetService.swift        — /system reset-configuration über eigene SSH-Verbindung ("Gefahrenzone")
      KeychainService.swift            — Passwort-Speicherung
      UpdateService.swift              — M14: RouterOS-Software-Update (/system package update check-for-updates/install) + Routerboard-Firmware (upgrade) + reboot, eigene SSH-Verbindung
  Features/
    Wizard/Steps/Connect/              — Verbinden-Tab (inkl. Schnell-Backup-Button, Routerboard-Info, Software-/Firmware-Update-Check (M14), nach erfolgreicher Verbindung)
    Wizard/Steps/Setup/                — Einrichten-Tab: Mode → Wan → Lan → (Vlan nur Experte) → Wifi → Firewall → Review/Apply
    Expert/                            — Experte-Tab (M10): ExpertView (Kategorie-/Menüliste + "eigener Pfad"), ExpertViewModel, ExpertMenuDetailView (Item-Liste + Add/Edit-Sheet)
    Overview/                          — Übersicht-Tab (M11): OverviewView (Diagramm+Legende+Detailpanel), OverviewViewModel (buildGraph, rein), OverviewLayout (Spalten/Zeilen-Geometrie)
    Devices/                           — Geräte-Tab (M12): DevicesView (Port-Tabellen+Rechtsklick+Rohdaten-Sheet), DevicesViewModel (buildDevices/buildPortGroups, rein; confirmStaticAssignment/confirmStaticRemoval)
    Backup/                            — Sicherungen-Tab (Ordner wählen, Gefahrenzone: Werkseinstellungen wiederherstellen)
  Resources/Assets.xcassets/AppIcon.appiconset/  — App-Icon "Signal Router" (16px1024px)
RouterOSAssistantTests/                — reine Unit-Tests (Command-Builder, CLI-Parser, Fallback-Logik via Mock-Transport)

Architekturprinzip RouterOSCommand (.add/.set): .add legt neue Einträge an (CLI ... add key=value, REST POST). .set ändert bestehende Einträge, per matchField/matchValue gefunden — CLI löst das inline über set [find field=value] ..., REST muss dafür erst per GET das Item suchen, seine RouterOS-interne .id auslesen, dann PATCH restPath/<id> schicken (RestTransport.findItemID). Eingeführt für WLAN (M5), weil Wireless-Interfaces schon vor jeder Konfiguration existieren. VLAN (M4) hat trotzdem weiterhin keine Port-Zuweisung (Access/Trunk) — technisch jetzt machbar, aber bewusste Scope-Entscheidung, nicht nachgezogen.

System-Aktionen (Backup, Werksreset) laufen immer über eine eigene SSH-Verbindung, unabhängig vom aktiven Live-Transport — BackupService und FactoryResetService folgen demselben Muster: es sind einmalige System-Kommandos, kein Menü-Item zum Hinzufügen/Ändern, passen also nicht ins RouterOSCommand-Modell und haben keine verifizierte REST-Entsprechung.

ConnectionService ist der einzige geteilte State. Alle drei Tabs bekommen dieselbe Instanz von der App-Ebene injiziert (kein @EnvironmentObject, explizite Übergabe im Init). SwiftUI-Falle: jede View, die connectionService-Felder liest, muss ihn selbst als @ObservedObject halten — nicht nur transitiv über ein anderes ViewModel erreichen (siehe Bug 1 unten).

Gefundene Bugs (chronologisch, alle gegen echte Geräte)

  1. ConnectView zeigte nie VerbindungsstatusconnectionService war nur über viewModel.connectionService erreichbar (zwei Ebenen tief verschachteltes ObservableObject), SwiftUI abonniert das nicht automatisch. Fix: ConnectView hält connectionService zusätzlich selbst als @ObservedObject.

  2. NIOSSHError.localizedDescription nutzlos — bridged auf generisches NSError ("error 1"). Fix: String(describing:).

  3. SSH scheiterte mit keyExchangeNegotiationFailure — RouterOS bietet nur Legacy-Algorithmen an, Citadel-Default enthält sie nicht. Fix: algorithms: .all.

  4. Citadel.SSHClient.CommandFailed zeigte nur den Exit-CodeexecuteCommand() verwirft die gesammelte Ausgabe bei Exit-Code ≠ 0. Fix: eigene Sammlung über executeCommandStream().

  5. Picker-Warnungen durch Platzhalter-InterfacenamenprepareDefaults() korrigiert jetzt WAN und LAN, läuft schon im init.

  6. Kritisch: CLI-Parser verlor 6 von 7 Interfaces auf einem hEX-RouterparseInterfaces/parseDeviceInfo splitteten nur auf "\n"; die SSH-Ausgabe dieses Geräts trennte Zeilen anders, sodass alle Interface-Zeilen zu einer verschmolzen — beim key=value-Parsen überschrieb jedes Feld den vorherigen Wert, übrig blieb nur das letzte Interface im Text (lo, Loopback) mit vermischten Werten. Folge: jede WAN-Interface-Referenz in den angewendeten Firewall-Regeln zeigte auf lo statt den echten WAN-Port — auf diesem Testgerät folgenlos, weil RouterOS bereits eine vollständige eigene Standard-Firewall (defconf) mitbrachte, die weiterhin schützte; auf einem Gerät ohne bestehende Firewall wäre das eine wirkungslose, aber als aktiv angezeigte Firewall gewesen. Fix: split(whereSeparator: \.isNewline) (robust gegen \n/\r/\r\n) + zusätzliches Sicherheitsnetz: Loopback-Interfaces werden in SetupView jetzt grundsätzlich aus allen WAN/LAN/VLAN- Auswahllisten gefiltert. Regressionstest mit echten \r\n-getrennten hEX-Daten in RouterOSCliParserTests.

  7. /ip service: Feld heißt available-from, nicht address — beim Kuratieren des Experte-Schemas geraten statt geprüft. Live gegen den hEX bestätigt: /ip service print zeigt tatsächlich available-from=; ein set ... address=... hätte den Zugriffsfilter stillschweigend nicht gesetzt. Schema korrigiert (RouterOSSchemaCatalog.swift).

  8. RouterOS 7.24.2 zeigt .id gar nicht in print terse, und Singleton-Menüs verweigern terse komplett — Annahme beim Bau des generischen Experte-Motors (M10) war, .id=*N sei Teil jeder print terse-Zeile (wie bei REST-JSON). Live-Test zeigte: RouterOS 7.24.2 liefert dort gar kein .id-Feld; Menüs mit genau einem Eintrag statt einer Liste (/ip dns, /system identity, /system clock, /system ntp client, /tool e-mail) lehnen terse sogar komplett ab ("bad parameter terse"). Fix: :put [<Menüpfad> find] liefert die echten internen IDs (z.B. *C;*1;*2) — live bestätigt, dass die Reihenfolge exakt der von print terse entspricht, damit per Position zuordenbar. Für Singleton-Menüs neuer RouterOSMenuSchema.isSingleton- Schalter: Fetch fällt auf normales print (Doppelpunkt-Format, parseSingletonItem) zurück, .set läuft ohne [find ...] direkt (matchField: "" als Sentinel in RouterOSCommand/beiden Transports). Beides live gegen /system identity durchgespielt (Name testweise geändert, Fehlertext exakt geprüft, zurückgesetzt).

  9. Cross-Referenz-Felder (z.B. DHCP-Server → Adress-Pool) waren reiner Freitext — Nutzer meldete: "kann den vorher angelegten Adress-Pool nicht in der Auswahl sehen". Betraf jedes Feld, das den Namen eines Eintrags aus einem anderen Menü referenziert (DHCP-Server→Pool, PPP-Benutzer→Profil, Scheduler→Skript, Firewall-Filter→Adress-Liste, WireGuard-Peer→Interface) — alle als .text statt als Auswahl gebaut. Fix: neuer Feldtyp RouterOSFieldSchema.Kind.menuItemPick(menuPath: restPath:valueField:), lädt beim Öffnen eines Menüs live die existierenden Namen aus dem referenzierten Menü (ExpertViewModel.loadCrossReferenceOptions) und zeigt sie als Picker. Auf alle fünf betroffenen Felder angewendet.

  10. Kritisch, app-weit: RouterOS' SSH-CLI gibt bei fehlgeschlagenen Befehlen Exit-Code 0 zurück — Nutzer meldete: DHCP-Server-Anlage zeigte keine Fehlermeldung, wurde aber auch nicht angelegt. Live reproduziert: /ip dhcp-server add ... auf einem Interface mit bereits bestehendem Server lieferte "failure: server or relay with such interface already exists" und Exit-Status 0; ein ungültiger action=-Wert lieferte "syntax error (...)", ebenfalls Exit-Status 0 (per expect-Skript mit catch wait result geprüft). SSHTransport. apply() warf bisher nur bei einem von Citadel gemeldeten Nicht-Null-Exit-Code — also nie. Betraf jeden .apply()-Aufruf über SSH app-weit, nicht nur den Experte-Tab: der komplette Einrichten- Wizard (WAN/LAN/VLAN/Firewall) lief bei jedem SSH-Schreibvorgang mit demselben blinden Fleck, seit M3. Fix: apply() behandelt jede nicht-leere Ausgabe eines add/set/remove-Befehls als Fehler — laut allen in dieser Session live getesteten Befehlsfamilien (15+) sind erfolgreiche mutierende Befehle immer still. Nach dem Fix live end-to- end bestätigt: komplette Kette VLAN→IP-Adresse→Pool→DHCP-Server→ DHCP-Netzwerk auf einem freien Interface angelegt, lief fehlerfrei durch, danach vollständig wieder entfernt.

  11. Bool-Felder im Experte-Schema als "true"/"false" statt "yes"/"no" geschrieben — Nutzer meldete nach Bug-10-Fix: VLAN-Anlage zeigte jetzt "Unerwartete Antwort vom Router: syntax error (line 1 column 30)". Live reproduziert mit der exakt von der App erzeugten Befehlszeile (disabled=false) — identischer Fehler; mit disabled=no lief es durch. RouterOS' CLI akzeptiert nur yes/no für Bool-Parameter, nicht true/false. Betraf praktisch jedes kuratierte Schema (23× "false", 4× "true" als defaultValue). Fix zweigleisig: ExpertViewModel.pendingCommand normalisiert jeden .bool-Feldwert vor dem Senden auf yes/no (Schutz unabhängig von der Quelle), zusätzlich Katalog-Strings selbst bereinigt. Live erneut end-to-end bestätigt (VLAN→IP→Pool→DHCP-Server mit disabled=no).

  12. Lösch-Buttons fehlten komplett im Experte-Tab — Nutzer: "mir fehlen komplett die Löschen-Buttons". Ursache: .swipeActions(...) ist eine iOS/iPadOS-Wischgeste ohne Entsprechung auf macOS-Listen — dort erscheint schlicht nichts, unabhängig vom Code. Fix: sichtbarer Papierkorb-Icon-Button je Zeile statt Wisch-Geste (nur bei Nicht-Singleton-Menüs, wo Löschen überhaupt Sinn ergibt). Lösch-Pfad (ID-Ermittlung + remove [find .id=...]) live gegen das vom Nutzer selbst angelegte VLAN nachvollzogen, funktioniert.

  13. Interface-Auswahl (.interfacePick) war einmalig beim Verbinden eingefroren — Nutzer legte ein VLAN an, danach fehlte es in der Interface-Auswahl beim Anlegen eines DHCP-Servers. Ursache: .interfacePick las aus ConnectionService.interfaces, nur einmal bei finishConnecting() befüllt, nie danach aktualisiert — anders als die neueren .menuItemPick-Felder (Pool/Profil/Skript), die bei jedem Menü-Öffnen frisch laden. Fix: neues ExpertViewModel.liveInterfaceNames, per /interface print terse bei jedem reloadItems() aktualisiert; availableInterfaces-Parameter- Durchreichung (ExpertViewExpertMenuDetailViewExpertItemEditView) komplett entfernt, da überflüssig. Danach vom Nutzer selbst in der App bestätigt: neu angelegtes VLAN erscheint sofort in der DHCP-Server-Interface-Auswahl.

  14. /ip dhcp-server lease gibt das dynamic-Feld über print terse nie aus, in keinem Zustand — beim Bau des Geräte-Tabs (M12) zwei falsche Annahmen nacheinander gemacht: erst "fehlt = fest" (Standard- Annahme falsch herum, jedes echte DHCP-Gerät zeigte "Fest"), dann nach Korrektur "fehlt = dynamisch, dynamic=no erscheint explizit bei festen Leases" — beides falsch. Live an echten Rohdaten geprüft (Nutzer schickte per "Rohdaten anzeigen" wiederholt komplette Feldlisten, u.a. für einen zweifelsfrei dynamischen UND einen zweifelsfrei festen Eintrag): das Feld taucht in terse überhaupt nie auf, unabhängig vom Status — nur /ip dhcp-server lease print (ohne terse) zeigt es als Flag-Buchstabe D. Fix: Status kommt nicht mehr aus gelesenen Feldern, sondern aus einer gezielten Abfrage gegen RouterOS' interne Eigenschaft (RouterOSTransport.fetchFieldValues, siehe Bug 17 für die korrekte Syntax).

  15. fetchMenuItemss .id-Positions-Überlagerung ordnete bei /ip dhcp-server lease die falsche .id der falschen Zeile zu — zwei Leases (iMac dynamisch, Laptop nach Umstellung fest), aber die App zeigte beide als "Fest". Ursache: fetchMenuItems liest die .ids über einen separaten :put [<Pfad> find]-Aufruf und ordnet sie nach Position den print terse-Zeilen zu (Mechanismus aus Bug 8, dort für ein anderes Menü live bestätigt) — für dieses Menü stimmte die Reihenfolge zwischen beiden Aufrufen offenbar nicht überein, die feste .id landete auf der iMac-Zeile. Fix ausschließlich im Geräte-Tab: Erkennung und der make-static-Zielauswahl laufen jetzt komplett über die MAC-Adresse (steht direkt in derselben terse-Zeile, kein Cross-Command-Abgleich nötig) statt über .id. Nicht behoben: derselbe .id-Überlagerungsmechanismus wird auch vom Experte-Tab für .set/.remove auf beliebigen Menüs benutzt — ob das dort ebenfalls (menü- oder situationsabhängig) falsch zuordnen kann, ist ungeprüft. Siehe Nächste Schritte.

  16. SwiftUI-.confirmationDialog löschte sein eigenes Ziel, bevor die bestätigte Aktion es lesen konnte — "Feste IP zuweisen" tat sichtbar nichts: kein Fehler, kein Erfolg, Dialog schloss normal. Ursache: isPresented war eine berechnete Binding, deren Setter bei jedem Schließen (auch durch den bestätigenden Knopf selbst — SwiftUI ruft den Setter mit false bei jedem Tap in einem .confirmationDialog, unabhängig vom gedrückten Knopf) pendingStaticAssignment = nil setzte — also genau das Objekt, das confirmStaticAssignment() Sekundenbruchteile später lesen wollte. Fix: Dialog-Sichtbarkeit (showStaticConfirmation, ein einfaches @State Bool) und Ziel- Objekt (pendingStaticAssignment) komplett entkoppelt — genau das Muster, das BackupListViews Werksreset-Dialog schon die ganze Zeit korrekt verwendet (eigenes @State-Bool statt abgeleitetem Binding).

  17. Ein erster Fix-Versuch für Bug 14 geriet zu einer weiteren, nie verifizierten Befehls-Annahme<Menü> get [find feld=wert] rückgabefeld] als ein kombinierter Befehl geraten (Annahme: get akzeptiert eine find-Ergebnisliste direkt). Live widerlegt: eine per Winbox bestätigte, echte feste Zuweisung wurde von dieser Abfrage trotzdem nicht gefunden (falsches Negativ). Fix: nur noch aus zwei einzeln diese Session mehrfach bestätigten Bausteinen zusammengesetzt — nacktes find mit einer Bedingung, plus get <id> feld auf eine einzelne bekannte .id, verbunden über eine :foreach-Schleife (RouterOSTransport.fetchFieldValues/SSHTransport.fetchFieldValues).

  18. Kritisch, live so passiert: Backup-Wiederherstellung sperrte den Login komplett aus, nur ein Hardware-Reset half noch — beim ersten Live-Test der neuen Restore-Funktion (M13). Ursache doppelt: RouterOS' /export kann Benutzerpasswörter grundsätzlich nie exportieren (offiziell dokumentiert), und der für den Restore genutzte no-defaults=yes-Reset löscht zusätzlich den mitgelieferten Standard-Admin-Account komplett — nach dem Restore existierte also weder aus der Sicherung noch als Werksvorgabe ein funktionierender Login. Fix: BackupService.restoreBackup stellt dem hochgeladenen Skript jetzt einen kleinen, selbst generierten Block voran (nicht angehängt — RouterOS bricht den Import beim ersten Fehler ab, ein Login-Erhalt am Ende hätte also nie gegriffen, falls irgendwo vorher im eigentlichen Sicherungsskript ein Fehler auftritt), der exakt das aktuell in der App verwendete Login per :if ([/user find name=...] = "") do={add ...} else={set ... password=...} neu anlegt bzw. das Passwort neu setzt — das einzige Passwort, das die App zuverlässig kennt. Beim erneuten Live-Test danach vom Nutzer bestätigt ("das hat funktioniert").

  19. SwiftUI-Typprüfung bricht bei zu langen Modifier-Ketten ab, kein LogikfehlerBackupListView warf nach dem Hinzufügen weiterer .confirmationDialog/.alert-Modifiere für den Restore-Dialog "the compiler is unable to type-check this expression in reasonable time", mit einer irreführenden Zeilenangabe (zeigte auf einen ganz gewöhnlichen, bereits woanders im selben File funktionierenden Binding-Ausdruck). Fix: die Restore-spezifischen Dialoge/Alerts in eine eigene @ViewBuilder-Computed-Property ausgelagert, statt alles in einer einzigen body-Kette zu verketten — teilt die Typinferenz-Last auf zwei kleinere Ausdrücke auf. Bei künftigen Views mit vielen .alert/.confirmationDialog/.sheet-Modifiern in Folge vorbeugend gleich aufteilen, statt erst bei diesem Fehler zu reagieren.

  20. fetchMenuItemss Singleton-Fallback (Bug 8/M10) griff für /system routerboard nicht — genau dasselbe Bug-10-Muster, nur an einer Lesestelle statt beim Schreiben. Die neue Routerboard-Sektion (M14) blieb leer, kein Fehler, nichts. Ursache: der Fallback reagiert bisher nur auf eine echte Exception ("bad parameter terse" via Citadels CommandFailed), aber /system routerboard print terse liefert diesen Fehlertext live bestätigt mit Exit-Code 0 zurück — der Fehlertext landete also als normaler (unparsbarer) Output, fetchMenuItems gab still [] zurück statt in den Singleton-Zweig zu fallen. Fix: zusätzlich den zurückgegebenen Output-Text selbst auf "bad parameter terse" prüfen, nicht nur die Exception — deckt beide Varianten ab, wie RouterOS diesen Fehler je nach Menü meldet. Betrifft potenziell jedes Singleton-Menü, nicht nur /system routerboard — der generische Lese-Pfad wird von Übersicht-, Geräte- und Experte-Tab gemeinsam genutzt.

  21. Wizard-WAN-Apply war nicht idempotent — Wizard erneut auf einem Interface durchlaufen, das schon einen DHCP-Client hat (hier: aus einer zurückgespielten Sicherung), scheiterte live mit "failure: dhcp-client on that interface already exists". WanConfig. buildCommands() erzeugt für DHCP-Client/PPPoE immer .add, ohne vorher zu prüfen, ob auf dem Interface schon einer existiert. Fix: SetupViewModel.applyIdempotently fängt einen .add-Fehlschlag auf /ip dhcp-client//interface pppoe-client ab und wiederholt ihn als .set (nach "interface" gematcht) — bewusst nur für diese zwei Menüs mit "maximal ein Eintrag pro Interface"-Semantik, nicht generell für jedes .add (z.B. /ip address erlaubt legitim mehrere Adressen pro Interface). Noch nicht erneut live getestet, siehe Nächste Schritte.

Lehren: Citadel/NIOSSH-Fehler immer mit String(describing:) loggen, nie .localizedDescription. Jede View, die ein ObservableObject aus einem ViewModel liest, muss es selbst separat als @ObservedObject halten. Zeilen-Splitting bei SSH-Ausgaben grundsätzlich mit \.isNewline statt festem "\n", da sich das je nach Gerät/Version unterscheiden kann. Bei generischem RouterOS-Tooling (M10) nichts über CLI-Ausgabeformate annehmen, was nicht live geprüft ist — .id-Verhalten und terse-Support unterscheiden sich zwischen Menütypen und RouterOS-Versionen. RouterOS' SSH-Exit-Code ist kein verlässliches Erfolgssignal — Ausgabetext ist die einzige verlässliche Fehlerquelle über diesen Transport (Bug 10); REST ist davon nicht betroffen (prüft HTTP-Statuscodes korrekt). RouterOS' CLI will yes/no für Bools, nie true/false (Bug 11) — an einer einzigen Stelle normalisieren (pendingCommand) statt an jedem Schema-Eintrag hoffen, es richtig geschrieben zu haben. SwiftUI- Interaktionsmuster nicht plattform-blind übernehmen — .swipeActions sieht in der Doku harmlos aus, ist aber faktisch iOS/iPadOS-only (Bug 12). Jeder Zustand, der sich während der Nutzung ändern kann (Interface-Liste nach Anlegen eines VLANs), gehört live nachgeladen, nicht einmalig beim Verbinden gecacht (Bug 13) — dasselbe Prinzip wie crossReferenceOptions für Pool/Profil/Skript, nur nicht von Anfang an konsequent auf alle Auswahllisten angewendet. print terse gibt nicht jede Eigenschaft als Feld aus — manche (z.B. dynamic bei DHCP-Leases) erscheinen nur als Flag-Buchstabe im menschenlesbaren print, nie als key=value (Bug 14); wenn ein Feld partout nicht auftaucht, RouterOS' find/get gegen die interne Eigenschaft fragen statt weiter nach Formulierungen im Text zu suchen, die es nicht gibt. fetchMenuItemss .id-Überlagerung ist nicht für jedes Menü verlässlich (Bug 15) — bei mehreren möglichen Identifikatoren lieber über ein Feld matchen, das direkt in derselben Zeile steht (MAC, Name), statt über eine per Position aus einem zweiten Befehl zugeordnete .id. Jede kombinierte RouterOS-Skript-Syntax vor dem Einsatz live prüfen, auch wenn die Einzelteile schon bestätigt sind (Bug 17) — find allein und get <id> feld allein zu kennen heißt nicht, dass get [find ...] feld als eine Einheit genauso funktioniert. Bei SwiftUI-Bestätigungsdialogen Sichtbarkeit und Nutzlast nie an dasselbe State-Feld koppeln (Bug 16) — isPresenteds Setter feuert bei jedem Knopfdruck, nicht nur bei Abbrechen; ein eigenes, unabhängiges Bool fürs Zeigen/Verstecken hält die Nutzlast so lange am Leben, wie die bestätigte Aktion sie braucht. RouterOS-Backups über /export enthalten grundsätzlich nie Passwörter, und no-defaults=yes löscht auch den Werks-Admin-Account (Bug 18) — jede Restore-Funktion, die einen kompletten Reset vor dem Re-Import macht, muss den Login selbst wieder herstellen, sonst folgt ein kompletter Aussperr-Vorfall (nur per Hardware-Reset behebbar, live so passiert). Login-Erhalt gehört an den Anfang des Skripts, nicht ans Ende — RouterOS bricht den Import beim ersten Fehler irgendwo im Skript komplett ab. SwiftUI-Typprüfung kann bei langen Modifier-Ketten mit einer irreführenden Fehlerzeile timeouten (Bug 19) — kein Hinweis auf einen echten Logikfehler an der genannten Stelle; bei vielen .alert/.confirmationDialog in Folge vorbeugend in eigene @ViewBuilder-Properties aufteilen. "RouterOS' Exit-Code ist kein verlässliches Erfolgssignal" (Bug 10) gilt auch für Lesebefehle, nicht nur fürs Schreiben (Bug 20) — derselbe Singleton-Fallback, der für ein Menü per Exception ausgelöst wurde (Bug 8), blieb für ein anderes Menü aus, weil RouterOS denselben Fehlertext dort mit Exit-Code 0 zurückgibt; robuste Erkennung muss immer auch den Output-Text selbst prüfen, nie nur auf eine geworfene Exception vertrauen. Wizard-Apply nutzte für WAN-DHCP-Client/PPPoE immer .add (Bug 21) — scheiterte live mit "failure: dhcp-client on that interface already exists" auf einem Interface, das (z.B. aus einer zurückgespielten Sicherung) schon einen Client hatte; SetupViewModel.applyIdempotently versucht seitdem bei .add-Fehlschlag auf diesen beiden Menüs automatisch .set (nach "interface" gematcht). Ein neu eingerichtetes LAN-/VLAN-Interface wurde nie der defconf-Interface-Liste "LAN" hinzugefügt (Bug 22) — auf Routern mit Werks-Firewall (chain=input action=drop in-interface-list=!LAN) blieb dadurch jede Anfrage an den Router selbst (DNS, Winbox) vom neuen Netz aus blockiert, obwohl DHCP/Routing/Internet normal liefen (reine Forward-Chain-Sache, unberührt) — live an einer manuell nachgebauten Isolation gefunden (Lease + Default-Route vorhanden, jede DNS-Anfrage lief trotzdem in Timeout). Voller Wizard-Durchlauf gegen einen bereits konfigurierten Router brach an jedem nicht-idempotenten .add ab (Bug 23) — nur WAN-DHCP-Client/PPPoE waren idempotent (Bug 21); /ip address (exaktes Duplikat = bereits erledigt) sowie /ip pool//ip dhcp-server//ip dhcp-server network (Retry als .set, gematcht auf name/name/address) kamen dazu. Ein als eigenes isoliertes Netz konfiguriertes Interface blieb Bridge-"Slave" (Bug 24) — auf Werks-Routern sind ether25 ab Werk gebridged; ohne explizites Lösen aus der Bridge verwarf RouterOS die generierten Forward-Isolationsregeln selbst als ungültig ("in/out-interface matcher not possible when interface is slave - use master instead"), live an ether4 bestätigt; DhcpServerCommandBuilder stellt seitdem ein /interface bridge port remove [find interface=<name>] voran (übersprungen für "bridge" selbst). Ein geleertes Textfeld (z.B. Kommentar) blieb nach dem Speichern unverändert bestehen (Bug 25) — pendingCommand filterte alle leeren formValues grundsätzlich aus den Argumenten heraus, RouterOS' set ändert aber nur explizit übergebene Parameter, ein weggelassener bleibt unangetastet statt geleert. Fix: ein Feld bleibt im Argument-Set, wenn es vorher einen Wert hatte (jetzt als feld="" explizit geleert); zusätzlich musste RouterOSCommands SSH-Zeilen-Rendering einen leeren Wert als "" statt als nacktes feld= ausgeben. Bearbeiten einer Route (z.B. nur den Kommentar ändern) scheiterte mit "bad parameter immediate-gw" (Bug 26) — RouterOS liefert bei /ip route print u.a. das berechnete, nur lesbare Feld immediate-gw mit; da dieses Feld im Route-Schema nicht kuratiert ist, landete es unverändert in den freien "Weitere Parameter" und wurde bei jedem Speichern blind mitgeschickt — RouterOS lehnt immediate-gw beim set als ungültigen Parameter ab und bricht dadurch die komplette Änderung ab, auch wenn nur ein unrelated Feld wie der Kommentar geändert wurde. Fix: ein unkuratiertes Feld wird nur noch mitgeschickt, wenn sein Wert sich gegenüber dem ursprünglich geladenen Item tatsächlich geändert hat (oder neu hinzugefügt wurde) — betrifft nicht nur Routen, sondern jedes Menü mit berechneten/nur lesbaren Feldern im generischen Lese-Pfad.

xcodebuild test hängt — Gatekeeper, kein Code-Bug

xcodebuild ... test kann exakt beim App-Start für ~5 Minuten hängen, dann "The test runner hung before establishing connection.". Ursache (per log show, Filter auf syspolicyd/amfid): ad-hoc-signierte Binaries lösen bei syspolicyd einen Live-Netzwerk-Check (GK performScan) gegen Apples Gatekeeper-Server aus; jeder Neu-Build ändert den Binary-Hash und kann einen neuen, manchmal langsamen Check auslösen. Betrifft nur den CLI-Weg — Xcodes Cmd+R-Weg bekommt vom Debugger-Start eine Gatekeeper-Ausnahme. Deshalb: bei Hängern erst xcodebuild ... build-for-testing (kompiliert nur, kein App-Start, kein Gatekeeper-Trigger) für den Compile-Check nutzen, echte Testläufe/manuelle Verifikation dem Nutzer über Xcode überlassen statt CLI-Versuche zu wiederholen.

Bekannte Einschränkungen (bewusst, nicht vergessen)

  • Restore nur für dasselbe Routermodell, RouterOS erzwingt das selbst nicht — die App blockt bei erkanntem Modell-Mismatch (per Recherche bestätigt: RouterOS importiert eine Fremd-Modell-Config anstandslos, potenziell mit falsch zugeordneten Interfaces). Schutz greift nur, wenn die Backup-Datei die # model = ...-Kopfzeile hat (RouterOS ≥ mind. 6.49.5) — bei sehr alten/handbearbeiteten Dateien ohne diese Zeile läuft der Restore ungeprüft durch (Dialog weist explizit auf "unbekannt" hin, blockt aber nicht).
  • Restore-Skript-Datei bleibt nach der Wiederherstellung auf dem Router liegen (flash/routerosassistant-restore.rsc) — kein automatisches Aufräumen, weil der Router direkt im Zuge des Befehls neu startet und die Verbindung dabei abbricht; ein erneutes Verbinden nur zum Löschen dieser einen Datei erschien nicht aufwandsgerecht. Harmlos (überschreibt sich beim nächsten Restore selbst), aber bewusst in Kauf genommen, nicht vergessen.
  • REST-Pfad ungetestet für Schreibvorgänge — auf beiden bisherigen Testgeräten war www-ssl (Port 443) aus, jeder Schreibtest lief über SSH. Der REST-apply()-Pfad (POST/PATCH, findItemID für .set) ist nur gegen Mocks getestet. Der REST-Zweig von fetchFieldValues (M12/Bug 14) ist zusätzlich auf einer nirgends offiziell dokumentierten Annahme aufgebaut (Query-String-Filter ?feld=wert auf einem GET) — komplett unverifiziert.
  • fetchMenuItemss .id-Positions-Überlagerung: Verlässlichkeit für andere Menüs ungeprüft (siehe Bug 15) — bei /ip dhcp-server lease live als falsch bestätigt (.id landete auf der falschen Zeile), für den Geräte-Tab per MAC-Adress-Matching umgangen. Derselbe Mechanismus läuft aber weiterhin im Experte-Tab für .set/.remove auf jedem Menü — ob und wo das dort ebenfalls falsch zuordnen kann, ist nicht untersucht. Sollte bei unerklärlichem Verhalten dort (falsches Item geändert/gelöscht) als erste Verdachtsquelle geprüft werden.
  • WLAN-.set-Pfad (M5) weiterhin ungetestet gegen echte Hardware — nur der "kein WLAN"-Zweig ist bestätigt (zwei Testgeräte, beide ohne WLAN-Chip). Sicherheitsprofil-Anlage + SSID/Passwort-.set auf einem echten /interface wireless-Interface noch nie live gelaufen.
  • Firewall-Regeln mit korrektem WAN-Interface noch nicht erneut bestätigt — erledigt: beim M8-App-Test (siehe oben) lief ein voller FirewallConfig-Apply mit ether1 als WAN erneut durch, /ip firewall filter print zeigte alle Basisregeln (established/ related, invalid-drop, ICMP-Accept, WAN-Drop) korrekt mit ether1.
  • Neuer RouterOS-WiFi-Treiber (/interface wifi, wifiwave2/802.11ax) nicht unterstützt — wird erkannt und im UI erklärt, aber nicht konfiguriert.
  • Kein garantiertes Auto-Rollback bei Verbindungsabbruch während "Jetzt anwenden" — nur Backup-vorher + Bestätigungspflicht.
  • App ist ad-hoc signiert, nicht notarisiert — beim ersten Start einer frisch nach /Applications kopierten Version zeigt macOS die "nicht verifizierter Entwickler"-Warnung (Rechtsklick → Öffnen nötig).
  • SSH-Hostkey-TOFU ungetestetbestätigt gegen echte Hardware: erste Verbindung zeigte den "Unbekannter SSH-Schlüssel"-Dialog, nach Bestätigen + Trennen + erneutem Verbinden lief es ohne Rückfrage durch.
  • Experte-Tool (M10): mehrere kuratierte Bereiche noch nicht gegen Hardware bestätigt — WLAN (Legacy- und neuer wifiwave2-Treiber: Testgerät hat keinen WLAN-Chip), Bonding und PPPoE-Client (beide riskant für die eigene Erreichbarkeit/brauchen echte ISP-Zugangsdaten) nur gegen RouterOS-Dokumentation kuratiert, nicht live geprüft. /ip address, /ip dhcp-client, /ip dns, /ip service nur lesend geprüft (Feldnamen bestätigt), keine Schreibtests — Risiko, sich selbst auszusperren (SSH/REST-Dienst) bzw. die WAN-Verbindung zu kappen.
  • Experte-Tool: einige RouterOS-Bereiche bewusst nur generisch, nicht kuratiert — Hotspot, Logging (beide mehrteilig, kein flaches Schema), CAPsMAN, L2TP-/OpenVPN-Server, OSPF/BGP-Routing. Grund: keine verifizierte Quelle für die genauen Feldnamen, lieber generischer Schlüssel/Wert- Zugriff als geratene Felder (siehe Bug 7 oben, warum das eine berechtigte Sorge ist).

M8: Mehrere LAN-Interfaces + Netzwerk-Isolation

Auf Nutzerwunsch: mehrere physische LAN-Interfaces je mit eigenem DHCP-Server (SetupViewModel.lanConfigs: [LanDhcpConfig], analog zum schon vorhandenen VLAN-Listen-Muster), plus ein Isolation-Toggle pro LAN-/VLAN-Eintrag ("Von anderen Netzwerken isolieren"). Bei aktivem Firewall-Grundschutz erzeugt FirewallConfig daraus paarweise Forward-Drop-Regeln zwischen jedem isolierten Netzwerk und allen anderen konfigurierten Netzwerken (beide Richtungen, Pair-Dedup bei gegenseitiger Isolation). Internetzugriff bleibt weiterhin global über die bestehende WAN-NAT-Logik, nicht pro Netzwerk abschaltbar (bewusst außerhalb Scope).

Nebenbei behoben: VlanStepView behauptete vorher im Hilfetext bereits Isolation vom Hauptnetzwerk, ohne dass eine einzige Firewall-Regel das durchsetzte — reiner Text ohne Wirkung. Jetzt ist Isolation ein echter, optionaler Schalter mit tatsächlicher Regel-Erzeugung.

Live gegen Hardware verifiziert (2026-09-15) — zuerst manuell per SSH nachgebaut (siehe "Zum Sessionende" oben), danach über den App-Wizard selbst (ether4, Experte-Modus). iMac ↔ Laptop erreichen sich über die Netzgrenze nicht mehr, Internet + DNS funktionieren auf beiden Seiten weiter. Dabei drei App-Bugs gefunden und gefixt (Bug 2224, siehe Bug-Liste oben) — alle drei Voraussetzung dafür, dass ein per Wizard eingerichtetes isoliertes Netz auf einem bereits konfigurierten bzw. werksseitig gebridgten Router überhaupt funktioniert: fehlende "LAN"-Interface-Listen-Mitgliedschaft (DNS zum Router blockiert), fehlende Idempotenz bei erneutem Wizard-Lauf (Apply brach sofort ab), und ein nie aus der Bridge gelöstes physisches Interface (RouterOS verwarf die Isolationsregeln selbst als ungültig). Weiterhin unverändert seit vorher: FirewallConfigTests (isoliertes Netzwerk gegen nicht-isoliertes, gegenseitige Isolation, keine Isolation).

M9: Einfach/Experte-Modus im Einrichten-Wizard

Nutzerwunsch: zwei Modi statt einem Wizard-Durchlauf für alle. Neuer erster Schritt SetupStep.mode (ModeStepView, Segmented Picker).

  • Einfach: WAN → LAN (nur ein Netzwerk, kein Hinzufügen/Entfernen, kein Isolations-Toggle) → WLAN → Firewall (Grundschutz fest an, kein Toggle) → Review. VLAN-Schritt wird komplett übersprungen.
  • Experte: unveränderter voller Flow (Multi-LAN, VLAN, Isolation, Firewall-Toggle) — das, was vorher der einzige Wizard-Pfad war.

Beim Umschalten auf Einfach werden evtl. im Expertenmodus gesetzte Werte zurückgesetzt (SetupViewModel.applySimpleModeConstraints). Navigation (goNext/goBack) von linearem SetupStep.rawValue+1 auf expliziten switch umgebaut (nötig wegen bedingtem VLAN-Skip). Nur gegen Unit-Tests

  • Compile/Test-Build verifiziert, nicht gegen echte Hardware (der Live-Test dieser Session lief komplett über direktes SSH, nicht über die App-UI).

M10: Experte-Tab — generischer RouterOS-Konfigurationszugriff

Nutzerwunsch: "Zugriff auf alle Funktionen, was RouterOS bietet ... alles was du findest, der komplette Umfang". RouterOS hat 300+ Menüs (siehe /console inspect / CLI-Referenz) — statt für jedes einzelne ein Swift-Modell zu bauen (unrealistisch), zweistufige Architektur:

  1. Generischer Motor (deckt technisch jeden Menüpfad ab): RouterOSMenuItem + RouterOSCliParser.parseGenericItems/ parseSingletonItem, RouterOSCommand.remove (neu, neben add/set), ConnectionService.fetchMenuItems + Transport-Implementierungen. Im UI: freies "eigener Menüpfad"-Eingabefeld — jeder RouterOS-Pfad ist erreichbar, auch ohne kuratiertes Schema, dann als rohes Schlüssel/Wert-Formular.
  2. Kuratierte Schemas (RouterOSSchemaCatalog) mit echten Formularfeldern, Tooltips, Erklärtext und Warnhinweisen: die komplette Firewall-Familie (Filter/NAT/Mangle/Raw/Adress-Listen — der explizit genannte Schwerpunkt), dazu Interfaces (Bridge, VLAN, WireGuard+Peers, PPPoE-Client, Bonding), IP (Adressen, Pools, DHCP-Server+Netzwerk, DHCP-Client, DNS, Verwaltungsdienste, Routen), VPN (PPP-Secret/Profile), WLAN (Legacy + neuer wifiwave2-Treiber + Sicherheitsprofile), Queues (Simple/Tree), System (Identity/Clock/NTP/Scheduler/Skripte/Nutzer), Werkzeuge (Netwatch/E-Mail). Bewusst nicht kuratiert (siehe oben): Hotspot, Logging, CAPsMAN, L2TP-/OVPN-Server, OSPF/BGP.

Live gegen den hEX-Testrouter verifiziert (Add→Print→Set→Remove- Zyklus je Familie, Router danach nachweislich unverändert — Config-Export vorher/nachher byte-identisch bis auf Zeitstempel): Firewall Filter/NAT/Mangle/Raw/Adress-Listen, VLAN, WireGuard+Peers, IP-Pool, DHCP-Server+Netzwerk, statische Route, PPP-Secret/Profile, Queue Simple, Scheduler+Skript, Netwatch, sowie der neue Singleton-Pfad (/system identity). Dabei zwei Bugs gefunden und gefixt, siehe Bug 7/8 oben. Nicht gegen Hardware getestet: WLAN (kein Chip im Testgerät), Bonding, PPPoE-Client (beide riskant/brauchen echte Zugangsdaten), Schreibzugriff auf /ip address//ip dhcp-client//ip dns/ /ip service (nur lesend geprüft, Risiko Selbstaussperrung/WAN-Ausfall).

Nachtrag (Nutzer-Feedback-Runde, gleiche Session): Nutzer meldete zwei reale Probleme beim gedanklichen/App-Testen des DHCP-Server-Formulars — Bug 9 (Adress-Pool-Feld ohne Auswahl) und Bug 10 (fehlgeschlagenes Anlegen ohne jede Fehlermeldung). Beide gefunden und gefixt (siehe oben). Bug 10s Fix wurde erneut end-to-end live bestätigt: komplette Kette VLAN → IP-Adresse → Pool → DHCP-Server → DHCP-Netzwerk auf einem freien Interface (statt des zuvor kollidierenden ether5) angelegt, kein Fehlertext (weil tatsächlich erfolgreich), anschließend vollständig wieder entfernt.

Zweiter Nachtrag — jetzt tatsächlich durch die App-UI getestet (Nutzer selbst, gleiche Session): Nutzer legte VLAN + DHCP-Server über den Experte-Tab live an, fand dabei drei weitere reale Bugs (11: yes/no, 12: fehlende Lösch-Buttons, 13: eingefrorene Interface-Auswahl, siehe oben) — alle gefunden, gefixt, nach jedem Fix Release neu gebaut und deployt, vom Nutzer direkt in der App erneut getestet. Damit ist die SwiftUI-Oberfläche des Experte-Tabs (VLAN anlegen, einem DHCP-Server zuweisen, Adress-Pool wählen, löschen) erstmals tatsächlich manuell durchgeklickt und vom Nutzer als funktionierend bestätigt — nicht mehr nur die zugrundeliegende RouterOS-Syntax per SSH. Bestätigt: RouterOS erlaubt nur einen DHCP-Server pro Interface — der Versuch, ihn direkt auf ether5 zu setzen, scheiterte zurecht (dort hängt bereits dhcp5 aus einer früheren Session), auf dem VLAN-Interface funktioniert es.

Dritter Nachtrag — Sicherheits-Angleichung ans übrige App-Verhalten: Nutzer fragte, ob Experte-Tab-Änderungen sofort geschrieben werden oder eine Bestätigung braucht — Antwort war "sofort, kein eigener Bestätigungs-Button" (nur Löschen hatte einen Dialog). Das wich vom Einrichten-Wizard ab (Übersicht + Pflicht-Backup vor "Jetzt anwenden"). Auf Wunsch angeglichen:

  • Bestätigungsdialog vor Anlegen/Ändern: "Anlegen"/"Speichern" öffnet jetzt zuerst einen .confirmationDialog mit dem exakten Befehl, erst ein zweiter Klick führt ihn aus (ExpertMenuDetailView).
  • Auto-Backup einmal pro Verbindung: ConnectionService. hasExpertToolBackedUpThisSession (zurückgesetzt bei disconnect()), ExpertViewModel.ensureSessionBackup() zieht vor dem allerersten Schreibvorgang im Experte-Tab automatisch ein Backup (wie der Wizard), aber nur einmal pro Sitzung, nicht vor jeder einzelnen Änderung (ein Experte-Tab-Durchgang sind oft viele kleine Edits hintereinander).

Vierter Nachtrag — Dauer-Editor: Nutzer fragte, in welcher Einheit die DHCP-Server-Lease-Zeit angegeben wird. Live geprüft: RouterOS- Zeitwerte sind Zahl+Einheit (s/m/h/d/w, kombinierbar wie 1d12h30m), eine bare Zahl gilt als Sekunden (bestätigt: 90 → intern 1m30s). Nutzerwunsch danach: "ein sinnvoll gestaltetes Auswahlfenster ... ein Tooltip wäre hilfreich" statt nur Tooltip-Text. Neuer Feldtyp RouterOSFieldSchema.Kind.duration + DurationFieldEditor (vier Stepper: Tage/Std/Min/Sek, parst bestehende Werte beim Öffnen, schreibt beim Ändern die RouterOS-Suffix-Form zurück) — angewendet auf DHCP-Server "Lease-Zeit" und Adress-Listen "Ablaufzeit". Nutzer bestätigte danach: "funktioniert".

M15: Übersicht-Tab — Diagramm-Fixes + Direktes Bearbeiten

Auf Nutzerwunsch zwei Themen in einer Session: erst mehrere echte Bugs im bestehenden Übersicht-Diagramm gefunden und gefixt, danach eine neue Fähigkeit ergänzt — ein Knoten direkt anklicken und bearbeiten, statt nur anzusehen.

Diagramm-Fixes (alle live an "Verbindungslinien werden links abgeschnitten" / "Hover springt ständig" diagnostiziert):

  • Erst versucht, das Diagramm beim Öffnen automatisch ans Fenster anzupassen (GeometryReader + einmalig gesetzter Scale) — hat den ScrollView-Scrollzustand durcheinandergebracht (Inhalt verschob sich nach dem ersten Layout, oberer Teil nicht mehr erreichbar). Wieder komplett zurückgezogen, zurück zur ursprünglichen manuellen Zoom-Steuerung (-/100%/+).
  • Echter Geometrie-Bug in EdgesCanvas: VLAN-/Bridge-Port-Kanten laufen innerhalb derselben Spalte (Interface → Interface), eine Firewall/NAT-Kante läuft rückwärts (rechts nach links) — die Kurven-Berechnung nahm aber immer "rechts aus dem Start-, links in den Zielknoten" an, wodurch die Kontrollpunkte bei diesen Fällen weit über den Canvas hinausschossen (auch ins Negative) und am linken Rand abgeschnitten wurden. Fix: Kurvenaufbau jetzt richtungsabhängig (gleiche Spalte → kleine Ausbuchtung rechts, rückwärts → gespiegelte S-Kurve).
  • Hover-Instabilität: Mehrere Kanten verlassen denselben Knotenpunkt am selben Punkt — dort sind sie fast gleich nah am Cursor, kleinste Mausbewegung ließ die Auswahl ständig springen. Fix in zwei Schritten: (1) Trägheit — einmal gehoverte Kante bleibt aktiv, solange der Cursor nah genug bleibt, statt bei jedem Event neu die global nächste zu wählen; (2) Hover-Kandidaten auf die gerade hervorgehobenen (verbundenen) Kanten beschränkt, statt gegen alle Kanten im Diagramm zu prüfen.
  • Klick-vs-Hover-Priorität vertauscht: highlightNodeID ließ Hover über Klick gewinnen — beim Nachfahren einer Linie zu ihrem Ziel streifte der Cursor oft andere, unbeteiligte Karten, deren Hover dann die Hervorhebung des angeklickten Knotens verdrängte. Fix: Klick- Auswahl gewinnt jetzt über Hover (Hover dient nur noch als Vorschau, solange nichts ausgewählt ist).
  • Verbindungslinie anklicken zeigt jetzt ausführliche, verständliche Infos im rechten Panel (EdgeDetailView: Klartext-Erklärung pro Verbindungsart, "Von"/"Nach" mit Sprung zum jeweiligen Knoten) — vorher gab es nur den kleinen Hover-Tooltip mit den bloßen Rohdaten.

Direktes Bearbeiten: OverviewNode.EditTarget (Menüpfad, REST-Pfad, RouterOS-eigene .id) an jedem Knoten, der sich sauber auf genau ein Router-Item abbildet — IP-Adressen, Pools, DHCP-Server/-Netzwerke/ -Clients, Routen, Firewall-Filter-/NAT-Regeln, WireGuard-Peers. Klick auf "Bearbeiten" im Detailpanel öffnet dieselbe ExpertItemEditView-Sheet wie der Experte-Tab (dafür private entfernt, jetzt modulweit sichtbar), über eine eigene ExpertViewModel-Instanz — gleiche Felder/ Tooltips, gleicher Bestätigungsdialog, gleicher Schreibpfad, keine Duplikation der Schema-/Write-Logik. Nach dem Speichern lädt die Übersicht automatisch neu. Bewusst nicht abgedeckt: physische/VLAN/ Bridge-Interfaces (Felder über mehrere typ-spezifische Menüs verteilt) und Adress-Listen-Knoten (fassen mehrere Einträge zusammen, kein 1:1- Bezug zu einem Router-Item).

Live gegen Hardware verifiziert — Nutzer bestätigte Kommentar- Änderung an einer Firewall-Regel über den neuen Bearbeiten-Weg ("funktioniert"). Direkt danach beim Weitertesten zwei echte Bugs in der gemeinsamen Bearbeiten-Logik gefunden (Bug 25, 26 — siehe Bug-Liste oben): geleertes Textfeld blieb bestehen, Route-Bearbeiten scheiterte an einem nur-lesbaren Feld (immediate-gw). Beide betreffen ExpertViewModel. pendingCommand und damit auch den Experte-Tab direkt, nicht nur den neuen Übersicht-Weg — gefixt, alle 54 Unit-Tests grün (inkl. neuer ExpertViewModelTests und OverviewGraphTests. testEditableNodesCarryTheirRouterOSMenuAndItemID).

Stand der Milestones

  • M1M4: Projektgerüst, Connect, Backup, WAN/LAN/DHCP, VLAN — gegen echtes Testgerät verifiziert.
  • M5: WLAN-Schritt — nur "kein WLAN"-Zweig verifiziert, .set-Pfad weiterhin ungetestet (kein WLAN-Chip auf beiden Testgeräten bisher).
  • M6: Firewall-Schritt — live angewendet, dabei Bug 6 gefunden+gefixt; Interface-Auswahl danach erneut bestätigt, Regel-Ergebnis mit korrektem WAN-Port aber noch nicht erneut kontrolliert.
  • Zusatzfeatures nach M6: Tooltips auf allen Konfigurationsfeldern, wählbarer Backup-Ordner (Standard: ~/Library/Application Support/...), Schnell-Backup-Button im Verbinden-Tab, "Werkseinstellungen wiederherstellen" (Gefahrenzone im Sicherungen-Tab, /system reset-configuration no-defaults=no), eigenes App-Icon ("Signal Router"-Motiv).
  • 🔶 M7: Härtung — SSH-Hostkey-TOFU fertig, gegen echte Hardware bestätigt (inkl. neuem "Trennen"-Button im Verbinden-Tab, der dafür nötig wurde). Fehlerzustände/Politur und REST-Schreibpfad-Verifikation gegen ein Gerät mit aktivem www-ssl stehen noch aus.
  • M8: Mehrere LAN-Interfaces mit eigenem DHCP + Netzwerk-Isolation (eigene Firewall-Regeln pro LAN/VLAN) — live gegen Hardware verifiziert, sowohl manuell (SSH) als auch über den App-Wizard selbst (ether4), dabei drei App-Bugs gefunden+gefixt (Bug 2224, siehe oben). Isolation, DNS und Internet vom Nutzer am echten Gerät bestätigt.
  • 🔶 M9: Einfach/Experte-Modus im Einrichten-Wizard — gebaut, Compile/ Unit-Test-verifiziert. UI (Modusumschalter selbst) noch nicht manuell durchgeklickt — nur M10s Experte-Tab wurde das (siehe M10).
  • M10: Experte-Tab (generischer RouterOS-Zugriff + kuratierte Firewall- und weitere Schemas) — Kern-Logik live gegen Hardware verifiziert, UI (VLAN anlegen/löschen, DHCP-Server mit Adress-Pool zuweisen) vom Nutzer selbst in der laufenden App bestätigt funktionierend — sieben reale Bugs dabei gefunden und gefixt (Bug 713), danach zusätzlich Bestätigungsdialog vor Anlegen/Ändern + einmaliges Auto-Backup pro Sitzung (Angleichung an den Wizard) sowie ein Dauer-Editor (Tage/Std/ Min/Sek statt Freitext) für Lease-/Ablaufzeit-Felder ergänzt, alles vom Nutzer bestätigt. Weiterhin ungetestet: WLAN (kein Chip im Testgerät), Bonding, PPPoE-Client (beide riskant/brauchen echte Zugangsdaten), Schreibzugriff auf /ip address//ip dhcp-client//ip dns/ /ip service (Risiko Selbstaussperrung/ WAN-Ausfall).
  • M11: Übersicht-Tab (Diagramm: Interfaces→IP→DHCP/Pools→Routen→ Firewall/NAT, echte Referenz-Kanten, nach Verbindungsart eingefärbt, Hover/Klick-Hervorhebung) — live gegen Hardware verifiziert, vom Nutzer bestätigt ("das gefällt mir schon sehr gut"). War anfangs rein lesend, seit M15 kann ein Knoten direkt bearbeitet werden (siehe unten) — deckt weiterhin bewusst nicht ab: VPN/WLAN-Sicherheitsprofile/ Queues/System/Werkzeuge/Mangle/Raw (siehe OverviewGraph. unmappedAreas), weiterhin nur über den Experte-Tab erreichbar.
  • M12: Geräte-Tab (LAN-Scanner: DHCP-Leases+ARP+Bridge-Host, eine Tabelle pro physischem Port, "Feste IP zuweisen" für dynamische Leases, "Feste Zuweisung entfernen" für den Rückweg) — live gegen Hardware verifiziert und vom Nutzer bestätigt funktionierend ("das funktioniert jetzt super" / "das hat sofort funktioniert"), nach vier gefundenen und gefixten Bugs beim Hinweg (Bug 1417, siehe oben) — die aufwendigste Live-Verifikation aller Milestones bisher, mehrere Diagnose-Runden mit Rohdaten-Vergleichen und direkten Router-Gegenchecks (SSH-CLI + Winbox). Rückweg ("Feste Zuweisung entfernen") lief dagegen beim ersten Versuch fehlerfrei — RouterOS-Doku vorher per Recherche-Agent geprüft (kein make-dynamic, nur remove + Client muss neu verbinden), nicht geraten. Nutzer bestätigte den vollen Kreislauf (fest zuweisen → entfernen → Kabel/WLAN trennen+verbinden → wieder dynamisch).
  • M13: Backup-Wiederherstellung — BackupService.restoreBackup (SFTP-Upload des .rsc-Skripts + RouterOS' dokumentierter reset-configuration no-defaults=yes run-after-reset=...-Restore-Weg), Modell-Abgleich vor dem Bestätigungsdialog (/system routerboards model-Feld gegen die # model = ...-Kopfzeile der Sicherung, blockt bei Mismatch komplett), Gefahrenzone-UI mit explizitem Brick-Warnhinweis im Sicherungen-Tab. Live gegen Hardware verifiziert — erster Testlauf sperrte den Router komplett aus (Bug 18: keine Passwörter im Export + no-defaults=yes löscht auch den Werks-Admin), Fix (Login-Erhalt vorangestellt) danach vom Nutzer bestätigt ("das hat funktioniert"). Restore-Weg selbst (Modell-Match, SFTP-Upload, reset+run-after-reset) lief dagegen schon im ersten Versuch fehlerfrei.
  • M14: Update-Check im Verbinden-Tab — Routerboard-Vollinfo (/system routerboard: Modell/Revision/Seriennummer/Firmware-Typ/ Firmware-Versionen), RouterOS-Software-Update-Check (/system package update: Kanal/installiert/neueste Version/Status, "Jetzt prüfen"+"Update installieren"), Routerboard-Firmware-Update ("Firmware aktualisieren") und "Jetzt neu starten" danach. Alle vier Aktionen live gegen Hardware verifiziert, inkl. der zuvor offenen Frage, ob /system routerboard upgrades normalerweise interaktive Bestätigung ("Do you really want to upgrade firmware? [y/n]") den nicht-interaktiven SSH-Weg dieser App blockiert — tut es nicht, Firmware-Update lief beim ersten Versuch sauber durch ("Firmware upgraded successfully, please reboot..."), ebenso der Neustart danach. Dabei ein weiterer Bug im generischen Lese-Pfad gefunden (Bug 20). Anschließend Design-Durchgang: Verbinden-Detailseite/Sicherungen/ Geräte von nackter List auf Form+.formStyle(.grouped) (macOS-typischer "Karten"-Look) umgestellt, damit einheitlich mit Wizard/Experte-Tab. Dark Mode auf Nachfrage geprüft und ohne Codeänderung bestätigt funktionierend (keine feste Farbwerte/ Appearance-Überschreibung im Code).
  • M15: Übersicht-Tab — mehrere Diagramm-Bugs gefixt (Verbindungslinien am linken Rand abgeschnitten bei gleicher-Spalte-/Rückwärts-Kanten, nervöses Hover, Klick-Auswahl wurde von Hover unbeteiligter Karten verdrängt) plus neue Fähigkeit: Knoten (IP-Adresse, Pool, DHCP-Server/ -Netzwerk/-Client, Route, Firewall-Filter-/NAT-Regel, WireGuard-Peer) direkt über dieselbe Sheet wie im Experte-Tab bearbeiten und zurückschreiben. Dabei zwei Bugs in der gemeinsamen Bearbeiten-Logik gefunden+gefixt (Bug 25, 26 — betreffen auch den Experte-Tab direkt): geleertes Feld blieb bestehen statt geleert zu werden, Route-Bearbeiten scheiterte an einem nur-lesbaren Feld (immediate-gw). Live gegen Hardware verifiziert — Nutzer bestätigte Kommentar-Änderung an einer Firewall-Regel über den neuen Weg ("funktioniert").

Nächste Schritte

  1. WLAN (M5) an einem Gerät mit echtem WLAN-Chip nachholen.
  2. Rest von M7: Fehlerzustände/Politur, REST-Schreibtest an einem Gerät mit aktivem www-ssl.
  3. M9 UI (Einfach/Experte-Modusumschalter im Einrichten-Tab selbst) noch manuell durchklicken — M10s Experte-Tab wurde bereits vom Nutzer bestätigt (siehe oben), der Moduswechsel im Wizard noch nicht.
  4. M10: WLAN-Schemas (an Gerät mit WLAN-Chip), Bonding, PPPoE-Client (mit echten oder Test-ISP-Zugangsdaten) noch gegen Hardware verifizieren.
  5. Optional, kleinere Politur: den neuen Dauer-Editor (RouterOSFieldSchema.Kind.duration) auch auf weitere Zeitwert-Felder anwenden, die bisher nur Text mit Beispiel-Tooltip sind — WireGuard- Peer "Keepalive", Scheduler "Intervall", Netwatch "Prüf-Intervall". Optional auch für den Geräte-Tab, falls dort künftig Zeitfelder dazukommen.
  6. Firewall-NAT-/Filter-add-Befehle sind bei wiederholtem Wizard-Lauf weiterhin nicht idempotent — im Gegensatz zu den bei Bug 2224 gefixten Menüs lässt RouterOS identische NAT-/Filter-Regeln mehrfach zu (kein Fehler, kein Abbruch), aber jeder erneute Wizard-Durchlauf häuft doppelte Regeln an. Nicht blockierend (heute so live beobachtet, Apply lief trotzdem durch), aber auf Dauer Regel-Bloat — bei Gelegenheit auf "vorhandene identische Regel überspringen" umstellen.
  7. .id-Positions-Überlagerung (fetchMenuItems) auf weitere Menüs prüfen — live als falsch bestätigt für /ip dhcp-server lease (Bug 15). Betrifft potenziell jedes .set/.remove im Experte-Tab. Am ehesten zu prüfen: bei einem Menü mit mehreren gleichzeitig vorhandenen Einträgen :put [<Pfad> find] und <Pfad> print terse unabhängig ausführen und die Reihenfolgen von Hand vergleichen.
  8. Router wurde zwischen M12 und M13 vom Nutzer komplett auf Werkseinstellungen zurückgesetzt ("damit wir sauber weitermachen können"), und im Zuge des ersten M13-Testlaufs (Bug 18, Login-Sperre) nochmal per Hardware-Reset zurückgesetzt. test-vlan/testpool/ testdhcp/dhcp5/pool5 aus früheren Sessions existieren dadurch nicht mehr — nicht mehr relevant, nicht danach suchen. Aktueller Stand: Kay-Uwes-iMac an ether2 und Test-Laptop DEDELLB2M6GK3 an ether3, beide dynamisch, plus was auch immer die zuletzt erfolgreich getestete Backup-Wiederherstellung zurückgespielt hat, und die Routerboard-Firmware wurde per M14 aktualisiert (Neustart ausgeführt) — vor Annahmen über den genauen aktuellen Stand lieber neu per Geräte-/Übersicht-/Verbinden-Tab prüfen statt auf ältere Einträge hier zu vertrauen.
  9. fetchMenuItemss Output-Text-Fallback (Bug 20) auf weitere Singleton-Menüs prüfen — live nur für /system routerboard bestätigt gebraucht zu werden (/system identity etc. liefen schon vorher über den Exception-Zweig, siehe Bug 8). Nicht geprüft, ob es noch andere Menüs gibt, die den Fehler auf eine DRITTE Art melden (weder Exception noch im Output-Text) — bei einer leer bleibenden Liste in Übersicht/Geräte/Experte-Tab als erste Verdachtsquelle prüfen.
  10. Ausführlichere Tooltips im Experte-Tab — erledigt: alle RouterOSFieldSchema.help-Texte in RouterOSSchemaCatalog.swift überarbeitet, jedes Adress-/Netz-/Bereichs-Feld hat jetzt ein konkretes Beispiel (z.B. "192.168.88.1/24" oder "192.168.88.10-192.168.88.254"), vorher leere help: ""-Texte gefüllt (u.a. bei Mangle/Raw-Regeln, den meisten "Deaktiviert"- Feldern, WireGuard-Peer-Feldern, System-/Tool-Menüs). Dabei zwei weitere Nutzerwünsche ergänzt: eine fette Überschrift (Menüname + Kategorie) oben in ExpertMenuDetailView, da vorher nicht erkennbar war, in welcher Sektion man sich befindet; und eine hellblaue Hervorhebung des gerade geöffneten Eintrags in der linken Liste (ExpertView) — dafür .listRowBackground auf macOS' Sidebar-Stil verworfen (wird vom System überschrieben/nicht sichtbar), stattdessen Hintergrund direkt am Zeileninhalt (Text in RoundedRectangle- Fill) gesetzt. Alles vom Nutzer live in Xcode bestätigt ("passt gut").
  11. M15-Bearbeiten-Funktion deckt Interfaces und Adress-Listen bewusst nicht ab — physische/VLAN/Bridge-Interfaces verteilen ihre Felder über mehrere typ-spezifische RouterOS-Menüs (/interface ethernet, /interface bridge, /interface vlan, ...), ein sauberes OverviewNode.EditTarget bräuchte pro Interface-Typ eine eigene Zuordnung statt der generischen /interface. Adress-Listen-Knoten fassen mehrere Einträge in einem Knoten zusammen, kein 1:1-Bezug zu einem Router-Item — bräuchte eine andere UI (Liste statt Formular). Beides bei Bedarf nachrüstbar, für diese Session bewusst zurückgestellt.

Gitea-Remote origin ist eingerichtet und wird laufend gepusht (siehe oben) — dieser Hinweis war veraltet, korrigiert am 2026-09-15.

/Applications/RouterOS Assistant.app ist der aktuell installierte Release-Build, auf dem Stand des jeweils letzten Commits auf main (App-Icon "Signal Router"). Nach jeder weiteren Code-Änderung muss dieser Build-und-Kopier-Schritt (siehe oben) wiederholt werden, damit die installierte App aktuell bleibt.