Portierungsleitfaden fuer die Fortsetzung der Avalonia-Umstellung
docs/LEITFADEN-Avalonia-Portierung.md: Anleitung fuer einen KI-Agenten, der die UI- Portierung weiterfuehrt. Enthaelt bewusst eine EHRLICHE Luecken-Liste - alle acht Fenster konstruieren zwar, mehrere sind aber inhaltlich unvollstaendig. Gegen den Tag winforms-final verglichen und belegt: - A1: Launcher-Live-Ueberblick fehlt KOMPLETT (auffaellige Trades 24h, Warnungen/Fehler heute aus der JSONL, letzter Supervisor-Bericht, Modul-KPIs) - groesste Luecke - A2: Launcher-Account-Uebersicht fehlt KOMPLETT (inkl. Polymarket-Button je Konto) - A3: Copytrading, Master-Trader: 'Neu' und 'Loeschen' nicht verdrahtet - A4: Terminal-Kontextmenue (Kopieren/Alles auswaehlen) fehlt - A5: kein Fenster wurde mit echten Daten durchgeklickt - Spaltenbreiten, Umbrueche und Splitter-Positionen sind ungeprueft Dazu die Regeln (Layout deklarativ, keine festen Farben, keine Erfolgs-Dialoge, Module bleiben frei von Avalonia), das Baukastenmuster, die Stolperfallen die mich Zeit gekostet haben (x:DataType, CalendarDatePicker-Typ, BindingList vs ObservableCollection, LoadingRow, Namenskollision ClosedTradeRow, SaveFilePicker-Pfad), die Pruefbefehle mit erwarteter Ausgabe und was NICHT angefasst werden darf (Avalonia 12, Lizenzdialog, WinForms-Projekt). Alle Dateiverweise und der dokumentierte Smoke-Befehl sind verifiziert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
# Portierungsleitfaden Avalonia (Fortsetzung)
|
||||
|
||||
**Stand:** 06.08.2026 · **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
|
||||
|
||||
Der Stand ist ehrlich gesagt: **alle acht Fenster existieren und konstruieren, aber mehrere sind
|
||||
inhaltlich unvollständig.** Ich habe sie gegen den Tag `winforms-final` verglichen. Sortiert nach
|
||||
Wichtigkeit:
|
||||
|
||||
### A1 — Launcher: Live-Überblick fehlt vollständig ⚠️ größte Lücke
|
||||
|
||||
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 fehlt vollständig ⚠️
|
||||
|
||||
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 fehlen
|
||||
|
||||
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ü fehlt
|
||||
|
||||
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
|
||||
|
||||
Ich habe alle Fenster **konstruiert** und die App laufen lassen, aber **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)
|
||||
|
||||
| Was | Warum |
|
||||
|---|---|
|
||||
| **Lizenzdialog** (`LicenseDialog`) | Hängt am alten LicenseLabrador-SDK, das durch die Deploymentcenter-Anbindung ersetzt wird. Eine Portierung wäre Wegwerfarbeit. Die Schaltfläche „Lizenz prüfen / setzen …" fehlt deshalb im Einstellungsfenster. |
|
||||
| **WinForms-Projekt entfernen** | Erst wenn die Avalonia-Fassung abgenommen ist. Betrifft `PolyTrader.App` und `Ui/`. |
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user