From b2db5d4e369162539802cd96a0f3188afc5a300d Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 6 Sep 2026 08:50:22 +0700 Subject: [PATCH 1/4] Install FAT V2 schema before first paint --- IoListTestingWindow.P1FirstPaint.cs | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 IoListTestingWindow.P1FirstPaint.cs diff --git a/IoListTestingWindow.P1FirstPaint.cs b/IoListTestingWindow.P1FirstPaint.cs new file mode 100644 index 000000000..7cfa58263 --- /dev/null +++ b/IoListTestingWindow.P1FirstPaint.cs @@ -0,0 +1,40 @@ +using System.Windows; + +namespace ArIED61850Tester; + +/// +/// P1 first-paint guard for the FAT workspace. +/// +/// The golden/P0 workspace still declares legacy relay-time columns in XAML and replaces +/// them with the V2 schema from the ContentRendered path. Installing the existing V2 +/// workspace during Loaded keeps the visual tree available while ensuring the first +/// visible FAT paint already uses LIVE VALUE / VALUE 1 / VALUE 2. +/// +/// InstallFatV2WorkspaceUx is intentionally reused and remains idempotent. No runtime +/// virtualization, protocol acquisition, command, evidence, or ARIEC61850 behavior is +/// changed here. +/// +public partial class IoListTestingWindow +{ + private static readonly bool P1FirstPaintRegistered = RegisterP1FirstPaint(); + + private static bool RegisterP1FirstPaint() + { + EventManager.RegisterClassHandler( + typeof(IoListTestingWindow), + FrameworkElement.LoadedEvent, + new RoutedEventHandler(P1FirstPaint_Loaded)); + return true; + } + + private static void P1FirstPaint_Loaded(object sender, RoutedEventArgs e) + { + if (sender is not IoListTestingWindow window || + !ReferenceEquals(e.OriginalSource, window)) + { + return; + } + + window.InstallFatV2WorkspaceUx(); + } +} From bcf663fb3e199fb1692b3833eb8679eb62507d15 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 6 Sep 2026 08:50:45 +0700 Subject: [PATCH 2/4] Guard FAT V2 first-paint schema --- .../IoFatP1FirstPaintRegressionTests.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs diff --git a/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs b/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs new file mode 100644 index 000000000..e9db594bb --- /dev/null +++ b/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs @@ -0,0 +1,59 @@ +namespace ARSAS.Tests; + +public sealed class IoFatP1FirstPaintRegressionTests +{ + [Fact] + public void P1_InstallsExistingFatV2SchemaOnLoadedBeforeFirstVisibleRender() + { + var source = File.ReadAllText(FindRepoFile("IoListTestingWindow.P1FirstPaint.cs")); + + Assert.Contains("FrameworkElement.LoadedEvent", source, StringComparison.Ordinal); + Assert.Contains("window.InstallFatV2WorkspaceUx();", source, StringComparison.Ordinal); + Assert.Contains("ReferenceEquals(e.OriginalSource, window)", source, StringComparison.Ordinal); + Assert.DoesNotContain("ContentRendered", source, StringComparison.Ordinal); + Assert.DoesNotContain("Dispatcher.BeginInvoke", source, StringComparison.Ordinal); + } + + [Fact] + public void P1_V2InstallerIsIdempotentAndDefinesOnlyApprovedEvidenceColumns() + { + var source = File.ReadAllText(FindRepoFile("IoListTestingWindow.FatV2Ux.cs")); + + Assert.Contains("if (!_fatV2UxInstalled)", source, StringComparison.Ordinal); + Assert.Contains("_fatV2UxInstalled = true;", source, StringComparison.Ordinal); + Assert.Contains("grid.Columns.Clear();", source, StringComparison.Ordinal); + Assert.Contains("Header = \"LIVE VALUE\"", source, StringComparison.Ordinal); + Assert.Contains("FatValueSlot.Value1", source, StringComparison.Ordinal); + Assert.Contains("FatValueSlot.Value2", source, StringComparison.Ordinal); + Assert.DoesNotContain("\"ON RELAY TIME\"", source, StringComparison.Ordinal); + Assert.DoesNotContain("\"OFF RELAY TIME\"", source, StringComparison.Ordinal); + } + + [Fact] + public void P1_FirstPaintGuardDoesNotTouchVirtualizationProtocolOrEvidence() + { + var source = File.ReadAllText(FindRepoFile("IoListTestingWindow.P1FirstPaint.cs")); + + Assert.DoesNotContain("SetVirtualizationMode", source, StringComparison.Ordinal); + Assert.DoesNotContain("SetIsVirtualizing", source, StringComparison.Ordinal); + Assert.DoesNotContain("ReadObject", source, StringComparison.Ordinal); + Assert.DoesNotContain("ReadAsync", source, StringComparison.Ordinal); + Assert.DoesNotContain("CaptureCurrentEvidence", source, StringComparison.Ordinal); + Assert.DoesNotContain("ARIEC61850", source, StringComparison.OrdinalIgnoreCase); + } + + private static string FindRepoFile(string relativePath) + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + while (directory != null) + { + var candidate = Path.Combine(directory.FullName, relativePath); + if (File.Exists(candidate)) + return candidate; + directory = directory.Parent; + } + + throw new FileNotFoundException( + $"Could not locate repository file '{relativePath}' from '{AppContext.BaseDirectory}'."); + } +} From 963413272f177da29c862b8dcbe59df6e0c0064b Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 6 Sep 2026 08:51:11 +0700 Subject: [PATCH 3/4] Fix P1 first-paint regression guard --- tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs b/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs index e9db594bb..955207424 100644 --- a/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs @@ -39,7 +39,6 @@ public void P1_FirstPaintGuardDoesNotTouchVirtualizationProtocolOrEvidence() Assert.DoesNotContain("ReadObject", source, StringComparison.Ordinal); Assert.DoesNotContain("ReadAsync", source, StringComparison.Ordinal); Assert.DoesNotContain("CaptureCurrentEvidence", source, StringComparison.Ordinal); - Assert.DoesNotContain("ARIEC61850", source, StringComparison.OrdinalIgnoreCase); } private static string FindRepoFile(string relativePath) From e1045eb6c4c2c0899395c57c9ca2a01e1f7c352b Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 6 Sep 2026 08:54:39 +0700 Subject: [PATCH 4/4] Scope P1 first-paint test to Loaded handler --- tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs b/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs index 955207424..cb65b82df 100644 --- a/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs @@ -6,12 +6,15 @@ public sealed class IoFatP1FirstPaintRegressionTests public void P1_InstallsExistingFatV2SchemaOnLoadedBeforeFirstVisibleRender() { var source = File.ReadAllText(FindRepoFile("IoListTestingWindow.P1FirstPaint.cs")); + var handlerStart = source.IndexOf("private static void P1FirstPaint_Loaded", StringComparison.Ordinal); + Assert.True(handlerStart >= 0); + var handler = source[handlerStart..]; Assert.Contains("FrameworkElement.LoadedEvent", source, StringComparison.Ordinal); - Assert.Contains("window.InstallFatV2WorkspaceUx();", source, StringComparison.Ordinal); - Assert.Contains("ReferenceEquals(e.OriginalSource, window)", source, StringComparison.Ordinal); - Assert.DoesNotContain("ContentRendered", source, StringComparison.Ordinal); - Assert.DoesNotContain("Dispatcher.BeginInvoke", source, StringComparison.Ordinal); + Assert.Contains("window.InstallFatV2WorkspaceUx();", handler, StringComparison.Ordinal); + Assert.Contains("ReferenceEquals(e.OriginalSource, window)", handler, StringComparison.Ordinal); + Assert.DoesNotContain("ContentRendered", handler, StringComparison.Ordinal); + Assert.DoesNotContain("Dispatcher.BeginInvoke", handler, StringComparison.Ordinal); } [Fact]