UI-Entwurf umgesetzt, Rocket.Chat-Tool, Deploymentcenter 2.4

Sicherungspunkt vor dem Aufraeumen. Buendelt die Arbeit, die seit dem
Abschluss der Avalonia-Portierung im Arbeitsverzeichnis lag.

Oberflaeche
- Entwurf aus Mockup/ umgesetzt: Theme.axaml (Farben je Thema, Barlow als
  mitgelieferte Schrift), Icons.axaml (Symbolgeometrien), Shell.axaml
  (eigene ControlThemes statt Fluent umzufaerben).
- Neue Steuerelemente StrokeIcon und BlueprintFrame, Seiten fuer
  Token-Verbrauch und Agenten-Chats, Werkzeug-Einstellungen als Seite
  statt eigenem Fenster, Texteditor-Fenster.
- ThemeManager mit hellem und dunklem Thema; die beiden Pinsel-Konverter
  entfallen, weil ein fester Farbwert den Themenwechsel nicht ueberlebt.

Rocket.Chat
- Neues Tool-Projekt (Client, Konfiguration, Workspace-Dateien) nach der
  Bauform des Telegram-Tools: rocketchat_poll als Tool-Job, geweckt wird
  nur, wenn wirklich etwas anliegt.
- send_file ist freigabepflichtig, send_message bewusst nicht: Der Raum
  ist Arbeitsraum, der Schutz sitzt an der Raum-Allowlist.
- Konzept-Doc um die Messung gegen die echte Instanz 8.7 ergaenzt; drei
  Annahmen waren falsch und sind korrigiert.

Deploymentcenter
- DC6 (Update anwenden) und DC7 (Erstinstallation ueber setup.json)
  erledigt, DC3 fuer win-x64/dev; deploy/publish.py als Release-Strecke.
- AppHost.DisposeAsync gegen doppeltes Herunterfahren gesperrt - sonst
  ueberschreibt eine zweite Abmeldung den Wartungszustand am Watchdog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-23 12:16:05 +02:00
