feat(ui): complete Avalonia UI port with 7 main pages, tool settings & top MenuBar

This commit is contained in:
Richard
2026-08-10 10:48:34 +02:00
parent a0e18d2a57
commit b5bf97ae74
187 changed files with 20054 additions and 882 deletions
+17 -24
View File
@@ -1,3 +1,5 @@
using ClawdDotNet.Core.Storage;
namespace ClawdDotNet.Tools.FileRW;
/// <summary>
@@ -11,6 +13,11 @@ namespace ClawdDotNet.Tools.FileRW;
/// Ohne abschließenden Verzeichnistrenner erlaubte ein Root wie
/// "…\Agent-X\Workspace" damit auch Zugriffe auf "…\Agent-X\Workspace-Backup\…" —
/// ein fremdes Verzeichnis, das zufällig mit demselben Präfix beginnt.
///
/// Der Vergleich selbst liegt in <see cref="PathBoundary"/>: Unter Linux
/// unterscheidet das Dateisystem Groß- und Kleinschreibung, und symbolische
/// Verknüpfungen können aus dem Workspace hinauszeigen, ohne dass die reine
/// Pfadrechnung das sieht.
/// </summary>
public static class WorkspacePath
{
@@ -31,6 +38,12 @@ public static class WorkspacePath
}
// Alternate Data Streams (datei.txt:versteckt) umgehen die Endungsprüfung.
//
// Die Prüfung trägt unter Linux zusätzlich: Dort meldet Path.IsPathRooted für
// "C:\Windows\…" false — ein Windows-Pfad ist unter Linux ein gewöhnlicher
// Dateiname. Ohne diese Zeile käme er als relativer Pfad durch. Ein Doppelpunkt
// im Dateinamen ist unter Linux zwar erlaubt, aber nichts, was ein Agent
// braucht; die Einschränkung kostet uns nichts.
if (value.Contains(':'))
{
throw new UnauthorizedAccessException(
@@ -51,33 +64,13 @@ public static class WorkspacePath
/// <summary>
/// Prüft, ob <paramref name="candidate"/> im Verzeichnis <paramref name="root"/> liegt.
/// Der Vergleich erfolgt auf Verzeichnisgrenzen, nicht auf Zeichenketten-Präfixen.
/// Der Vergleich erfolgt auf Verzeichnisgrenzen, nicht auf Zeichenketten-Präfixen,
/// und löst symbolische Verknüpfungen auf.
/// </summary>
public static bool IsInside(string candidate, string root)
{
var normalizedRoot = NormalizeDirectory(root);
var normalizedCandidate = Path.GetFullPath(candidate);
// Der Root selbst gilt als innerhalb.
if (string.Equals(
normalizedCandidate.TrimEnd(Path.DirectorySeparatorChar),
normalizedRoot.TrimEnd(Path.DirectorySeparatorChar),
StringComparison.OrdinalIgnoreCase))
{
return true;
}
// Der Trenner ist entscheidend: Ohne ihn gälte "…\Workspace-Backup" als
// Teil von "…\Workspace".
return normalizedCandidate.StartsWith(normalizedRoot, StringComparison.OrdinalIgnoreCase);
}
=> PathBoundary.IsInside(candidate, root);
/// <summary>Absoluter Pfad mit genau einem abschließenden Trenner.</summary>
private static string NormalizeDirectory(string path)
{
var full = Path.GetFullPath(path);
return full.EndsWith(Path.DirectorySeparatorChar)
? full
: full + Path.DirectorySeparatorChar;
}
=> PathBoundary.NormalizeDirectory(path);
}