Testanleitung: eigenes X-Display je Projekt fuer Parallelbetrieb
CI / Build & Tests (Linux) (push) Canceled after 0s
CI / Plattformneutralität & Hygiene (push) Canceled after 0s

Damit mehrere Agenten gleichzeitig Oberflaechen testen koennen, bekommt jedes
Projekt eine feste Displaynummer (Vorgabe Richard):

  :99 PolyTrader, :98 Predictalytics, :97 ClawdDotNet, :96 IBKRTrader,
  :95 und absteigend fuer kuenftige Projekte.

Zwei Prozesse auf demselben Display sehen die Fenster des jeweils anderen -
Klicks landen im falschen Programm, Aufnahmen zeigen fremde Anwendungen.

Beim Durcharbeiten fielen zwei weitere Kollisionspunkte auf, die zum
Parallelbetrieb dazugehoeren und mit aufgenommen wurden:

- Screenshot- und Logdateien in /tmp brauchen ein Projektpraefix. /tmp/v_1.png
  ueberschreiben sich parallel laufende Projekte gegenseitig.
- "xvfb-run -a" darf NICHT mehr verwendet werden: Das -a sucht sich selbst ein
  freies Display und beginnt dabei bei :99 - ausgerechnet PolyTraders Nummer.
  Stattdessen "xvfb-run -n <nummer>".

Die Warnung ist nicht theoretisch: Auf der VM lag ein verwaister X98-Socket,
entstanden durch genau so einen "xvfb-run -a"-Aufruf beim smoke-ui-Test, als
:99 belegt war. Er blockierte Predictalytics' reservierte Nummer, ohne dass ein
Prozess lief. Entfernt, und die Anleitung erklaert jetzt, wie man verwaiste
Sockets erkennt (ls /tmp/.X11-unix/ gegen pgrep -a Xvfb lesen) und aufraeumt.

Weiter ergaenzt:
- pkill nie ohne Displaynummer - "pkill Xvfb" erschlaegt die Sitzungen aller
  anderen Projekte.
- DISPLAY in jedem Skript setzen, sonst landet man auf :0, dem echten Desktop.
- Die Skriptvorlage hat jetzt einen Kopf mit PROJEKT/DISP/FENSTER; das ist
  alles, was sich je Projekt unterscheidet. Praktisch gegengetestet.
- Aufraeumabschnitt und Belegungsliste um Displays und Verzeichnisse erweitert.

