Files
IBKRTrader/docs/ANLEITUNG-WebUI.md
RichardandClaude Opus 5 7299148dc4 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 <noreply@anthropic.com>
2026-09-06 13:23:31 +02:00

153 lines
7.5 KiB
Markdown

# 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
<FrameworkReference Include="Microsoft.AspNetCore.App" />
```
— 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]/`.