Stufe 3 (#764): Avalonia-Fenster entfernt
- Views, ViewModels, Controls, Converters, Styles, Services, Desktop-i18n, Schriften und App.axaml raus; Avalonia- und Mvvm-Pakete raus - ClawdDotNet ohne Schalter: Manager starten (unter systemd ueber den Dienst, nie doppelt) und WebUI oeffnen (LauncherRun) - bis die Huelle kommt - Instanzen laufen immer kopflos; instanceWindows wird gelesen, nicht mehr ausgewertet; die Kette 'Fenster nach vorn holen' ist entfernt - Telegram-Anmeldung nur noch ueber die WebUI (tote Dialog-Rueckrufe raus) - systemd-Vorlagen im Linux-Paket; Oberflaechen-Leitfaden ins Archiv Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8ec7896417
commit
6d81693041
@@ -0,0 +1,215 @@
|
||||
# Oberfläche — Leitfaden
|
||||
|
||||
> **Archiv seit 2026-10-02 (#764, Stufe 3).** Die Avalonia-Fenster, für die dieser Leitfaden
|
||||
> galt, sind entfernt. Die einzige Oberfläche ist die WebUI ([WebUI-Leitfaden](../WebUI-Leitfaden.md)).
|
||||
> Aufbewahrt wegen der Begründungen darin.
|
||||
|
||||
Für alle, die an `src/ClawdDotNet.Desktop` arbeiten.
|
||||
Stand: 2026-08-23.
|
||||
|
||||
---
|
||||
|
||||
## 1. Stand
|
||||
|
||||
**Die Portierung ist abgeschlossen.** Es gibt keine Platzhalter mehr und keine
|
||||
WinForms-Vorlage, gegen die man vergleichen könnte: Die alte Oberfläche
|
||||
(`ClawdDotNet.csproj`, `frm_*.cs`, `UI/`, `Models/`, `EmbeddedUI/`) ist am
|
||||
2026-08-23 aus dem Arbeitsbaum entfernt worden. Wer sie doch einmal braucht,
|
||||
findet sie im Tag `vor-fruehjahrsputz-2026-08`.
|
||||
|
||||
Neun Bereiche, alle nativ in Avalonia:
|
||||
|
||||
| Gruppe | Bereich | Ansichtsmodell |
|
||||
|---|---|---|
|
||||
| Arbeit | Chat, Agenten, Aufgaben | `ChatPageViewModel`, `AgentsPageViewModel`, `TasksPageViewModel` |
|
||||
| Analyse | Token-Verbrauch, Agenten-Chats | `TokenUsagePageViewModel`, `AgentChatsPageViewModel` |
|
||||
| System | Sicherung, Protokoll, Einstellungen, Info | `BackupPageViewModel`, `LogPageViewModel`, `SettingsPageViewModel`, `InfoPageViewModel` |
|
||||
|
||||
Dazu die Fenster `InstancePickerWindow`, `LicenseWindow`, `TextEditorWindow` und
|
||||
die Dialoge zum Anlegen von Agent, Auftrag und Dienst.
|
||||
|
||||
Das Erscheinungsbild folgt dem Entwurf in `Mockup/` — wer daran etwas ändert,
|
||||
liest zuerst `Mockup/extracted/mockup/Implementierungsleitfaden.md`.
|
||||
|
||||
**Was in der Oberfläche noch fehlt**, siehe [Roadmap](Roadmap.md):
|
||||
die Freigabe-Ansicht für gestagte Aufrufe (A2) und die Schaltfläche
|
||||
„Fehler melden" (DC1).
|
||||
|
||||
---
|
||||
|
||||
## 2. Drei Regeln, die nicht verletzt werden dürfen
|
||||
|
||||
### 2.1 Der Schichtschnitt
|
||||
|
||||
```
|
||||
src/ClawdDotNet.App ← Fachlogik. KEIN Verweis auf Avalonia. Niemals.
|
||||
src/ClawdDotNet.Desktop ← Oberfläche. Darf App und Core verwenden.
|
||||
```
|
||||
|
||||
`ClawdDotNet.App` muss ohne Fenster laufen — darauf setzt der geplante systemd-Dienst auf.
|
||||
Sobald dort ein `using Avalonia…` steht, ist der Schnitt kaputt und fällt erst Wochen
|
||||
später auf.
|
||||
|
||||
**Faustregel:** Alles, was Dateien liest, rechnet oder mit der Engine spricht, gehört nach
|
||||
`App`. Alles, was etwas anzeigt, nach `Desktop`.
|
||||
|
||||
### 2.2 Fäden
|
||||
|
||||
Ereignisse aus `AgentEngine`, `TaskScanner`, `OpenRouterStatusService` und
|
||||
`BackupScheduler` kommen auf **Hintergrundfäden**. Eine `ObservableCollection` von dort aus
|
||||
zu ändern wirft entweder oder beschädigt still die Anzeige.
|
||||
|
||||
```csharp
|
||||
// Aus einem Ereignis der Fachschicht heraus:
|
||||
Dispatcher.UIThread.Post(() => Lines.Add(neu));
|
||||
|
||||
// Wenn ein Rückgabewert gebraucht wird:
|
||||
await Dispatcher.UIThread.InvokeAsync(() => …);
|
||||
```
|
||||
|
||||
Ein `DispatcherTimer` läuft dagegen bereits auf dem Oberflächenfaden — dort ist kein
|
||||
Wechsel nötig (siehe `LogPageViewModel`).
|
||||
|
||||
### 2.3 Avalonia **12**, nicht 11
|
||||
|
||||
Praktisch alle Anleitungen im Netz sind für Avalonia 11 und lassen sich hier nicht
|
||||
übernehmen. Bekannte Unterschiede:
|
||||
|
||||
- `BindingPlugins` ist nicht mehr öffentlich. Das übliche
|
||||
`DisableAvaloniaDataAnnotationValidation()` aus den 11er-Vorlagen **entfällt ersatzlos** —
|
||||
nicht nachbauen.
|
||||
- `ShutdownMode` voll qualifizieren: `Avalonia.Controls.ShutdownMode`.
|
||||
|
||||
Diese Fehler brechen den Build. Das ist gut — sie fallen sofort auf.
|
||||
|
||||
---
|
||||
|
||||
## 3. Das Muster
|
||||
|
||||
Der Logs-Bereich ist als vollständiges Beispiel gebaut. Drei Dateien, drei Aufgaben:
|
||||
|
||||
**`src/ClawdDotNet.App/Services/LogTail.cs`** — die Fachlogik. Liest Dateien, kennt keine
|
||||
Oberfläche, wäre ohne Fenster lauffähig.
|
||||
|
||||
**`src/ClawdDotNet.Desktop/ViewModels/LogPageViewModel.cs`** — das Ansichtsmodell. Erbt von
|
||||
`PageViewModel`, hält Zustand und Befehle. Kennt keine Steuerelemente.
|
||||
|
||||
**`src/ClawdDotNet.Desktop/Views/LogPageView.axaml`** — die Ansicht. Nur Aufbau und
|
||||
Bindungen.
|
||||
|
||||
### Ein neuer Bereich in vier Schritten
|
||||
|
||||
**1.** Ansichtsmodell anlegen, von `PageViewModel` erbend:
|
||||
|
||||
```csharp
|
||||
public sealed partial class InfoPageViewModel : PageViewModel
|
||||
{
|
||||
public InfoPageViewModel(AppHost? host) : base("Info") { … }
|
||||
}
|
||||
```
|
||||
|
||||
`AppHost?` ist **nullbar** — der Entwurfsmodus des Editors erzeugt das Ansichtsmodell ohne
|
||||
laufenden Aufbau. Bei `null` einfach nichts starten und Beispielwerte zeigen.
|
||||
|
||||
**2.** Ansicht anlegen: `Views/InfoPageView.axaml` + `.axaml.cs`. Der Name muss der
|
||||
Konvention folgen — `ViewLocator` sucht `…ViewModels.FooViewModel` → `…Views.FooView`.
|
||||
Passt der Name nicht, steht der gesuchte Typ im Fenster statt der Ansicht.
|
||||
|
||||
**3.** In `MainWindowViewModel` den Platzhalter ersetzen:
|
||||
|
||||
```csharp
|
||||
new PlaceholderPageViewModel("Info", "…") // vorher
|
||||
new InfoPageViewModel(host) // nachher
|
||||
```
|
||||
|
||||
**4.** `x:DataType` in der AXAML setzen. Ohne das greifen die kompilierten Bindungen nicht
|
||||
und Tippfehler in Bindungspfaden fallen erst zur Laufzeit auf.
|
||||
|
||||
### Werkzeugkasten
|
||||
|
||||
- Zustand: `[ObservableProperty] private string _text = "";` → erzeugt `Text` samt
|
||||
Benachrichtigung.
|
||||
- Befehle: `[RelayCommand] private void Speichern() { … }` → bindbar als
|
||||
`SpeichernCommand`.
|
||||
- Formatierung gehört in `Styles/`, nicht an einzelne Steuerelemente. Seit der
|
||||
Umsetzung des Entwurfs aus `Mockup/` liegt sie in drei Dateien:
|
||||
`Theme.axaml` (Farben je Thema, Schriften), `Icons.axaml` (Symbolgeometrien),
|
||||
`Shell.axaml` (Steuerelement-Vorlagen und Stilklassen).
|
||||
Klassen: `h1`, `h2`, `kicker`, `label`, `caption`, `muted`, `mono`, `card`,
|
||||
`console`, `hr`, `sep`, `toolbar`, `thead`, `tr`, `th`, `td`, `tag`,
|
||||
`statusbar`, `topbar`, `sidebar`, `nav`; an Schaltflächen zusätzlich
|
||||
`primary`, `toolbar`, `ghost`, `flat`, `icon`.
|
||||
- Symbole über `Controls/StrokeIcon.cs` mit einer Geometrie aus `Icons.axaml`.
|
||||
Die Farbe wird geerbt — nicht gesetzt.
|
||||
- Rahmen mit Eckmarken über `Controls/BlueprintFrame.cs`. Sparsam: nur Dialoge
|
||||
und die Info-Karte.
|
||||
- Tabellen von Hand aus `Border.thead` + `ListBox.table`, nicht mit `DataGrid`.
|
||||
Das Paket ist nicht mehr referenziert.
|
||||
- **Avalonia 12 hat die Ressourcenschlüssel des Fluent-Themas umgebaut.** Die aus
|
||||
11er-Anleitungen bekannten Namen (`ButtonBackground`, `TextControlBackground`,
|
||||
`ControlCornerRadius` …) existieren nicht mehr; ein Setter darauf ist wirkungslos
|
||||
und fällt nicht auf. Für neue Steuerelemente deshalb eine eigene `ControlTheme`
|
||||
in `Shell.axaml` schreiben statt zu versuchen, Fluent umzufärben.
|
||||
- Ordner öffnen: `Core.Storage.SystemShell.OpenFolder(pfad)`. **Kein** `explorer.exe`.
|
||||
- Dateinamen erzeugen: `Core.Storage.PortableFileName.Sanitize(name)`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Prüfliste für die leisen Fehler
|
||||
|
||||
Diese Klasse bricht weder den Build noch die Tests. Vor jeder Abgabe durchgehen:
|
||||
|
||||
- [ ] **Fenster-Schließen behandelt?** Wartet der Code auf eine Antwort aus einem Fenster
|
||||
(`TaskCompletionSource`), muss `window.Closed` als Abbruch gelten. Sonst hängt der
|
||||
Ablauf lautlos für immer.
|
||||
- [ ] **Sammlungen nur vom Oberflächenfaden geändert?** Siehe 2.2.
|
||||
- [ ] **Wächst etwas unbegrenzt?** Listen, die im Betrieb volllaufen, brauchen eine
|
||||
Obergrenze (`LogPageViewModel.MaxLines = 2000` als Vorbild).
|
||||
- [ ] **Timer beendet?** `DispatcherTimer` in einem Ansichtsmodell läuft weiter, auch wenn
|
||||
der Bereich nicht sichtbar ist. Bei teuren Abfragen anhalten.
|
||||
- [ ] **Farben aus dem Thema?** Keine festen Farbwerte — die Anwendung läuft hell und
|
||||
dunkel. `{DynamicResource …}` verwenden.
|
||||
- [ ] **Keine relativen Pfade.** `./Backups` und Ähnliches hängt vom Arbeitsverzeichnis ab
|
||||
und zeigt unter Linux ins Leere. `AppPaths.DataDirectory` verwenden.
|
||||
- [ ] **Kein `MessageBox`, kein `System.Windows.Forms`, kein `System.Drawing`.**
|
||||
|
||||
---
|
||||
|
||||
## 5. Abnahme
|
||||
|
||||
```bash
|
||||
dotnet build ClawdDotNet.slnx
|
||||
```
|
||||
|
||||
```bash
|
||||
dotnet test tests/ClawdDotNet.Core.Tests/ClawdDotNet.Core.Tests.csproj
|
||||
```
|
||||
|
||||
Beide müssen fehlerfrei sein — 567 Tests, keine neuen Fehlschläge.
|
||||
|
||||
**Und dann tatsächlich starten.** Die Oberfläche hat keine Testabdeckung; die Fehler aus
|
||||
Abschnitt 4 fallen ausschließlich beim Laufen auf.
|
||||
|
||||
```bash
|
||||
dotnet run --project src/ClawdDotNet.Desktop
|
||||
```
|
||||
|
||||
Hinweis: Ein Starttest hinterlässt unter Windows einen Prozess, der die `.exe` sperrt und
|
||||
den nächsten Build mit `MSB3021` scheitern lässt. Aufräumen mit:
|
||||
|
||||
```bash
|
||||
powershell -Command "Get-Process ClawdDotNet -EA SilentlyContinue | Stop-Process -Force"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Wenn etwas unklar ist
|
||||
|
||||
Lieber nachfragen als raten. Zwei Dinge sind besonders leicht falsch zu machen:
|
||||
|
||||
- **Was gehört in welche Schicht?** Im Zweifel nach `App` — von dort kann die Oberfläche
|
||||
es holen, umgekehrt nicht.
|
||||
- **Wie kommen Daten aus der Engine in die Ansicht?** `AppHost` gibt `Engine`, `Scanner`,
|
||||
`Staging`, `Status` und `Usage` heraus; alle sind **nullbar**, wenn kein
|
||||
OpenRouter-Schlüssel hinterlegt ist. Diesen Fall mitdenken — die Anwendung läuft dann
|
||||
bewusst ohne Agenten.
|
||||
Reference in New Issue
Block a user