Nebenbei aufgefallen: Das PolyTrader-Publish lag unter ~/pt-test statt
~/polytrader-test und verletzte damit die eigene Verzeichniskonvention.
Umbenannt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-27 20:49:28 +02:00
co-authored by Claude Opus 5
parent 62570cfdd8
commit 9b2294cb4c
33 changed files with 979 additions and 244 deletions
@@ -1,6 +1,7 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Reflection;
@@ -32,6 +33,9 @@ namespace PolyTrader.App.Avalonia.ViewModels
/// <summary>Falsch bei Eigenschaften ohne Setter (reine Statusanzeigen).</summary>
public bool IsEditable => Property.CanWrite;
/// <summary>Typ der dahinterliegenden Eigenschaft – die Konstruktionsprüfung schaut darauf.</summary>
public Type ValueType => Property.PropertyType;
protected void Raise([CallerMemberName] string? name = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
@@ -50,6 +54,79 @@ namespace PolyTrader.App.Avalonia.ViewModels
}
}
/// <summary>
/// Freitext über mehrere Zeilen. Gleiche Bindung wie <see cref="TextSettingsField"/>, nur mit
/// eigener Darstellung: Bis zum 26.08.2026 standen auch „Beschreibung" und „Begründung"
/// (je bis 1000 Zeichen) in einzeiligen Feldern, in denen nur ein Bruchteil sichtbar war
/// (Befund B-14 des Linux-Sichttests). Ausgezeichnet wird das am Modell mit
/// <c>[DataType(DataType.MultilineText)]</c>.
/// </summary>
public sealed class MultilineTextSettingsField : SettingsField
{
public MultilineTextSettingsField(object t, PropertyInfo p, string l, string? d) : base(t, p, l, d) { }
public string Value
{
get => Property.GetValue(Target) as string ?? string.Empty;
set { if (IsEditable) { Property.SetValue(Target, value ?? string.Empty); Raise(); } }
}
}
/// <summary>
/// Dezimalzahl (decimal/double/float) – wie <see cref="IntSettingsField"/> bewusst als Text:
/// eine ungültige Eingabe lässt den bisherigen Wert stehen, statt still eine 0 zu schreiben.
///
/// <para>Vor dem 27.08.2026 fehlte dieser Feldtyp: alle Dezimal-Einstellungen fielen auf
/// <see cref="ReadOnlySettingsField"/> zurück und waren damit in der Oberfläche gar nicht mehr
/// änderbar – betroffen war die komplette Risikokonfiguration (Positionsgrößen, Preisgrenzen,
/// Kill-Switch-Schwellen), die unter WinForms im PropertyGrid noch editierbar war.</para>
///
/// <para>Beim Lesen wird die Anzeigekultur verwendet (de-DE: „5,0"), beim Schreiben werden
/// Komma und Punkt akzeptiert – sonst scheitert ein aus der Dokumentation kopierter Wert.</para>
/// </summary>
public sealed class DecimalSettingsField : SettingsField
{
public DecimalSettingsField(object t, PropertyInfo p, string l, string? d) : base(t, p, l, d) { }
public string Value
{
get
{
object? raw = Property.GetValue(Target);
return raw switch
{
decimal m => m.ToString("0.############", CultureInfo.CurrentCulture),
double db => db.ToString("0.############", CultureInfo.CurrentCulture),
float f => f.ToString("0.############", CultureInfo.CurrentCulture),
_ => string.Empty
};
}
set
{
if (!IsEditable) return;
if (TryParse(value, out decimal parsed))
{
Type t = Nullable.GetUnderlyingType(Property.PropertyType) ?? Property.PropertyType;
if (t == typeof(decimal)) Property.SetValue(Target, parsed);
else if (t == typeof(double)) Property.SetValue(Target, (double)parsed);
else if (t == typeof(float)) Property.SetValue(Target, (float)parsed);
}
Raise();
}
}
/// <summary>Nimmt „5,0" und „5.0" an – unabhängig von der eingestellten Kultur.</summary>
internal static bool TryParse(string? text, out decimal value)
{
value = 0m;
if (string.IsNullOrWhiteSpace(text)) return false;
const NumberStyles styles = NumberStyles.Float;
return decimal.TryParse(text, styles, CultureInfo.CurrentCulture, out value)
|| decimal.TryParse(text, styles, CultureInfo.InvariantCulture, out value)
|| decimal.TryParse(text.Replace(',', '.'), styles, CultureInfo.InvariantCulture, out value);
}
}
/// <summary>Ja/Nein (bool) – als Kontrollkästchen.</summary>
public sealed class BoolSettingsField : SettingsField
{
@@ -178,7 +255,15 @@ namespace PolyTrader.App.Avalonia.ViewModels
if (t.IsEnum) return new ChoiceSettingsField(target, p, label, description);
if (t == typeof(bool)) return new BoolSettingsField(target, p, label, description);
if (t == typeof(int) || t == typeof(long)) return new IntSettingsField(target, p, label, description);
if (t == typeof(string)) return new TextSettingsField(target, p, label, description);
if (t == typeof(decimal) || t == typeof(double) || t == typeof(float))
return new DecimalSettingsField(target, p, label, description);
if (t == typeof(string))
{
bool multiline = p.GetCustomAttribute<DataTypeAttribute>()?.DataType == DataType.MultilineText;
return multiline
? new MultilineTextSettingsField(target, p, label, description)
: new TextSettingsField(target, p, label, description);
}
// Listen u.a. werden angezeigt, aber nicht hier bearbeitet (z.B. DisabledModules –
// die pflegt der Modul-Tab des Dashboards).