From dbfda09cf1df34a77782399a4ad1f4762dcacaaa Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 18:08:57 +0700 Subject: [PATCH 01/14] P0 UI: replace warm greige palette with shared SMV-aligned cool-blue tokens (#730) --- Resources/P2BlueSteelGreige.xaml | 149 ++++++++++++++++++------------- 1 file changed, 85 insertions(+), 64 deletions(-) diff --git a/Resources/P2BlueSteelGreige.xaml b/Resources/P2BlueSteelGreige.xaml index b24959046..e4df6b8cd 100644 --- a/Resources/P2BlueSteelGreige.xaml +++ b/Resources/P2BlueSteelGreige.xaml @@ -3,11 +3,11 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + + #F5F8FD + #F8FBFF + #FFFFFF + #EDF5FF + #E8F2FF + #D7E5F6 + #E4F0FF + #DCEAFF + #28343D #65727B @@ -23,11 +33,11 @@ #185FB8 #DDE8F2 #263F52 - #C8CDD0 - #EEEAE3 - #F3F0EA - #C6CCCF - #9DA9B0 + #D7E5F6 + #F8FBFF + #FFFFFF + #D7E5F6 + #B5CAE5 #3E8E5B #C84C4C #C28A2C @@ -46,26 +56,37 @@ - + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -75,18 +96,18 @@ - - + + - - + + - - + + @@ -95,39 +116,39 @@ - - + + - - + + - - + + - + - + - + - - + + - - + + @@ -247,11 +268,11 @@ - - + + - + @@ -267,7 +288,7 @@ - + @@ -425,10 +446,10 @@ - + - + @@ -446,19 +467,19 @@ @@ -466,7 +487,7 @@ From 3a624cb030e316d8aa424038c791fd7e2a404592 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 18:10:03 +0700 Subject: [PATCH 04/14] P0 UX: open workstation as centered normal resizable window (#730) --- MainWindow.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MainWindow.xaml b/MainWindow.xaml index 2c8e00b73..368bae1e1 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -3,8 +3,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:views="clr-namespace:ArIED61850Tester.Views" Title="ARSAS - Smart IEC 61850 Communication Tester" - Height="900" Width="1480" MinHeight="760" MinWidth="1180" - WindowStartupLocation="CenterScreen" WindowState="Maximized" Background="{StaticResource AppBackgroundGradient}" + Height="840" Width="1440" MinHeight="640" MinWidth="1100" + WindowStartupLocation="CenterScreen" WindowState="Normal" Background="{StaticResource AppBackgroundGradient}" FontFamily="{StaticResource AppFontFamily}" UseLayoutRounding="True" SnapsToDevicePixels="True" TextOptions.TextFormattingMode="Ideal" From 23578d517e88859b3202fdb7fd3672d186defa30 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 18:10:07 +0700 Subject: [PATCH 05/14] P0 UX: open workstation as centered normal resizable window (#730) --- IoListTestingWindow.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IoListTestingWindow.xaml b/IoListTestingWindow.xaml index 02464b849..f1d6ab1b1 100644 --- a/IoListTestingWindow.xaml +++ b/IoListTestingWindow.xaml @@ -4,8 +4,8 @@ xmlns:io="clr-namespace:ArIED61850Tester.Models.IoTesting" xmlns:local="clr-namespace:ArIED61850Tester" Title="ARSAS - IO List Testing" - Height="900" Width="1500" MinHeight="760" MinWidth="1180" - WindowStartupLocation="CenterOwner" WindowState="Maximized" + Height="820" Width="1440" MinHeight="640" MinWidth="1100" + WindowStartupLocation="CenterOwner" WindowState="Normal" Background="{StaticResource AppBackgroundGradient}" FontFamily="{StaticResource AppFontFamily}" UseLayoutRounding="True" SnapsToDevicePixels="True" From 2bec0500ea0e10db517308cf3d4a0d02150fef68 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 18:10:10 +0700 Subject: [PATCH 06/14] P0 UX: align shared cool-blue styles and one-time startup placement (#730) --- App.xaml.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/App.xaml.cs b/App.xaml.cs index 19e1aaac3..a0a3fa053 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -37,9 +37,9 @@ protected override void OnStartup(StartupEventArgs e) return; } - // P2 installs one calm industrial visual system before StartupUri materializes. + // P0 installs one cool-blue/white workstation system before StartupUri materializes. // Existing XAML keeps its semantic resource keys while the overlay replaces - // glare-heavy white/blue surfaces with Blue Steel + Light Greige equivalents. + // warm legacy surfaces with shared blue-white semantic resources. InstallP2BlueSteelGreigeTheme(); // Keep every IED card on one reusable template key while replacing the @@ -92,6 +92,8 @@ private void AppWindow_Loaded(object sender, RoutedEventArgs e) if (_styledWindows.TryGetValue(window, out _)) return; + // Initial placement is applied ONCE before first paint; never on activation or resize. + WorkstationWindowStartupBounds.Apply(window); P2BlueSteelGreigeUx.ApplyToOpenWindows(this); _styledWindows.Add(window, new object()); From cadbce6419046337b9330e16335315672ef7f3ac Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 18:10:46 +0700 Subject: [PATCH 07/14] P0 UX: clamp centered initial window bounds once without disabling user resizing (#730) --- WorkstationWindowStartupBounds.cs | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 WorkstationWindowStartupBounds.cs diff --git a/WorkstationWindowStartupBounds.cs b/WorkstationWindowStartupBounds.cs new file mode 100644 index 000000000..4ca349df6 --- /dev/null +++ b/WorkstationWindowStartupBounds.cs @@ -0,0 +1,32 @@ +using System; +using System.Windows; + +namespace ArIED61850Tester; + +/// +/// First-paint-only bounds for the interactive workstation windows. +/// Never intercept user move, resize, maximize or restore. +/// Hidden IO FAT donor windows use Manual placement and remain untouched. +/// +internal static class WorkstationWindowStartupBounds +{ + internal static void Apply(Window window) + { + if (window is not MainWindow && window is not IoListTestingWindow) + return; + + // The embedded FAT donor is intentionally hidden offscreen. + if (window.WindowStartupLocation == WindowStartupLocation.Manual) + return; + + var work = SystemParameters.WorkArea; // WPF device-independent units. + const double inset = 24d; + var maxWidth = Math.Max(window.MinWidth, work.Width - 2d * inset); + var maxHeight = Math.Max(window.MinHeight, work.Height - 2d * inset); + + window.Width = Math.Clamp(window.Width, window.MinWidth, maxWidth); + window.Height = Math.Clamp(window.Height, window.MinHeight, maxHeight); + window.Left = work.Left + Math.Max(0d, (work.Width - window.Width) / 2d); + window.Top = work.Top + Math.Max(0d, (work.Height - window.Height) / 2d); + } +} From 054b0e9227e583728ab8bdae3c8d5700986c7349 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 18:11:31 +0700 Subject: [PATCH 08/14] test(P0 UX): lock SMV-aligned cool-blue theme and normal centered startup (#730) --- .../P2BlueSteelGreigeThemeTests.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/tests/ARSAS.Tests/P2BlueSteelGreigeThemeTests.cs b/tests/ARSAS.Tests/P2BlueSteelGreigeThemeTests.cs index 930897d0e..68a7d4fc7 100644 --- a/tests/ARSAS.Tests/P2BlueSteelGreigeThemeTests.cs +++ b/tests/ARSAS.Tests/P2BlueSteelGreigeThemeTests.cs @@ -5,7 +5,7 @@ namespace ARSAS.Tests; public sealed class P2BlueSteelGreigeThemeTests { [Fact] - public void Theme_UsesBlueSteelShellAndWarmGreigeZebraRows() + public void Theme_UsesBlueSteelShellAndCoolBlueZebraRows() { var source = File.ReadAllText(FindRepoFile("Resources/P2BlueSteelGreige.xaml")); var document = XDocument.Parse(source); @@ -19,9 +19,15 @@ string ResourceValue(string elementName, string key, string attribute) ?? throw new Xunit.Sdk.XunitException($"Missing {key}.{attribute}"); Assert.Equal("#2A465B", ResourceValue("SolidColorBrush", "BlueSteelNavSurface", "Color")); - Assert.Equal("#F3F0EA", ResourceValue("SolidColorBrush", "GreigeRowA", "Color")); - Assert.Equal("#ECE8E1", ResourceValue("SolidColorBrush", "GreigeRowB", "Color")); - Assert.Equal("#E1DDD5", ResourceValue("SolidColorBrush", "GreigeHeader", "Color")); + Assert.Equal("#F8FBFF", document.Descendants(presentation + "Color") + .Single(node => (string?)node.Attribute(x + "Key") == "CoolPanelColor").Value); + Assert.Equal("#EDF5FF", document.Descendants(presentation + "Color") + .Single(node => (string?)node.Attribute(x + "Key") == "CoolAlternateColor").Value); + Assert.Equal("#E8F2FF", document.Descendants(presentation + "Color") + .Single(node => (string?)node.Attribute(x + "Key") == "CoolHeaderColor").Value); + Assert.Equal("{StaticResource CoolPanelColor}", ResourceValue("SolidColorBrush", "GreigeRowA", "Color")); + Assert.Equal("{StaticResource CoolAlternateColor}", ResourceValue("SolidColorBrush", "GreigeRowB", "Color")); + Assert.Equal("{StaticResource CoolHeaderColor}", ResourceValue("SolidColorBrush", "GreigeHeader", "Color")); var appBackground = document.Descendants(presentation + "LinearGradientBrush") .Single(node => (string?)node.Attribute(x + "Key") == "AppBackgroundGradient") @@ -33,9 +39,9 @@ string ResourceValue(string elementName, string key, string attribute) .Single(node => (string?)node.Attribute(x + "Key") == "ModernDataGrid") .ToString(); Assert.Contains("RowBackground", modernGrid, StringComparison.Ordinal); - Assert.Contains("GreigeRowA", modernGrid, StringComparison.Ordinal); + Assert.Contains("GridRowPrimary", modernGrid, StringComparison.Ordinal); Assert.Contains("AlternatingRowBackground", modernGrid, StringComparison.Ordinal); - Assert.Contains("GreigeRowB", modernGrid, StringComparison.Ordinal); + Assert.Contains("GridRowAlternate", modernGrid, StringComparison.Ordinal); Assert.Contains("AlternationCount", modernGrid, StringComparison.Ordinal); } @@ -53,16 +59,17 @@ public void Startup_InstallsThemeBeforeRelayFasciaAndAppliesWindowAdapter() } [Fact] - public void RuntimeAdapter_KeepsMaximizedWorkstationAndFatZebraRows() + public void RuntimeAdapter_PreservesWindowGeometryAndBlueGridRows() { var source = File.ReadAllText(FindRepoFile("P2BlueSteelGreigeUx.cs")); - Assert.Contains("window.WindowState = WindowState.Maximized", source, StringComparison.Ordinal); + Assert.DoesNotContain("WindowState.Maximized", source, StringComparison.Ordinal); + Assert.DoesNotContain("ApplyEngineeringGrid", source, StringComparison.Ordinal); Assert.Contains("P2MainIedListItemStyle", source, StringComparison.Ordinal); Assert.Contains("P2FatIedListItemStyle", source, StringComparison.Ordinal); Assert.Contains("P2FatDataGridRow", source, StringComparison.Ordinal); - Assert.Contains("GreigeRowA", source, StringComparison.Ordinal); - Assert.Contains("GreigeRowB", source, StringComparison.Ordinal); + Assert.Contains("GridRowPrimary", source, StringComparison.Ordinal); + Assert.Contains("GridRowAlternate", source, StringComparison.Ordinal); Assert.Contains("AlternationCount = 2", source, StringComparison.Ordinal); } From 01ffbc18b7d68a6e5962d736c53c8bcc02eb0b1f Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 18:11:34 +0700 Subject: [PATCH 09/14] test(P0 UX): lock SMV-aligned cool-blue theme and normal centered startup (#730) --- tests/ARSAS.Tests/StartupVisualStabilityRegressionTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/ARSAS.Tests/StartupVisualStabilityRegressionTests.cs b/tests/ARSAS.Tests/StartupVisualStabilityRegressionTests.cs index 12e1e92fe..be457934c 100644 --- a/tests/ARSAS.Tests/StartupVisualStabilityRegressionTests.cs +++ b/tests/ARSAS.Tests/StartupVisualStabilityRegressionTests.cs @@ -45,7 +45,9 @@ public void WindowAdapter_IsOnceOnlyAndNeverOverridesCanonicalInterFont() public void ResponsiveNavigation_OwnsShellHeight_WithoutLateClobber() { var main = XDocument.Parse(Read("MainWindow.xaml")); - Assert.Equal("Maximized", (string?)main.Root?.Attribute("WindowState")); + Assert.Equal("Normal", (string?)main.Root?.Attribute("WindowState")); + Assert.Equal("CenterScreen", (string?)main.Root?.Attribute("WindowStartupLocation")); + Assert.Contains("WorkstationWindowStartupBounds.Apply(window);", app, StringComparison.Ordinal); var responsive = Read("MainWindow.NavigationLayoutFix.cs"); var p2 = Read("P2IndustrialWorkstationUx.cs"); From d9b97c4856e8cadba1c4ab282ee34de26fb2efa4 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 18:12:20 +0700 Subject: [PATCH 10/14] test(P0 UX): guard cool-blue tokens, grid headers, normal first paint and resize freedom (#730) --- .../ARSAS.Tests/P0CoolBlueWorkstationTests.cs | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 tests/ARSAS.Tests/P0CoolBlueWorkstationTests.cs diff --git a/tests/ARSAS.Tests/P0CoolBlueWorkstationTests.cs b/tests/ARSAS.Tests/P0CoolBlueWorkstationTests.cs new file mode 100644 index 000000000..e476d6764 --- /dev/null +++ b/tests/ARSAS.Tests/P0CoolBlueWorkstationTests.cs @@ -0,0 +1,80 @@ +using System.Xml.Linq; + +namespace ARSAS.Tests; + +/// +/// P0 visual contract: one cool-blue source across engineering grids, and no +/// automatic main-window maximization after the first frame. +/// +public sealed class P0CoolBlueWorkstationTests +{ + [Fact] + public void AllEngineeringGridsShareCoolBlueSemanticResources() + { + var xaml = XDocument.Parse(Read("Resources/P2BlueSteelGreige.xaml")); + XNamespace wpf = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"; + XNamespace x = "http://schemas.microsoft.com/winfx/2006/xaml"; + + var colors = new Dictionary + { + ["CoolPanelColor"] = "#F8FBFF", + ["CoolAlternateColor"] = "#EDF5FF", + ["CoolHeaderColor"] = "#E8F2FF", + ["CoolBorderColor"] = "#D7E5F6", + ["CoolHoverColor"] = "#E4F0FF" + }; + foreach (var (name, color) in colors) + Assert.Equal(color, xaml.Descendants(wpf + "Color") + .Single(e => (string?)e.Attribute(x + "Key") == name).Value); + + var modern = xaml.Descendants(wpf + "Style") + .Single(e => (string?)e.Attribute(x + "Key") == "ModernDataGrid"); + Assert.Contains(modern.Elements(wpf + "Setter"), e => + (string?)e.Attribute("Property") == "RowBackground" && + (string?)e.Attribute("Value") == "{StaticResource GridRowPrimary}"); + Assert.Contains(modern.Elements(wpf + "Setter"), e => + (string?)e.Attribute("Property") == "AlternatingRowBackground" && + (string?)e.Attribute("Value") == "{StaticResource GridRowAlternate}"); + + var industrial = XDocument.Parse(Read("Resources/P2IndustrialControls.xaml")); + var header = industrial.Descendants(wpf + "Style") + .Single(e => (string?)e.Attribute(x + "Key") == "IndustrialGridHeader"); + Assert.Contains(header.Elements(wpf + "Setter"), e => + (string?)e.Attribute("Property") == "Background" && + (string?)e.Attribute("Value") == "{DynamicResource GridHeaderSurface}"); + } + + [Fact] + public void WindowGeometryIsUserControlledAfterCenteredNormalStartup() + { + foreach (var path in new[] { "MainWindow.xaml", "IoListTestingWindow.xaml" }) + { + var root = XDocument.Parse(Read(path)).Root!; + Assert.Equal("Normal", (string?)root.Attribute("WindowState")); + Assert.InRange(double.Parse(root.Attribute("MinHeight")!.Value), 600d, 700d); + } + var main = XDocument.Parse(Read("MainWindow.xaml")).Root!; + Assert.Equal("CenterScreen", (string?)main.Attribute("WindowStartupLocation")); + + var adapter = Read("P2BlueSteelGreigeUx.cs"); + Assert.DoesNotContain("WindowState.Maximized", adapter, StringComparison.Ordinal); + Assert.DoesNotContain("ApplyEngineeringGrid", adapter, StringComparison.Ordinal); + + var startup = Read("WorkstationWindowStartupBounds.cs"); + Assert.Contains("SystemParameters.WorkArea", startup, StringComparison.Ordinal); + Assert.Contains("WindowStartupLocation.Manual", startup, StringComparison.Ordinal); + Assert.DoesNotContain("SizeChanged +=", startup, StringComparison.Ordinal); + Assert.DoesNotContain("Activated +=", startup, StringComparison.Ordinal); + Assert.Contains("WorkstationWindowStartupBounds.Apply(window);", Read("App.xaml.cs"), StringComparison.Ordinal); + } + + private static string Read(string path) + { + for (DirectoryInfo? directory = new(AppContext.BaseDirectory); directory != null; directory = directory.Parent) + { + var full = Path.Combine(directory.FullName, path); + if (File.Exists(full)) return File.ReadAllText(full); + } + throw new FileNotFoundException(path); + } +} From 392db4a7bd1b37f4eed119eee20d9508b347ef17 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 18:14:46 +0700 Subject: [PATCH 11/14] P0 UX: preserve explicit SMV headers and eliminate warm scrollbar surfaces (#730) --- P2IndustrialWorkstationUx.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/P2IndustrialWorkstationUx.cs b/P2IndustrialWorkstationUx.cs index fa558bda2..bdfd8a457 100644 --- a/P2IndustrialWorkstationUx.cs +++ b/P2IndustrialWorkstationUx.cs @@ -408,6 +408,10 @@ private static void ApplyIndustrialGridHeaders(FrameworkElement owner, bool skip { if (skipCommandGrid && grid.Style == commandStyle) continue; + // A tab-specific instrument (e.g. SMV Measurements) owns its explicit + // header style. The global adapter must not override that contract. + if (grid.ReadLocalValue(DataGrid.ColumnHeaderStyleProperty) != DependencyProperty.UnsetValue) + continue; grid.ColumnHeaderStyle = header; } } From 909e8990e5077483100d54f43030b0d9e8cab68a Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 18:14:50 +0700 Subject: [PATCH 12/14] P0 UX: preserve explicit SMV headers and eliminate warm scrollbar surfaces (#730) --- Resources/P2BlueSteelGreige.xaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Resources/P2BlueSteelGreige.xaml b/Resources/P2BlueSteelGreige.xaml index e4df6b8cd..42564a713 100644 --- a/Resources/P2BlueSteelGreige.xaml +++ b/Resources/P2BlueSteelGreige.xaml @@ -533,7 +533,7 @@ left card edge, never in a detached 5px gutter. --> @@ -588,7 +588,7 @@ left card edge, never in a detached 5px gutter. --> @@ -620,20 +620,20 @@ From 743ab48be97b7f774b9e278103670c626fcdba31 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 18:16:13 +0700 Subject: [PATCH 13/14] P0 UX: unify shared search and utility buttons; protect primary hover contrast (#730) --- Resources/P2IndustrialControls.xaml | 34 +++++++++++++++-------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Resources/P2IndustrialControls.xaml b/Resources/P2IndustrialControls.xaml index 518e5bacb..a5f4d238d 100644 --- a/Resources/P2IndustrialControls.xaml +++ b/Resources/P2IndustrialControls.xaml @@ -25,9 +25,9 @@ - - - + + + @@ -57,13 +57,13 @@ - - + + - + @@ -75,9 +75,9 @@ - - - + + + @@ -92,11 +92,11 @@ - - + + - + @@ -107,10 +107,12 @@ -