Beide Portierungsdokumente hingen dem Code hinterher. Commit 7f0b05e setzte A1
bis A4 um, ohne eine einzige Markdown-Datei mitzuziehen - der Leitfaden wies die
vier Punkte weiter als offen aus. Ein Agent, der ihn als Arbeitsanweisung nimmt,
haette sie ein zweites Mal gebaut.
LEITFADEN-Avalonia-Portierung.md:
- A1 bis A4 als erledigt markiert, mit Ist-Tabelle: wo der Code liegt und dass
auch die Feinheiten sitzen (nullables GetService fuer Supervisor, LaunchUriAsync
statt Process.Start, DialogWindow.Confirm vor Delete, ContextMenu mit beiden
Eintraegen). Die Soll-Beschreibungen bleiben als Pruefvorlage fuer A5 stehen.
- A5 (Durchsehen mit echten Daten) als einziger offener Punkt ausgewiesen.
- Abschnitt B um das Lizenz-Gate und den Watchdog-Heartbeat ergaenzt. Wichtig fuer
den Naechsten: die Avalonia-App setzt derzeit GAR KEINE Lizenz durch, waehrend
die WinForms-Fassung das Gate weiter hat. Beides kommt mit dem Deploymentcenter
neu, also dort nichts mehr investieren.
- Neuer Abschnitt C fuer Folgepunkte ohne Blockerwirkung.
ANALYSE-Linux-Portierung.md (Revision 5):
- P5 bis P8 als erledigt, Stufen L3 und L4 entsprechend; Restaufwand von 33-51 PT
auf 7-13 PT korrigiert.
- Offen bleiben P3c (Deploymentcenter, steht als Naechstes), CI, systemd/Feldtest,
WinForms-Ausbau und die Abnahme A5.
- Historische Aufwandszahlen als Rueckblick gekennzeichnet statt geloescht.
Nachgeprueft statt uebernommen: A1-A4 gegen den Code verifiziert, ebenso dass es
keinerlei CI-Konfiguration gibt (weder .github noch .gitea) und dass --headless in
App.Avalonia/Program.cs bereits existiert.
Beim Pruefen aufgefallen, kein Fehler: TerminalLogger schreibt die Tagesdatei mit
DateTime.Now und der Launcher liest sie mit DateTime.Now - konsistent, aber beide
ignorieren die in P2 eingefuehrte AppTimeZone. Als Folgepunkt C notiert, relevant
erst beim Headless-Betrieb auf einem UTC-Server.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
18 KiB
Portierungsleitfaden Avalonia (Fortsetzung)
Stand: 13.08.2026 (A1–A4 abgehakt, Deploymentcenter-Hinweis) · Zielgruppe: KI-Agent, der die UI-Portierung fortsetzt
Vorgänger-Dokumente: UI-SPEZIFIKATION-WinForms.md (wie die alte
Oberfläche aussah), ANALYSE-Linux-Portierung.md (Gesamtplan)
0. Was du wissen musst, bevor du irgendetwas anfasst
| Punkt | Wert |
|---|---|
| Projekt | src/PolyTrader.App.Avalonia |
| Zielframework | net10.0 |
| Avalonia | 11.3.19 — NICHT auf 12 heben! LiveCharts2 2.0.5 ist gegen 11 gebaut und wirft unter 12 zur Laufzeit MissingFieldException: Avalonia.Input.Gestures.PinchEvent. Avalonia.Controls.DataGrid folgt einer eigenen Reihe und steht auf 11.3.13. |
| Diagramme | LiveCharts2 (LiveChartsCore.SkiaSharpView.Avalonia 2.0.5) |
| Alte Oberfläche | Git-Tag winforms-final — dort steht der komplette WinForms-Code |
| Sprache | Alles auf Deutsch: Beschriftungen, Kommentare, Commit-Nachrichten |
Der Referenzstand ist eine Tag-Abfrage entfernt
git show winforms-final:Ui/Views/DashboardView.cs
git show winforms-final:src/PolyTrader.Modules.CopyTrading/Ui/MasterTradersView.Designer.cs
Nutze das immer, bevor du eine Ansicht anfasst. Die Spezifikation ist eine Zusammenfassung – der Tag ist die Wahrheit.
1. Die harten Regeln
1.1 Layout ist deklarativ. Immer.
Jede View besteht aus
View.axaml(vollständiges Layout) undView.axaml.cs(nur Verdrahtung und Datenlogik). Steuerelemente und Layout werden NIEMALS zur Laufzeit im Code erzeugt.
Das ist Richards ausdrückliche Vorgabe und ersetzt die frühere WinForms-Designer-Regel. Wenn du etwas Dynamisches brauchst (Menüeinträge, Formularfelder), dann so:
- Das Datenmodell liefert eine Liste (
ObservableCollection<T>) - Das XAML beschreibt über
ItemsControl+DataTemplate, wie ein Element aussieht
Vorbilder im Code: Controls/WindowMenuBar.axaml
und Controls/SettingsEditor.axaml.
1.2 Keine festen Farben
Alle Farben kommen aus den Themen-Ressourcen (App.axaml, 18 Token je Variante). Im XAML:
Foreground="{DynamicResource AppMutedTextBrush}"
Im Code (nur wo unvermeidbar):
btn.Background = ThemeManager.Brush("AppToggleActiveBrush");
Wichtig: Im Code gesetzte Farben folgen dem Themenwechsel nicht von selbst. Wenn du eine
setzt, hänge dich an ThemeManager.ThemeChanged und zeichne dort neu — und melde dich beim
Closed-Ereignis wieder ab:
void OnTheme() => UpdateFarben();
ThemeManager.ThemeChanged += OnTheme;
Closed += (_, _) => ThemeManager.ThemeChanged -= OnTheme;
Verfügbare Token: AppSurfaceBrush, AppSurfaceAltBrush, AppCardBrush, AppBorderBrush,
AppMutedTextBrush, AppCaptionTextBrush, AppReadOnlyTextBrush, AppPositiveBrush,
AppNegativeBrush, AppWarningBrush, AppTradeLossBrush, AppTradeSmallWinBrush,
AppTradeBigWinBrush, AppToggleActiveBrush, AppToggleSellOnlyBrush, AppToggleInactiveBrush,
AppChatUserBrush, AppChatAgentBrush.
Neuen Token gebraucht? In App.axaml in beiden ResourceDictionary-Blöcken ergänzen und
den Schlüssel in die Prüfliste in Program.RunSmokeUi aufnehmen.
1.3 Keine Dialoge für Erfolgsmeldungen
Die WinForms-Fassung bestätigte jedes Speichern mit einer MessageBox. Das ist bewusst abgeschafft:
- Erfolg/Status → Statuszeile des Fensters (
lblStatus,Border Classes="statusbar") - Echte Entscheidung (Löschen bestätigen, Eingabe erfragen) →
DialogWindow.Confirm/.Prompt - Fehler, der Handeln erfordert →
DialogWindow.Info
1.4 Fehlerbehandlung: die Ansicht muss bedienbar bleiben
Datenzugriffe immer in try/catch. Bei Fehlern die Liste leeren und die Meldung in die Statuszeile
schreiben — nie die Ansicht mit einer Ausnahme aufreißen:
try { _rows.Clear(); foreach (var r in repo.GetAll()) _rows.Add(r); }
catch (Exception ex) { Status($"Laden fehlgeschlagen: {ex.Message}"); }
1.5 Module bleiben frei von Avalonia
Die Modul-Fenster liegen in Views/Modules/ in der App, nicht in den Modulprojekten. Grund:
Der kopflose Linux-Daemon (--headless) soll keine GUI-Bibliothek mitschleppen. Siehe
Views/Modules/README.md.
Füge NIEMALS eine Avalonia-Paketreferenz zu einem PolyTrader.Modules.*-Projekt hinzu.
2. Das Baukastenmuster
Jedes Fenster folgt demselben Aufbau. Kopiere Views/JobsWindow.axaml
als kleinstes Beispiel oder Views/Modules/CopyTradingWindow.axaml
als größtes.
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:PolyTrader.App.Avalonia.Controls"
xmlns:vm="using:PolyTrader.App.Avalonia.ViewModels"
x:Class="PolyTrader.App.Avalonia.Views.MeinFenster"
Title="Titel aus der Spezifikation"
Width="…" Height="…"> <!-- Maße aus UI-SPEZIFIKATION übernehmen -->
<DockPanel>
<controls:WindowMenuBar Name="menuBar" DockPanel.Dock="Top" />
<Border Classes="toolbar" DockPanel.Dock="Top">
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Name="btnRefresh" Content="Aktualisieren" />
</StackPanel>
</Border>
<Border Classes="statusbar" DockPanel.Dock="Bottom">
<TextBlock Name="lblStatus" Text="Bereit." />
</Border>
<DataGrid Name="grid" x:DataType="vm:MeineZeile">
<DataGrid.Columns>
<DataGridTextColumn Header="Spalte" Width="120" Binding="{Binding Feld}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Window>
Im Code-Behind zwei Konstruktoren — der parameterlose wird vom XAML-Lader gebraucht:
public MeinFenster() => AvaloniaXamlLoader.Load(this);
public MeinFenster(IModuleUiHost host, /* Abhängigkeiten */) : this()
{
this.FindControl<Controls.WindowMenuBar>("menuBar")!.Attach(host, "meine.view.id", this);
// ItemsSource setzen, Ereignisse verdrahten, Daten laden
}
Stolperfallen, die mich Zeit gekostet haben
| Falle | Lösung |
|---|---|
AVLN2100: Cannot parse a compiled binding without an explicit x:DataType |
x:DataType an das DataGrid (nicht an die Spalten) bzw. an das DataTemplate. Bei Bindungen gegen den DataContext des Fensters: x:DataType ans <Window>. |
CalendarDatePicker.SelectedDate |
ist DateTime?, nicht DateTimeOffset? |
| Neue Einträge erscheinen nicht im Grid | ObservableCollection<T> verwenden. BindingList<T> implementiert kein INotifyCollectionChanged — Avalonia sieht Ergänzungen nicht. |
| Zeilenfarben | über DataGrid.LoadingRow setzen, nicht über Styles. Greift auch bei virtualisierten Zeilen. |
TryFindResource nicht gefunden |
using Avalonia.Controls; (dort als Erweiterungsmethode definiert) |
Namenskollision ClosedTradeRow |
Es gibt bereits PolyTraderSharp.Models.ClosedTradeRow. Eigene Anzeigezeilen mit Präfix benennen (CopyClosedTradeRow). |
| Datei speichern | StorageProvider.SaveFilePickerAsync(...), dann picker.Path.LocalPath — nicht TryGetLocalPath() |
3. Was noch fehlt — die Aufgabenliste
Stand 13.08.2026 (nachgeprüft am Code): A1–A4 sind mit Commit
7f0b05eumgesetzt. Der Commit hat dieses Dokument seinerzeit nicht mitgezogen, weshalb die Liste unten zwei Wochen lang Arbeit als offen auswies, die längst erledigt war. Real offen ist nur noch A5.Die Beschreibungen von A1–A4 bleiben als Soll-Spezifikation stehen — sie sind die Vorlage, gegen die bei A5 geprüft wird. Was tatsächlich gebaut wurde:
| Soll | Ist (geprüft) | |
|---|---|---|
| A1 ✅ | Drei Widgets + Modul-KPIs | LauncherWindow.axaml — „Auffällige Trades (24h)", „Warnungen & Fehler (heute)", „Supervisor-KI", Modul-KPIs als ItemsControl. Supervisor korrekt über nullables GetService<ISupervisorReportRepository>(). |
| A2 ✅ | Account-Übersicht mit Profil-Schaltfläche | Spalten inkl. „3T Winrate %" / „Overall P/L"; OnOpenPolymarketProfileClick nutzt wie empfohlen TopLevel.Launcher.LaunchUriAsync, mit Hinweis bei fehlender Wallet-Adresse. |
| A3 ✅ | „Neu" + „Löschen" für Master-Trader | CopyTradingWindow.axaml.cs — DeleteSelectedTrader() mit DialogWindow.Confirm vor _traderRepo.Delete(id). |
| A4 ✅ | Kontextmenü im Terminal | TerminalWindow.axaml — <ContextMenu> am ScrollViewer mit „Kopieren" und „Alles auswählen". |
Wenn du hier etwas änderst, zieh dieses Dokument im selben Commit mit. Genau das ist beim letzten Mal unterblieben.
A1 — Launcher: Live-Überblick ✅ ERLEDIGT (Commit 7f0b05e)
Die WinForms-Fassung hatte unter den Fenster-Buttons ein LauncherWidgetsPanel (205 + 244 LOC) mit
drei Bereichen nebeneinander in einem TableLayoutPanel:
| Bereich | Inhalt | Datenquelle |
|---|---|---|
| Auffällige Trades (24h) | Grid: Modul, Markt, PnL, PnL % | ITradeLogRepository, letzte 30 nach Betrag sortiert |
| Warnungen & Fehler (heute) | Grid: Zeit, Level, Nachricht | Logs/{heute:yyyy-MM-dd}.jsonl über LogJson.ParseLine, max. 200 |
| Supervisor-KI (letzter Bericht) | Textfeld | ISupervisorReportRepository.GetRecent(1) |
Dazu die Modul-KPIs (UpdateModuleKpis): je Modul PnL/Winrate aus dem Trade-Log.
Referenz: git show winforms-final:Ui/LauncherWidgetsPanel.cs
Achtung: Der Supervisor-Teil darf nur erscheinen, wenn das Modul geladen ist — nutze
services.GetService<…>() (nullable) statt GetRequiredService.
A2 — Launcher: Account-Übersicht ✅ ERLEDIGT (Commit 7f0b05e)
Grid mit: Account, Module, Polymarket, Wallet (USDC), 3T PnL, 3T Winrate %, Overall P/L. Die Spalte „Polymarket" war ein Button, der das Polymarket-Profil des Kontos im Browser öffnet.
Referenz: git show winforms-final:Ui/LauncherForm.cs → UpdateAccountList(),
AccountList_CellContentClick()
Plattformhinweis: Das alte Process.Start(new ProcessStartInfo { UseShellExecute = true })
funktioniert auch auf Linux, ist aber unnötig — nimm stattdessen
TopLevel.GetTopLevel(this)!.Launcher.LaunchUriAsync(new Uri(url)). Das ist Avalonias
plattformneutraler Weg.
A3 — Copytrading: „Neu" und „Löschen" für Master-Trader ✅ ERLEDIGT (Commit 7f0b05e)
Die WinForms-MasterTradersView hatte vier Werkzeugleisten-Schaltflächen: Aktualisieren, Neu,
Speichern, Löschen. Im Avalonia-Fenster sind nur Aktualisieren und Speichern verdrahtet.
Referenz: git show winforms-final:src/PolyTrader.Modules.CopyTrading/Ui/MasterTradersView.cs
→ AddNew(), DeleteCurrent()
Löschen mit DialogWindow.Confirm absichern, danach ITrackedTraderRepository.Delete(id) und den
Eintrag aus CopyTradingState.Traders entfernen.
A4 — Terminal: Kontextmenü ✅ ERLEDIGT (Commit 7f0b05e)
Vorhanden sind Schaltflächen für „Alles kopieren" und „Terminal leeren". Es fehlen die Einträge „Kopieren" (nur Auswahl) und „Alles auswählen" als Kontextmenü auf der Log-Ausgabe.
In Avalonia deklarativ über <ContextMenu> am Container, gebunden an Befehle im Code-Behind.
A5 — Durchsehen mit echten Daten ⬅️ der einzige noch offene Punkt
Alle Fenster wurden konstruiert und die App lief, aber es wurde nicht jedes Fenster mit echten Daten durchgeklickt. Layout-Details siehst du erst im Gebrauch:
- Spaltenbreiten (feste Pixel wurden teils in Sternbreiten übersetzt)
- Umbrüche in Werkzeugleisten bei schmalen Fenstern
- Splitter-Positionen (Copytrading Master-Trader, Supervisor Dossiers)
- Ob die KPI-Kacheln bei acht Stück (Accounting) sinnvoll umbrechen
Vorgehen: App starten, jedes Fenster öffnen, mit winforms-final-Screenshots vergleichen.
B — Bewusst zurückgestellt (NICHT anfassen)
Stand 13.08.2026: Die Deploymentcenter-Integration steht unmittelbar bevor und ersetzt Lizenzverwaltung, Watchdog und Error-Reporting noch einmal grundlegend. Alles, was unten steht, wird davon berührt — fass es nicht an, sonst ist es Wegwerfarbeit.
| Was | Warum |
|---|---|
Lizenzdialog (LicenseDialog) |
Hängt am alten LicenseLabrador-SDK, das durch die Deploymentcenter-Anbindung ersetzt wird. Die Schaltfläche „Lizenz prüfen / setzen …" fehlt deshalb im Einstellungsfenster. |
Lizenz-Startprüfung (LicenseGate) |
In der Avalonia-App bewusst nicht verdrahtet. Konsequenz, die man kennen muss: der Avalonia-Build setzt derzeit gar keine Lizenz durch — alle Module laufen ungeprüft. In der WinForms-Fassung (Program.cs) gibt es das Gate weiterhin. Kommt mit dem Deploymentcenter neu. |
| Watchdog-Heartbeat | WatchdogHeartbeatService ist in der Avalonia-App registriert und läuft. Wird vom Deploymentcenter abgelöst — keine Erweiterungen mehr investieren. |
| WinForms-Projekt entfernen | Erst wenn die Avalonia-Fassung abgenommen ist. Betrifft PolyTrader.App und Ui/. |
C — Kleinere Folgepunkte (kein Blocker)
| Was | Befund |
|---|---|
Logging kennt AppTimeZone nicht |
TerminalLogger.cs:23 stempelt mit DateTime.Now, und der Launcher liest die Tagesdatei mit DateTime.Now. Schreiber und Leser stimmen also überein — kein Fehler. Beide ignorieren aber die in P2 eingeführte konfigurierbare Zeitzone, sodass auf einem UTC-Linuxserver mit Anzeigezone Europe/Berlin die Dateigrenzen nicht zur angezeigten Uhrzeit passen. Beim Headless-Schritt (L2) mitbehandeln. |
| Ungenutzte Designer-Felder | Der Solution-Build wirft 23 Warnungen, überwiegend CS0169 aus WinForms-Designer-Resten. Verschwinden mit dem WinForms-Ausbau von selbst. |
4. Wie du prüfst, ob es funktioniert
Nach jeder Änderung, ohne Ausnahme:
dotnet build PolyTraderSharp.sln -v q --nologo
dotnet test tests/PolyTrader.Tests/PolyTrader.Tests.csproj --nologo -v q
Der wichtigste Test — konstruiert alle Fenster kopflos, ohne die Trading-Dienste zu starten:
dotnet run --project src/PolyTrader.App.Avalonia --no-build -- --smoke-ui
Erwartete Ausgabe (Stand heute):
=== Smoke-UI: Fenster-Konstruktion (Avalonia) ===
[OK] core.dashboard (Dashboard)
[OK] core.settings (Server Settings)
[OK] core.jobs (Server Jobs)
[OK] core.terminal (Terminal / Logs)
[OK] copytrading.main (Copytrading)
[OK] resolutionfarming.main (ResolutionFarming)
[OK] supervisor.main (Supervisor)
[OK] accounting.main (Accounting)
[OK] LauncherWindow konstruiert
[OK] ShutdownConfirmWindow konstruiert
[OK] Einstellungs-Editor: 7 Abschnitte, 17 Felder (…)
[OK] Farbschema „Light": alle 18 Farben vorhanden
[OK] Farbschema „Dark": alle 18 Farben vorhanden
=== Smoke-UI OK ===
Der Smoke-Test startet den Host absichtlich NICHT — sonst liefe die Trading-Engine gegen die echten Börsen-Endpunkte. Nicht ändern.
Linux-Tauglichkeit gegenprüfen (der Sinn der ganzen Übung):
dotnet publish src/PolyTrader.App.Avalonia -r linux-x64 --self-contained false -o /tmp/pt
5. Wo was liegt
src/PolyTrader.App.Avalonia/
├─ App.axaml Farb-Token (Light/Dark) + projektweite Stile
├─ Program.cs Einstieg; BuildHost() ohne UI-Bezug, --headless, --smoke-ui
├─ Shell/
│ ├─ AvaloniaUiHost.cs Fensterverwaltung (IModuleUiHost)
│ ├─ ThemeManager.cs Farbschema + ThemeChanged
│ ├─ ViewIcons.cs Symbolschlüssel → PNG
│ ├─ CoreViews.cs Registrierung der Core-Fenster
│ └─ ModuleViews.cs Registrierung der Modul-Fenster
├─ Controls/
│ ├─ WindowMenuBar.axaml gemeinsame Fensterleiste (auf JEDEM Fenster)
│ └─ SettingsEditor.axaml Ersatz fürs PropertyGrid
├─ ViewModels/ Anzeigezeilen und Datenmodelle
└─ Views/
├─ *.axaml Core-Fenster
└─ Modules/*.axaml Modul-Fenster
Der SettingsEditor — nutze ihn
Du brauchst nie ein Einstellungsformular von Hand zu bauen. Ein Aufruf genügt:
this.FindControl<SettingsEditor>("editorXyz")!.Show(meinEinstellungsObjekt);
Er liest [Category], [DisplayName], [Description] und [Browsable(false)] vom Modell und
rendert Überschrift + Beschriftung links + Feld rechts + Hinweis darunter. Unterstützte Typen:
string, int/long, bool, Enums, Nur-Lese-Eigenschaften.
Ein neues Feld in den Einstellungen heißt also: Eigenschaft am Modell ergänzen, Attribute dran, fertig. Kein UI-Code.
6. Arbeitsweise
- Eine Aufgabe aus Abschnitt 3 nehmen, nicht mehrere gleichzeitig
git show winforms-final:<pfad>— das Original ansehen, bevor du schreibst- Umsetzen nach dem Muster aus Abschnitt 2
- Build + Tests +
--smoke-ui - Committen mit deutscher Nachricht, die das Warum erklärt, nicht nur das Was
- Commit-Fuß:
Co-Authored-By: <dein Name> <deine Adresse>
Was du NICHT tun sollst
- Avalonia auf 12 heben (siehe Abschnitt 0)
- Avalonia in die Modulprojekte ziehen
- Layout im Code aufbauen
- Feste Farben verwenden
- Den Lizenzdialog portieren
- Das WinForms-Projekt löschen
- Den Smoke-Test den Host starten lassen
- Fachlogik in Fenster verlagern — Auswertung gehört in
TradeAnalytics,AccountingEngineusw.
Wenn du unsicher bist
Der Tag winforms-final beantwortet fast jede Frage zum bisherigen Verhalten. Wenn er es nicht
tut und die Entscheidung fachlich ist (Handelslogik, Buchhaltung, Steuern), frag nach, statt zu
raten. Bei reinen Darstellungsfragen entscheide selbst und schreib eine Zeile ins Commit, warum.