From 7299148dc46547634a7dfa484a4b2ea4e25d5007 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 6 Sep 2026 13:23:31 +0200 Subject: [PATCH] API-First als Regel ausgeschrieben, Anleitung fuer die WebUI-Umsetzung Die Regel galt seit dem Umbau auf Avalonia, stand aber nirgends: Der Ausgabeweg ist waehlbar - Desktop, Web oder beides gleichzeitig -, und daraus folgt genau eine harte Vorgabe: Fachlichkeit lebt in einem Core-Dienst, nie in einer Ansicht. Ein Fenster und ein Endpunkt sind zwei Zugaenge auf denselben Dienst. In den Grundregeln steht jetzt die Prueffrage dazu (koennte ein zweiter Ausgabeweg dasselbe tun, ohne diese Zeile zu kopieren?), die Vorgabe, dass Dienst und Endpunkt im selben Zug wie das Fenster entstehen, und der Daemon als Gegenprobe: Was sich kopflos nicht bedienen laesst, haengt zu sehr am Fenster. docs/ANLEITUNG-WebUI.md richtet sich an den Agenten, der #490 uebernimmt. Enthaelt den gemessenen Bestand, die Reihenfolge der drei Bloecke, die Tabelle der 24 Endpunkte mit ihren Diensten, die beiden nicht-mechanischen Stellen (Logs als Push, Settings mit Zugangsdaten) und die Grenzen - vor allem: keine Fachlogik anfassen, keine schreibenden Endpunkte ohne Anmeldung. Ein Punkt darin ist Architektur und keine Geschmacksfrage: Der Core darf die Module nicht kennen, also koennen Modul-Routen nicht im Core registriert werden. Sie gehoeren ins Hosting-Projekt, das die Module ohnehin kennt - so wie die Modulfenster zentral in App/Shell/ModuleViews.cs stehen und nicht im Modul. 473 Tests gruen. Co-Authored-By: Claude Opus 5 --- .agents/rules/grundregeln.md | 16 ++++ docs/ANLEITUNG-WebUI.md | 152 +++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 docs/ANLEITUNG-WebUI.md diff --git a/.agents/rules/grundregeln.md b/.agents/rules/grundregeln.md index 5b59d1c..4dfbb9f 100644 --- a/.agents/rules/grundregeln.md +++ b/.agents/rules/grundregeln.md @@ -36,6 +36,22 @@ Kultur: Jede Zahl-/Datumsformatierung und jedes Parsen braucht einen ausdrückli Tests: eigenes Projekt IBKRTrader.Tests (xUnit + NSubstitute + FluentAssertions), NUR Unit-Tests, alles Externe (DB/IBKR/Scraper) gemockt. DoD jeder Phase: `dotnet test` grün + Build sauber. +API-First (gilt seit dem Umbau auf Avalonia, seit 2026-09-06 ausgeschrieben): + +Der Ausgabeweg ist wählbar – Desktop-UI, Web-UI oder beide gleichzeitig. Daraus folgt genau + eine harte Regel: **Fachlichkeit lebt in einem Core-Dienst, nie in einer Ansicht.** Ein Fenster + und ein REST-Endpunkt sind zwei Zugänge auf denselben Dienst, und beide dürfen nur formatieren. +Prüffrage vor jedem UI-Code: Könnte ein zweiter Ausgabeweg dasselbe tun, ohne diese Zeile zu + kopieren? Lautet die Antwort nein, gehört die Zeile in den Core. +Jede neue Funktion bekommt ihren Core-Dienst UND ihren Endpunkt, im selben Zug wie ihr Fenster. + Nicht nachträglich: Endpunkte, die später in einem Block nachgezogen werden, kosten mehr und + weichen ab. Vorbild ist `Core/Positions/PositionManager.cs` – ein Dienst, drei Zugänge + (Shell-Fenster, Daemon-Schalter, `GET /api/positions`). +Der Daemon ist die Gegenprobe: Lässt sich eine Funktion kopflos nicht bedienen, hängt zu viel + am Fenster. Der Testbetrieb läuft als systemd-Dienst – dort gibt es keines. +Schreibende Endpunkte erst mit Anmeldung. Ohne sie wäre ein Eingriff in die Bücher möglich, den + niemand belegen kann; solange keine steht, bleibt die REST-Schnittstelle lesend. + UI-Grundmodell (Launcher-Prinzip nach Polytrader): LauncherWindow = Basis-Fenster mit einer Schaltfläche je registrierter Ansicht und dem diff --git a/docs/ANLEITUNG-WebUI.md b/docs/ANLEITUNG-WebUI.md new file mode 100644 index 0000000..6a4e176 --- /dev/null +++ b/docs/ANLEITUNG-WebUI.md @@ -0,0 +1,152 @@ +# Anleitung: Web-UI als zweiter Ausgabeweg + +**Für den Agenten, der diese Aufgabe übernimmt.** Bugtracker-Item `#490` im Deploymentcenter, +Projekt `ibkrtrader`. Lies zuerst `.agents/rules/grundregeln.md` — die Regeln dort haben Vorrang +vor allem, was hier steht. + +--- + +## Der Auftrag in einem Satz + +IBKRTrader hat heute eine Avalonia-Desktopoberfläche. Es soll daneben eine Weboberfläche geben — +**wählbar, auch beide gleichzeitig**. Kein Ersatz: Die Shell bleibt. + +## Die eine Regel, aus der alles folgt + +> **Fachlichkeit lebt in einem Core-Dienst, nie in einer Ansicht.** + +Ein Fenster und ein REST-Endpunkt sind zwei Zugänge auf denselben Dienst. Beide dürfen +formatieren, sonst nichts. Wenn du beim Bauen einer Route merkst, dass du eine Berechnung, eine +Prüfung oder eine Regel schreibst — halt an. Die gehört in den Core, und dann kann das +Avalonia-Fenster sie auch benutzen. + +Die Prüffrage: *Könnte ein zweiter Ausgabeweg dasselbe tun, ohne diese Zeile zu kopieren?* + +Vorbild ist `src/IBKRTrader.Core/Positions/PositionManager.cs`: ein Dienst, drei Zugänge — +Shell-Fenster, Daemon-Schalter, `GET /api/positions`. Sieh dir die drei an, bevor du anfängst. +Der Endpunkt darin ist rund dreißig Zeilen; so soll jeder aussehen. + +--- + +## Was schon da ist + +| | | +|---|---| +| `Core/Workers/BuiltIn/WebApiService.cs` | `HttpListener`, Port `Webserver.Port + 1`, 3 Routen | +| `Core/Workers/BuiltIn/WebserverService.cs` | zweiter `HttpListener` auf `Webserver.Port`, liefert nur einen Status-JSON | +| `WebserverSettings` | `Port` (Vorgabe 5001), `Enabled` (Vorgabe false) | +| Routen | `GET /api/status`, `GET /api/workers`, `GET /api/positions` | + +Beide Listener sind Worker vom Typ `Service` und laufen in beiden Betriebsformen — Shell **und** +Daemon. Das ist gut so und soll so bleiben: Die Weboberfläche muss auf der kopflosen Instanz +funktionieren, dort ist sie am meisten wert. + +Die Oberfläche selbst umfasst heute rund 585 Zeilen AXAML und 1.370 Zeilen Code-behind in zehn +Fenstern. Sie sind dünn — jedes holt Daten aus einem Core-Dienst und verdrahtet Steuerelemente. +**Der teure Teil einer Portierung ist damit schon erledigt.** Du brauchst keine Fachlogik zu +verstehen oder nachzubauen, sondern nur einen zweiten Zugang zu bestehenden Diensten. + +--- + +## Reihenfolge + +### 1. HTTP-Unterbau ersetzen + +Der `HttpListener` mit handgeschriebenem `switch`-Router trägt drei Routen, aber keine Oberfläche: +localhost-gebunden, ohne Anmeldung, ohne statische Dateien, mit `catch { }` um jede Antwort. + +Nimm ASP.NET Core Minimal APIs. Im Hosting-Projekt genügt + +```xml + +``` + +— das SDK bleibt `Microsoft.NET.Sdk`, das Projekt wird kein Web-Projekt. Achte darauf, dass +`dotnet publish` für den Daemon weiter durchläuft; die Zielmaschine braucht dann die +ASP.NET-Core-Runtime (bei self-contained wird sie mitgeliefert). + +Was dabei zusammenkommt: Die beiden Listener werden einer. Ein Host liefert die statischen Dateien +der Oberfläche und die API. + +**Was hier hineingehört, bevor irgendein schreibender Endpunkt entsteht:** eine Anmeldung. Solange +keine steht, bleibt die Schnittstelle lesend — ohne sie wäre ein Eingriff in die Bücher möglich, +den niemand belegen kann. + +### 2. Endpunkte + +Rund 24 gezählt, 3 vorhanden. Sie sind mechanisch: Der Dienst existiert jeweils schon. + +| Ansicht | Dienst | lesend | auslösend | +|---|---|---|---| +| Dashboard | `DashboardService.GetSnapshotAsync` | 1 | — | +| Workers | `WorkerEngine.WorkerInfos` | 1 | `TriggerWorkerAsync` | +| Positionen | `PositionManager.GetOverviewAsync` | ✔ fertig | `ReassignAsync`, `SetLockAsync`, `SetRuleAsync` | +| Orders | `IBrokerOrderBook.GetOpenOrdersAsync` | 1 | `CancelOrderAsync` | +| Logs | `LoggingService` | 1 + Strom | — | +| Settings | `SettingsService` | 1 | speichern | +| Accounting | `AccountingReportService`, `ILedgerRepository`, `IIngestRunRepository` | ~4 | `IngestAllAsync` | +| CongressTrading | `CongressRepository`, `IPortfolioService` | 2 | `TriggerWorkerAsync` | +| Supervisor | `DossierService`, `ISupervisorReportRepository` | 2 | `SupervisorAgent.AskAsync` | + +**Wichtig für die Modul-Endpunkte:** Der Core darf die Module nicht kennen — das ist eine harte +Architekturregel. `IBKRTrader.Hosting` kennt sie (siehe die `ProjectReference`-Liste in +`IBKRTrader.Hosting.csproj`) und ist deshalb der richtige Ort, um Modul-Routen zu registrieren. +Das spiegelt, wie es bei den Fenstern gelöst ist: zentral in `App/Shell/ModuleViews.cs`, nicht im +Modul. Leg eine entsprechende Stelle für Routen an; Core-Routen kommen aus dem Core, Modul-Routen +aus dem Hosting. + +### 3. Frontend + +Hier liegt das Volumen. Neun Ansichten, überwiegend Tabelle plus Werkzeugleiste. Es braucht kein +Rahmenwerk, das darüber hinausgeht. Halte dich an die Spalten, die die Fenster heute zeigen — die +Workers-Ansicht hat ihre Spalten sogar in den Grundregeln festgeschrieben. + +--- + +## Die beiden Stellen, die nicht mechanisch sind + +**Logs.** `LoggingService` meldet Einträge über das Ereignis `EntryWritten`; die Shell hängt sich +ein und färbt selbst. Das ist ein Push. Polling wäre ein Rückschritt gegenüber dem, was die Shell +heute kann — nimm Server-Sent Events. Entscheide dabei ausdrücklich, was bei mehreren offenen +Browsern passiert, und schreib die Entscheidung hin. + +**Settings.** Schreibt `settings.json`, und darin stehen Zugangsdaten zu Datenbank und Broker. +Dieser Endpunkt darf ohne Anmeldung nicht existieren. Er ist der Grund, aus dem Schritt 1 vor +Schritt 2 kommt. Gib Geheimnisse niemals lesend heraus — auch nicht maskiert, wenn sich daraus +etwas ableiten lässt. + +--- + +## Grenzen + +- **Fass keine Fachlogik an.** Kapitalmodell, Risikopfad, Orderstrecke, Abgleich: nicht dein + Auftrag. Fehlt dir dort etwas, melde es im Deploymentcenter, statt es zu umgehen. +- **Module bleiben unberührt.** Sie dürfen weder Avalonia noch einen Webstack referenzieren, und + sie müssen kopflos lauffähig bleiben. Es gibt einen Architekturtest, der das prüft + (`tests/IBKRTrader.Tests/Modules/ModuleBoundaryTests.cs`) — wenn er anschlägt, hat er recht. +- **Keine schreibenden Endpunkte ohne Anmeldung.** Auch nicht „vorläufig". +- **Zeit und Kultur:** Zeitstempel immer UTC persistieren, für Anzeige `AppTimeZone`. Jede + Formatierung braucht einen ausdrücklichen `IFormatProvider`. In JSON gehört ISO-8601 in UTC. + Achtung: `CA1305` greift **nicht** bei interpolierten Zeichenketten — dort musst du selbst + aufpassen (`FormattableString.Invariant`). Das hat uns schon Logdateien mit dem Jahr 1405 + eingebracht. +- **Der Daemon ist die Gegenprobe.** Was du baust, muss auf der kopflosen Instanz laufen. + +## Fertig ist es, wenn + +- `dotnet test` grün und der Build ohne Warnungen ist (das ist die DoD jeder Phase), +- `dotnet publish` für Shell **und** Daemon durchläuft, +- die Oberfläche auf der Testmaschine erreichbar ist und dieselben Zahlen zeigt wie die Shell, +- kein schreibender Endpunkt ohne Anmeldung existiert, +- und für jede Ansicht gilt: Die Shell tut es weiterhin, unverändert. + +## Arbeitsweise + +Der Planungsstand liegt im Deploymentcenter (`https://dc.mhdf.de`, Projekt `ibkrtrader`), nicht in +Dateien. Vor der Arbeit `dc_roadmap`, Item `#490` übernehmen, Fortschritt mit `dc_comment` +festhalten. Findest du unterwegs einen Fehler außerhalb deines Auftrags: melden, nicht nebenbei +beheben. Einzelheiten stehen in `CLAUDE.md`. + +Eine Warnung aus eigener Erfahrung mit dieser Codebasis: Der Daemon schreibt derzeit jede +SQL-Anweisung von EF Core auf stdout (`#392`). `journalctl` ist dadurch praktisch unbrauchbar — +nimm die Logdateien unter `Logs/[Modul]/`.