# 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`](UI-SPEZIFIKATION-WinForms.md) (wie die alte
Oberfläche aussah), [`ANALYSE-Linux-Portierung.md`](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

```bash
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) und `View.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`](../src/PolyTrader.App.Avalonia/Controls/WindowMenuBar.axaml)
und [`Controls/SettingsEditor.axaml`](../src/PolyTrader.App.Avalonia/Controls/SettingsEditor.axaml).

### 1.2 Keine festen Farben

Alle Farben kommen aus den Themen-Ressourcen (`App.axaml`, 18 Token je Variante). Im XAML:

```xml
Foreground="{DynamicResource AppMutedTextBrush}"
```

Im Code (nur wo unvermeidbar):

```csharp
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:

```csharp
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:

```csharp
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`](../src/PolyTrader.App.Avalonia/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`](../src/PolyTrader.App.Avalonia/Views/JobsWindow.axaml)
als kleinstes Beispiel oder [`Views/Modules/CopyTradingWindow.axaml`](../src/PolyTrader.App.Avalonia/Views/Modules/CopyTradingWindow.axaml)
als größtes.

```xml
<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:

```csharp
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 `7f0b05e` **umgesetzt**. 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`](../src/PolyTrader.App.Avalonia/Views/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`](../src/PolyTrader.App.Avalonia/Views/Modules/CopyTradingWindow.axaml.cs) — `DeleteSelectedTrader()` mit `DialogWindow.Confirm` vor `_traderRepo.Delete(id)`. |
| **A4** ✅ | Kontextmenü im Terminal | [`TerminalWindow.axaml`](../src/PolyTrader.App.Avalonia/Views/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`](../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`](../src/PolyTrader.Core/Services/TerminalLogger.cs) 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:**

```bash
dotnet build PolyTraderSharp.sln -v q --nologo
```

```bash
dotnet test tests/PolyTrader.Tests/PolyTrader.Tests.csproj --nologo -v q
```

**Der wichtigste Test — konstruiert alle Fenster kopflos, ohne die Trading-Dienste zu starten:**

```bash
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):

```bash
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:

```csharp
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

1. **Eine Aufgabe aus Abschnitt 3 nehmen**, nicht mehrere gleichzeitig
2. **`git show winforms-final:<pfad>`** — das Original ansehen, bevor du schreibst
3. Umsetzen nach dem Muster aus Abschnitt 2
4. Build + Tests + `--smoke-ui`
5. Committen mit deutscher Nachricht, die **das Warum** erklärt, nicht nur das Was
6. 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`, `AccountingEngine` usw.

### 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.