co-authored by Claude Opus 5
parent b5bf97ae74
commit 33d95a6c3f
88 changed files with 11665 additions and 1029 deletions
@@ -1,65 +0,0 @@
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Layout;
using Avalonia.Media;
namespace ClawdDotNet.Desktop.Converters;
/// <summary>
/// Färbt den Hintergrund einer Chat-Sprechblase für Benutzer-Nachrichten.
/// </summary>
public sealed class ChatBubbleBrushConverter : IValueConverter
{
public static readonly ChatBubbleBrushConverter Instance = new();
private static readonly IBrush UserBubble = new SolidColorBrush(Color.FromArgb(0x28, 0x00, 0x78, 0xD4));
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool isUser && isUser)
return UserBubble;
return null;
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException("Nur zur Anzeige.");
}
/// <summary>
/// Richtet Chat-Sprechblasen aus: Rechts für den Benutzer, links für den Agenten.
/// </summary>
public sealed class ChatAlignmentConverter : IValueConverter
{
public static readonly ChatAlignmentConverter Instance = new();
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool isUser && isUser)
return HorizontalAlignment.Right;
return HorizontalAlignment.Left;
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException("Nur zur Anzeige.");
}
/// <summary>
/// Prüft, ob ein Tool-Name mit dem ConverterParameter übereinstimmt.
/// </summary>
public sealed class ToolMatchConverter : IValueConverter
{
public static readonly ToolMatchConverter Instance = new();
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is string toolName && parameter is string targetTool)
return toolName.Equals(targetTool, StringComparison.OrdinalIgnoreCase);
return false;
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException("Nur zur Anzeige.");
}
@@ -0,0 +1,54 @@
using System.Globalization;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Media;
namespace ClawdDotNet.Desktop.Converters;
/// <summary>
/// Schlägt eine Symbol-Geometrie aus <c>Styles/Icons.axaml</c> über ihren Namen nach.
///
/// <para>Damit halten die Ansichtsmodelle nur den Namen („IconChat") statt einer
/// <see cref="Geometry"/> — sie brauchen dann keinen Verweis auf <c>Avalonia.Media</c>,
/// und die Navigationsleiste kommt mit einer schlichten Zeichenkette je Eintrag aus.</para>
///
/// <para>Dass hier keine Themenumschaltung nötig ist, ist kein Zufall: Die Geometrien sind
/// themenunabhängig, gefärbt wird erst beim Zeichnen über die geerbte Vordergrundfarbe
/// (siehe <c>Controls/StrokeIcon.cs</c>). Für Farben wäre dieser Weg falsch — ein einmal
/// aufgelöster Pinsel bliebe beim Wechsel auf Dunkel stehen.</para>
/// </summary>
public sealed class IconConverter : IValueConverter
{
public static readonly IconConverter Instance = new();
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not string key || string.IsNullOrEmpty(key)) return null;
if (Application.Current is not { } app) return null;
return app.TryFindResource(key, out var resource) ? resource as Geometry : null;
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException("Nur zur Anzeige.");
}
/// <summary>
/// Der Pfeil am Einklapp-Schalter der Seitenleiste: zeigt nach links, solange
/// eingeklappt werden kann, und nach rechts, wenn wieder aufgeklappt wird.
/// </summary>
public sealed class SidebarChevronConverter : IValueConverter
{
public static readonly SidebarChevronConverter Instance = new();
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
var isCollapsed = value is true;
return IconConverter.Instance.Convert(
isCollapsed ? "IconChevronRight" : "IconChevronLeft", targetType, parameter, culture);
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException("Nur zur Anzeige.");
}
@@ -0,0 +1,40 @@
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Layout;
namespace ClawdDotNet.Desktop.Converters;
/// <summary>
/// Richtet Sprechblasen aus: rechts fuer den Benutzer, links fuer den Agenten.
///
/// <para>Ausrichtung und nicht Farbe — deshalb ist ein Konverter hier richtig. Der
/// frueher danebenstehende ChatBubbleBrushConverter lieferte einen Pinsel mit festem
/// Farbwert und ueberlebte den Themenwechsel nicht; die Hinterlegung eigener Nachrichten
/// laeuft jetzt ueber die Stilklasse <c>card accent</c>.</para>
/// </summary>
public sealed class ChatAlignmentConverter : IValueConverter
{
public static readonly ChatAlignmentConverter Instance = new();
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is true ? HorizontalAlignment.Right : HorizontalAlignment.Left;
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException("Nur zur Anzeige.");
}
/// <summary>
/// Prueft, ob ein Werkzeugname mit dem <c>ConverterParameter</c> uebereinstimmt.
/// Blendet im Werkzeug-Einstellungsbereich die passende Feldgruppe ein.
/// </summary>
public sealed class ToolMatchConverter : IValueConverter
{
public static readonly ToolMatchConverter Instance = new();
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is string toolName && parameter is string targetTool
&& toolName.Equals(targetTool, StringComparison.OrdinalIgnoreCase);
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException("Nur zur Anzeige.");
}
@@ -1,33 +0,0 @@
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
namespace ClawdDotNet.Desktop.Converters;
/// <summary>
/// Färbt eine Logzeile nach ihrer Stufe.
///
/// Bewusst nur Warnung und Fehler hervorgehoben — alles einzufärben macht die Ansicht
/// unruhig und lenkt von genau den Zeilen ab, die auffallen sollen. Der Rest behält die
/// Vordergrundfarbe des Themas und funktioniert damit hell wie dunkel.
/// </summary>
public sealed class LogLevelBrushConverter : IValueConverter
{
public static readonly LogLevelBrushConverter Instance = new();
private static readonly IBrush Error = new SolidColorBrush(Color.FromRgb(0xE8, 0x4B, 0x4B));
private static readonly IBrush Warn = new SolidColorBrush(Color.FromRgb(0xD8, 0x9C, 0x2A));
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value as string switch
{
"ERR" or "FTL" => Error,
"WRN" => Warn,
// null heißt: Bindung greift nicht, das Steuerelement behält seine eigene
// Farbe aus dem Thema.
_ => null
};
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException("Nur zur Anzeige.");
}
@@ -0,0 +1,27 @@
using System.Globalization;
using Avalonia.Data.Converters;
namespace ClawdDotNet.Desktop.Converters;
/// <summary>
/// Vergleicht eine Zeichenkette mit dem <c>ConverterParameter</c>, ohne Gross- und
/// Kleinschreibung zu unterscheiden.
///
/// <para>Gedacht fuer <c>Classes.xyz="{Binding …}"</c>: Die Ansicht setzt damit eine
/// Stilklasse, und die Farbe dazu steht in <c>Styles/Shell.axaml</c> an
/// <c>DynamicResource</c>. Das ersetzt den frueheren LogLevelBrushConverter, der
/// Pinsel mit festen Farbwerten zurueckgab — die blieben beim Wechsel auf Dunkel
/// unveraendert stehen, weil ein einmal gelieferter Pinsel nichts mehr vom Thema
/// mitbekommt.</para>
/// </summary>
public sealed class StringEqualsConverter : IValueConverter
{
public static readonly StringEqualsConverter Instance = new();
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is string text && parameter is string expected
&& text.Equals(expected, StringComparison.OrdinalIgnoreCase);
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException("Nur zur Anzeige.");
}