Files
IBKRTrader/docs/ANLEITUNG-WebUI.md
T
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

7.5 KiB

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

<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]/.