From 0fba4339ba7d46f693aa3d26696a629169bf9bdf Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 10:15:27 +0700 Subject: [PATCH 01/22] Lock P0 visual system unification contracts --- .../VisualSystemP0RegressionTests.cs | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs diff --git a/tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs b/tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs new file mode 100644 index 000000000..c5b7ac281 --- /dev/null +++ b/tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs @@ -0,0 +1,96 @@ +namespace ARSAS.Tests; + +public sealed class VisualSystemP0RegressionTests +{ + [Fact] + public void AppTheme_ExposesOneCompactWorkspaceTypographyAndSurfaceContract() + { + var app = File.ReadAllText(FindRepoFile("App.xaml")); + + Assert.Contains("x:Key=\"WorkspaceTitle\"", app, StringComparison.Ordinal); + Assert.Contains("x:Key=\"WorkspaceSubtitle\"", app, StringComparison.Ordinal); + Assert.Contains("x:Key=\"WorkspaceCard\"", app, StringComparison.Ordinal); + Assert.Contains("x:Key=\"SearchSurface\"", app, StringComparison.Ordinal); + Assert.Contains("", Slice(app, "x:Key=\"WorkspaceTitle\"", ""), StringComparison.Ordinal); + Assert.Contains("", Slice(app, "x:Key=\"WorkspaceSubtitle\"", ""), StringComparison.Ordinal); + Assert.Contains("", Slice(app, "x:Key=\"WorkspaceCard\"", ""), StringComparison.Ordinal); + Assert.Contains("", Slice(app, "x:Key=\"WorkspaceCard\"", ""), StringComparison.Ordinal); + } + + [Fact] + public void Navigation_UsesStableTypographyAndKeyboardOnlyInternalFocusRing() + { + var app = File.ReadAllText(FindRepoFile("App.xaml")); + var nav = Slice(app, "x:Key=\"SegmentedNavButton\"", ""); + + Assert.Contains("FontSize\" Value=\"12.5\"", nav, StringComparison.Ordinal); + Assert.Contains("FocusVisualStyle\" Value=\"{x:Null}\"", nav, StringComparison.Ordinal); + Assert.Contains("x:Name=\"KeyboardFocusRing\"", nav, StringComparison.Ordinal); + Assert.Contains("Property=\"IsKeyboardFocused\"", nav, StringComparison.Ordinal); + Assert.DoesNotContain("Property=\"FontSize\" Value=\"13.45\"", nav, StringComparison.Ordinal); + Assert.DoesNotContain("Property=\"FontSize\" Value=\"12.55\"", nav, StringComparison.Ordinal); + } + + [Fact] + public void MainWorkspaces_UseSharedWorkspaceSurfaceAndHeaderTypography() + { + var xaml = File.ReadAllText(FindRepoFile("MainWindow.xaml")); + + Assert.Contains("Style=\"{StaticResource WorkspaceCard}\"", xaml, StringComparison.Ordinal); + Assert.Contains("Text=\"Global Multi-IED Live Monitor\" Style=\"{StaticResource WorkspaceTitle}\"", xaml, StringComparison.Ordinal); + Assert.Contains("Text=\"IEC 61850 Sequence of Events\" Style=\"{StaticResource WorkspaceTitle}\"", xaml, StringComparison.Ordinal); + Assert.Contains("Text=\"Diagnostics & Communication Journal\" Style=\"{StaticResource WorkspaceTitle}\"", xaml, StringComparison.Ordinal); + Assert.Contains("Text=\"Alarm Annunciator\" Style=\"{StaticResource WorkspaceTitle}\"", xaml, StringComparison.Ordinal); + Assert.DoesNotContain("Every IED keeps its own connection, report subscription, validated reads, and event-driven RCB state.", xaml, StringComparison.Ordinal); + Assert.DoesNotContain("SCADA/SAS SOE: state values use ARSAS blue for active", xaml, StringComparison.Ordinal); + } + + [Fact] + public void LiveMonitor_GlobalSearchIsPrimary_AndColumnFiltersAreProgressiveDisclosure() + { + var xaml = File.ReadAllText(FindRepoFile("MainWindow.xaml")); + var behavior = File.ReadAllText(FindRepoFile("GridUxBehavior.cs")); + var bridge = File.ReadAllText(FindRepoFile("MainWindow.GlobalLiveSearch.cs")); + var section = Slice(xaml, "", ""); + + Assert.Contains("x:Name=\"GlobalLiveSearchBox\"", section, StringComparison.Ordinal); + Assert.Contains("x:Name=\"GlobalLiveFiltersButton\"", section, StringComparison.Ordinal); + Assert.Contains("GlobalLiveFilters_Click", section, StringComparison.Ordinal); + Assert.Contains("SetGlobalRapidFiltersExpanded", behavior, StringComparison.Ordinal); + Assert.Contains("FiltersExpanded", behavior, StringComparison.Ordinal); + Assert.Contains("new GridLength(0)", behavior, StringComparison.Ordinal); + Assert.Contains("ColumnHeaderHeight = expanded ? 68 : 34", behavior, StringComparison.Ordinal); + Assert.Contains("SetGlobalRapidFiltersExpanded(GlobalLiveGrid", bridge, StringComparison.Ordinal); + } + + [Fact] + public void P0_RemainsPresentationOnly() + { + var testDirectory = Path.GetDirectoryName(FindRepoFile("MainWindow.xaml"))!; + var engineLock = File.ReadAllText(Path.Combine(testDirectory, "engines", "ARIEC61850.lock.json")); + + Assert.Contains("manual-reviewed-immutable-commit", engineLock, StringComparison.OrdinalIgnoreCase); + } + + private static string Slice(string source, string start, string end) + { + var a = source.IndexOf(start, StringComparison.Ordinal); + Assert.True(a >= 0, $"Start marker not found: {start}"); + var b = source.IndexOf(end, a + start.Length, StringComparison.Ordinal); + Assert.True(b > a, $"End marker not found: {end}"); + return source[a..b]; + } + + 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(relativePath); + } +} From 69fcb005f364923374bea9f70cd1c5ad67b12306 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 10:17:02 +0700 Subject: [PATCH 02/22] Add deterministic P0 visual system patch --- scripts/patch-p0-visual-system.py | 426 ++++++++++++++++++++++++++++++ 1 file changed, 426 insertions(+) create mode 100644 scripts/patch-p0-visual-system.py diff --git a/scripts/patch-p0-visual-system.py b/scripts/patch-p0-visual-system.py new file mode 100644 index 000000000..003b65448 --- /dev/null +++ b/scripts/patch-p0-visual-system.py @@ -0,0 +1,426 @@ +from pathlib import Path +import re + + +def replace_once(text: str, old: str, new: str, label: str) -> str: + count = text.count(old) + if count != 1: + raise SystemExit(f"{label}: expected exactly one match, found {count}") + return text.replace(old, new, 1) + + +def regex_once(text: str, pattern: str, replacement: str, label: str) -> str: + result, count = re.subn(pattern, replacement, text, count=1, flags=re.S) + if count != 1: + raise SystemExit(f"{label}: expected exactly one regex match, found {count}") + return result + + +app_path = Path("App.xaml") +main_path = Path("MainWindow.xaml") +grid_path = Path("GridUxBehavior.cs") +search_path = Path("MainWindow.GlobalLiveSearch.cs") +sas_path = Path("SasOperationalUiPolicy.cs") + +app = app_path.read_text(encoding="utf-8") +main = main_path.read_text(encoding="utf-8") +grid = grid_path.read_text(encoding="utf-8") +search = search_path.read_text(encoding="utf-8") +sas = sas_path.read_text(encoding="utf-8") + +# --------------------------------------------------------------------------- +# App.xaml — compact typography, workspace surfaces, stable navigation. +# --------------------------------------------------------------------------- +old_typography = ''' + + + + + + + +''' +new_typography = ''' + + + + + + + + + + + + + + + + +''' +app = replace_once(app, old_typography, new_typography, "typography block") + +nav_pattern = r''' \n\n + + + + + + + + + + + + + + + ''', +''' + + + ''', "event log header") + +main = replace_once(main, '', + '', "alarm title") +main = replace_once(main, 'Text="IED FASCIA • FLASH = UNACK • STEADY = ACK • RTN = RETURNED"\n FontSize="9.8" FontWeight="SemiBold" Foreground="#7A8797" Margin="0,2,0,0"/', + 'Text="FLASH = UNACK • STEADY = ACK • RTN = RETURNED"\n Style="{StaticResource MicroLabel}" Foreground="#7A8797" Margin="0,2,0,0"/', "alarm legend") + +main = replace_once(main, +''' + ''', +''' + ''', "diagnostics header") + +# GOOSE primary title follows the same section-level typography rather than an arbitrary size. +main = replace_once(main, '', + '', "goose title") + +# --------------------------------------------------------------------------- +# GridUxBehavior.cs — global search is primary; filters expand only on demand. +# --------------------------------------------------------------------------- +grid = replace_once(grid, +''' public Dictionary Filters { get; } = new(StringComparer.OrdinalIgnoreCase); + public string SearchQuery { get; set; } = string.Empty;''', +''' public Dictionary Filters { get; } = new(StringComparer.OrdinalIgnoreCase); + public List HeaderRoots { get; } = new(); + public string SearchQuery { get; set; } = string.Empty; + public bool FiltersExpanded { get; set; }''', "rapid filter state") + +grid = replace_once(grid, +''' headerStyle.Setters.Add(new Setter(Control.BorderBrushProperty, new SolidColorBrush(Color.FromRgb(220, 227, 236)))); + headerStyle.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0, 0, 1, 1))); + headerStyle.Setters.Add(new Setter(FrameworkElement.HeightProperty, 74d)); + grid.ColumnHeaderStyle = headerStyle; + grid.ColumnHeaderHeight = 74;''', +''' headerStyle.Setters.Add(new Setter(Control.BorderBrushProperty, new SolidColorBrush(Color.FromRgb(220, 227, 236)))); + headerStyle.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0, 0, 1, 1))); + grid.ColumnHeaderStyle = headerStyle; + grid.ColumnHeaderHeight = 34;''', "collapsed header baseline") + +grid = replace_once(grid, +''' internal static void SetGlobalRapidSearch(DataGrid grid, string? query) + { + if (!GlobalGrids.TryGetValue(grid, out var state)) + return; + + state.SearchQuery = query?.Trim() ?? string.Empty; + state.RefreshTimer.Stop(); + state.RefreshTimer.Start(); + } +''', +''' internal static void SetGlobalRapidSearch(DataGrid grid, string? query) + { + if (!GlobalGrids.TryGetValue(grid, out var state)) + return; + + state.SearchQuery = query?.Trim() ?? string.Empty; + state.RefreshTimer.Stop(); + state.RefreshTimer.Start(); + } + + internal static void SetGlobalRapidFiltersExpanded(DataGrid grid, bool expanded) + { + if (!GlobalGrids.TryGetValue(grid, out var state)) + return; + + state.FiltersExpanded = expanded; + foreach (var root in state.HeaderRoots) + { + if (root.RowDefinitions.Count < 2) + continue; + root.RowDefinitions[1].Height = expanded ? new GridLength(34) : new GridLength(0); + } + grid.ColumnHeaderHeight = expanded ? 68 : 34; + grid.UpdateLayout(); + } +''', "filters disclosure method") + +grid = replace_once(grid, +''' root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(38) }); + root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(36) });''', +''' root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(34) }); + root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(0) });''', "filter header row geometry") +grid = replace_once(grid, ' FontSize = 12.5,\n VerticalAlignment = VerticalAlignment.Center,', + ' FontSize = 12.0,\n VerticalAlignment = VerticalAlignment.Center,', "rapid header title font") +grid = replace_once(grid, +''' var filterBox = CreateRapidFilterTextBox(state, caption); + Grid.SetRow(filterBox, 1); + root.Children.Add(filterBox); + + return root;''', +''' var filterBox = CreateRapidFilterTextBox(state, caption); + Grid.SetRow(filterBox, 1); + root.Children.Add(filterBox); + state.HeaderRoots.Add(root); + + return root;''', "track filter header roots") +grid = replace_once(grid, ' Height = 36,\n Padding = new Thickness(10, 0, 7, 0),\n FontSize = 12.5,', + ' Height = 34,\n Padding = new Thickness(9, 0, 7, 0),\n FontSize = 12.0,', "rapid filter input density") +grid = replace_once(grid, ' watermark.SetValue(TextBlock.FontSizeProperty, 12.5d);', + ' watermark.SetValue(TextBlock.FontSizeProperty, 12.0d);', "rapid filter watermark font") + +# --------------------------------------------------------------------------- +# Global Live search bridge — one-click filter disclosure. +# --------------------------------------------------------------------------- +search = '''using System.Windows; +using System.Windows.Controls; + +namespace ArIED61850Tester; + +public partial class MainWindow +{ + private bool _globalLiveFiltersExpanded; + + private void GlobalLiveSearch_TextChanged(object sender, TextChangedEventArgs e) + => GridUxBehavior.SetGlobalRapidSearch(GlobalLiveGrid, GlobalLiveSearchBox?.Text); + + private void GlobalLiveSearchClear_Click(object sender, RoutedEventArgs e) + { + if (GlobalLiveSearchBox == null) + return; + + GlobalLiveSearchBox.Clear(); + GlobalLiveSearchBox.Focus(); + } + + private void GlobalLiveFilters_Click(object sender, RoutedEventArgs e) + { + _globalLiveFiltersExpanded = !_globalLiveFiltersExpanded; + GridUxBehavior.SetGlobalRapidFiltersExpanded(GlobalLiveGrid, _globalLiveFiltersExpanded); + if (GlobalLiveFiltersLabel != null) + GlobalLiveFiltersLabel.Text = _globalLiveFiltersExpanded ? "Hide filters" : "Filters"; + } +} +''' + +# --------------------------------------------------------------------------- +# Runtime nav policy must match the XAML/theme contract after Loaded. +# --------------------------------------------------------------------------- +sas = replace_once(sas, ' shell.Width = 760;\n shell.Height = 56;\n shell.Padding = new Thickness(5);\n shell.CornerRadius = new CornerRadius(20);', + ' shell.Width = 900;\n shell.Height = 56;\n shell.Padding = new Thickness(5);\n shell.CornerRadius = new CornerRadius(14);', "runtime nav shell") +sas = replace_once(sas, ' button.FontSize = 12.4;\n button.PreviewMouseLeftButtonUp -= OnNavigationClick;', + ' button.FontSize = 12.5;\n button.FocusVisualStyle = null;\n button.PreviewMouseLeftButtonUp -= OnNavigationClick;', "runtime nav typography") +sas = sas.replace('CornerRadius="14"', 'CornerRadius="10"') + +app_path.write_text(app, encoding="utf-8", newline="\n") +main_path.write_text(main, encoding="utf-8", newline="\n") +grid_path.write_text(grid, encoding="utf-8", newline="\n") +search_path.write_text(search, encoding="utf-8", newline="\n") +sas_path.write_text(sas, encoding="utf-8", newline="\n") + +print("Applied P0 visual system unification: typography, surfaces, stable nav and progressive Live Monitor filters.") From 85e017cdac95df21ea6f0d8614f5e0e5564d43b7 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 10:17:37 +0700 Subject: [PATCH 03/22] Run deterministic P0 visual system patch --- .../workflows/temp-apply-p0-visual-system.yml | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/temp-apply-p0-visual-system.yml diff --git a/.github/workflows/temp-apply-p0-visual-system.yml b/.github/workflows/temp-apply-p0-visual-system.yml new file mode 100644 index 000000000..dd7bd9e22 --- /dev/null +++ b/.github/workflows/temp-apply-p0-visual-system.yml @@ -0,0 +1,53 @@ +name: Temporary P0 visual system patch + +on: + push: + branches: + - polish/p0-visual-system-unification + +permissions: + contents: write + +jobs: + patch: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: polish/p0-visual-system-unification + fetch-depth: 0 + + - name: Normalize temporary patch helper + shell: bash + run: | + python - <<'PY' + from pathlib import Path + path = Path('scripts/patch-p0-visual-system.py') + text = path.read_text(encoding='utf-8') + text = text.replace('Margin=\\"0,2,0,0\\"/\'', 'Margin=\\"0,2,0,0\\"/>\'') + text = text.replace('Style=\\"{StaticResource Caption}\\" Foreground=\\"#98A2B3\\">', 'Foreground=\\"#98A2B3\\">') + path.write_text(text, encoding='utf-8', newline='\n') + PY + + - name: Apply P0 visual system patch + run: python scripts/patch-p0-visual-system.py + + - name: Validate focused diff + shell: bash + run: | + git diff --check + git diff -- App.xaml MainWindow.xaml GridUxBehavior.cs MainWindow.GlobalLiveSearch.cs SasOperationalUiPolicy.cs + + - name: Commit production P0 changes + shell: bash + run: | + if git diff --quiet -- App.xaml MainWindow.xaml GridUxBehavior.cs MainWindow.GlobalLiveSearch.cs SasOperationalUiPolicy.cs; then + echo "No production changes to commit." + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add App.xaml MainWindow.xaml GridUxBehavior.cs MainWindow.GlobalLiveSearch.cs SasOperationalUiPolicy.cs + git commit -m "Unify P0 workspace visual system" + git push origin HEAD:polish/p0-visual-system-unification From 335158c8424143c290881408c973104256020e7e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 03:17:45 +0000 Subject: [PATCH 04/22] Unify P0 workspace visual system --- App.xaml | 68 ++++++++++++--- GridUxBehavior.cs | 36 ++++++-- MainWindow.GlobalLiveSearch.cs | 10 +++ MainWindow.xaml | 153 ++++++++++++++++++--------------- SasOperationalUiPolicy.cs | 11 +-- 5 files changed, 180 insertions(+), 98 deletions(-) diff --git a/App.xaml b/App.xaml index a26f4a708..4f824166e 100644 --- a/App.xaml +++ b/App.xaml @@ -189,33 +189,64 @@ - + + + + + + + + + + @@ -745,7 +784,8 @@ - + + diff --git a/GridUxBehavior.cs b/GridUxBehavior.cs index f3514172b..44127f65c 100644 --- a/GridUxBehavior.cs +++ b/GridUxBehavior.cs @@ -41,7 +41,9 @@ private sealed class GlobalRapidFilterState public required ICollectionView View { get; init; } public required DispatcherTimer RefreshTimer { get; init; } public Dictionary Filters { get; } = new(StringComparer.OrdinalIgnoreCase); + public List HeaderRoots { get; } = new(); public string SearchQuery { get; set; } = string.Empty; + public bool FiltersExpanded { get; set; } } private static readonly ConditionalWeakTable MainWindows = new(); @@ -494,9 +496,8 @@ private static void InstallGlobalRapidFilters(MainWindow owner, DataGrid grid) headerStyle.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Color.FromRgb(243, 246, 250)))); headerStyle.Setters.Add(new Setter(Control.BorderBrushProperty, new SolidColorBrush(Color.FromRgb(220, 227, 236)))); headerStyle.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0, 0, 1, 1))); - headerStyle.Setters.Add(new Setter(FrameworkElement.HeightProperty, 74d)); grid.ColumnHeaderStyle = headerStyle; - grid.ColumnHeaderHeight = 74; + grid.ColumnHeaderHeight = 34; foreach (var column in grid.Columns) { @@ -515,6 +516,22 @@ internal static void SetGlobalRapidSearch(DataGrid grid, string? query) state.RefreshTimer.Start(); } + internal static void SetGlobalRapidFiltersExpanded(DataGrid grid, bool expanded) + { + if (!GlobalGrids.TryGetValue(grid, out var state)) + return; + + state.FiltersExpanded = expanded; + foreach (var root in state.HeaderRoots) + { + if (root.RowDefinitions.Count < 2) + continue; + root.RowDefinitions[1].Height = expanded ? new GridLength(34) : new GridLength(0); + } + grid.ColumnHeaderHeight = expanded ? 68 : 34; + grid.UpdateLayout(); + } + private static void ApplyGlobalColumnStretch(DataGrid grid) { var layout = new Dictionary(StringComparer.OrdinalIgnoreCase) @@ -553,8 +570,8 @@ private static FrameworkElement BuildRapidFilterHeader(GlobalRapidFilterState st Source = column, Mode = BindingMode.OneWay }); - root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(38) }); - root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(36) }); + root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(34) }); + root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(0) }); var title = new TextBlock { @@ -562,7 +579,7 @@ private static FrameworkElement BuildRapidFilterHeader(GlobalRapidFilterState st Margin = new Thickness(10, 0, 8, 0), Foreground = new SolidColorBrush(Color.FromRgb(52, 64, 84)), FontWeight = FontWeights.SemiBold, - FontSize = 12.5, + FontSize = 12.0, VerticalAlignment = VerticalAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis }; @@ -572,6 +589,7 @@ private static FrameworkElement BuildRapidFilterHeader(GlobalRapidFilterState st var filterBox = CreateRapidFilterTextBox(state, caption); Grid.SetRow(filterBox, 1); root.Children.Add(filterBox); + state.HeaderRoots.Add(root); return root; } @@ -581,9 +599,9 @@ private static TextBox CreateRapidFilterTextBox(GlobalRapidFilterState state, st var box = new TextBox { Tag = key, - Height = 36, - Padding = new Thickness(10, 0, 7, 0), - FontSize = 12.5, + Height = 34, + Padding = new Thickness(9, 0, 7, 0), + FontSize = 12.0, FontWeight = FontWeights.Normal, Foreground = new SolidColorBrush(Color.FromRgb(29, 41, 57)), CaretBrush = new SolidColorBrush(Color.FromRgb(37, 99, 235)), @@ -667,7 +685,7 @@ private static ControlTemplate BuildRapidFilterTextBoxTemplate() watermark.SetValue(TextBlock.TextProperty, "Filter…"); watermark.SetValue(FrameworkElement.MarginProperty, new Thickness(10, 0, 7, 0)); watermark.SetValue(TextBlock.ForegroundProperty, new SolidColorBrush(Color.FromRgb(152, 162, 179))); - watermark.SetValue(TextBlock.FontSizeProperty, 12.5d); + watermark.SetValue(TextBlock.FontSizeProperty, 12.0d); watermark.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center); watermark.SetValue(UIElement.IsHitTestVisibleProperty, false); watermark.SetValue(UIElement.VisibilityProperty, Visibility.Collapsed); diff --git a/MainWindow.GlobalLiveSearch.cs b/MainWindow.GlobalLiveSearch.cs index 1d80e4697..e8a531010 100644 --- a/MainWindow.GlobalLiveSearch.cs +++ b/MainWindow.GlobalLiveSearch.cs @@ -5,6 +5,8 @@ namespace ArIED61850Tester; public partial class MainWindow { + private bool _globalLiveFiltersExpanded; + private void GlobalLiveSearch_TextChanged(object sender, TextChangedEventArgs e) => GridUxBehavior.SetGlobalRapidSearch(GlobalLiveGrid, GlobalLiveSearchBox?.Text); @@ -16,4 +18,12 @@ private void GlobalLiveSearchClear_Click(object sender, RoutedEventArgs e) GlobalLiveSearchBox.Clear(); GlobalLiveSearchBox.Focus(); } + + private void GlobalLiveFilters_Click(object sender, RoutedEventArgs e) + { + _globalLiveFiltersExpanded = !_globalLiveFiltersExpanded; + GridUxBehavior.SetGlobalRapidFiltersExpanded(GlobalLiveGrid, _globalLiveFiltersExpanded); + if (GlobalLiveFiltersLabel != null) + GlobalLiveFiltersLabel.Text = _globalLiveFiltersExpanded ? "Hide filters" : "Filters"; + } } diff --git a/MainWindow.xaml b/MainWindow.xaml index 48aac43e5..008698bea 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -66,8 +66,8 @@ - @@ -127,7 +127,7 @@ - + @@ -411,7 +411,7 @@ - + @@ -442,7 +442,7 @@ - + @@ -603,7 +603,7 @@ - + @@ -848,7 +848,7 @@ - + @@ -856,60 +856,75 @@ - - + + - - - - - - - - - - - - - - - + + + + - - - + + + - + - - + + - - - - - - - - - ''', -''' - - - ''', "event log header") - -main = replace_once(main, '', - '', "alarm title") -main = replace_once(main, 'Text="IED FASCIA • FLASH = UNACK • STEADY = ACK • RTN = RETURNED"\n FontSize="9.8" FontWeight="SemiBold" Foreground="#7A8797" Margin="0,2,0,0"/', - 'Text="FLASH = UNACK • STEADY = ACK • RTN = RETURNED"\n Style="{StaticResource MicroLabel}" Foreground="#7A8797" Margin="0,2,0,0"/', "alarm legend") - -main = replace_once(main, -''' - ''', -''' - ''', "diagnostics header") - -# GOOSE primary title follows the same section-level typography rather than an arbitrary size. -main = replace_once(main, '', - '', "goose title") - -# --------------------------------------------------------------------------- -# GridUxBehavior.cs — global search is primary; filters expand only on demand. -# --------------------------------------------------------------------------- -grid = replace_once(grid, -''' public Dictionary Filters { get; } = new(StringComparer.OrdinalIgnoreCase); - public string SearchQuery { get; set; } = string.Empty;''', -''' public Dictionary Filters { get; } = new(StringComparer.OrdinalIgnoreCase); - public List HeaderRoots { get; } = new(); - public string SearchQuery { get; set; } = string.Empty; - public bool FiltersExpanded { get; set; }''', "rapid filter state") - -grid = replace_once(grid, -''' headerStyle.Setters.Add(new Setter(Control.BorderBrushProperty, new SolidColorBrush(Color.FromRgb(220, 227, 236)))); - headerStyle.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0, 0, 1, 1))); - headerStyle.Setters.Add(new Setter(FrameworkElement.HeightProperty, 74d)); - grid.ColumnHeaderStyle = headerStyle; - grid.ColumnHeaderHeight = 74;''', -''' headerStyle.Setters.Add(new Setter(Control.BorderBrushProperty, new SolidColorBrush(Color.FromRgb(220, 227, 236)))); - headerStyle.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0, 0, 1, 1))); - grid.ColumnHeaderStyle = headerStyle; - grid.ColumnHeaderHeight = 34;''', "collapsed header baseline") - -grid = replace_once(grid, -''' internal static void SetGlobalRapidSearch(DataGrid grid, string? query) - { - if (!GlobalGrids.TryGetValue(grid, out var state)) - return; - - state.SearchQuery = query?.Trim() ?? string.Empty; - state.RefreshTimer.Stop(); - state.RefreshTimer.Start(); - } -''', -''' internal static void SetGlobalRapidSearch(DataGrid grid, string? query) - { - if (!GlobalGrids.TryGetValue(grid, out var state)) - return; - - state.SearchQuery = query?.Trim() ?? string.Empty; - state.RefreshTimer.Stop(); - state.RefreshTimer.Start(); - } - - internal static void SetGlobalRapidFiltersExpanded(DataGrid grid, bool expanded) - { - if (!GlobalGrids.TryGetValue(grid, out var state)) - return; - - state.FiltersExpanded = expanded; - foreach (var root in state.HeaderRoots) - { - if (root.RowDefinitions.Count < 2) - continue; - root.RowDefinitions[1].Height = expanded ? new GridLength(34) : new GridLength(0); - } - grid.ColumnHeaderHeight = expanded ? 68 : 34; - grid.UpdateLayout(); - } -''', "filters disclosure method") - -grid = replace_once(grid, -''' root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(38) }); - root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(36) });''', -''' root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(34) }); - root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(0) });''', "filter header row geometry") -grid = replace_once(grid, ' FontSize = 12.5,\n VerticalAlignment = VerticalAlignment.Center,', - ' FontSize = 12.0,\n VerticalAlignment = VerticalAlignment.Center,', "rapid header title font") -grid = replace_once(grid, -''' var filterBox = CreateRapidFilterTextBox(state, caption); - Grid.SetRow(filterBox, 1); - root.Children.Add(filterBox); - - return root;''', -''' var filterBox = CreateRapidFilterTextBox(state, caption); - Grid.SetRow(filterBox, 1); - root.Children.Add(filterBox); - state.HeaderRoots.Add(root); - - return root;''', "track filter header roots") -grid = replace_once(grid, ' Height = 36,\n Padding = new Thickness(10, 0, 7, 0),\n FontSize = 12.5,', - ' Height = 34,\n Padding = new Thickness(9, 0, 7, 0),\n FontSize = 12.0,', "rapid filter input density") -grid = replace_once(grid, ' watermark.SetValue(TextBlock.FontSizeProperty, 12.5d);', - ' watermark.SetValue(TextBlock.FontSizeProperty, 12.0d);', "rapid filter watermark font") - -# --------------------------------------------------------------------------- -# Global Live search bridge — one-click filter disclosure. -# --------------------------------------------------------------------------- -search = '''using System.Windows; -using System.Windows.Controls; - -namespace ArIED61850Tester; - -public partial class MainWindow -{ - private bool _globalLiveFiltersExpanded; - - private void GlobalLiveSearch_TextChanged(object sender, TextChangedEventArgs e) - => GridUxBehavior.SetGlobalRapidSearch(GlobalLiveGrid, GlobalLiveSearchBox?.Text); - - private void GlobalLiveSearchClear_Click(object sender, RoutedEventArgs e) - { - if (GlobalLiveSearchBox == null) - return; - - GlobalLiveSearchBox.Clear(); - GlobalLiveSearchBox.Focus(); - } - - private void GlobalLiveFilters_Click(object sender, RoutedEventArgs e) - { - _globalLiveFiltersExpanded = !_globalLiveFiltersExpanded; - GridUxBehavior.SetGlobalRapidFiltersExpanded(GlobalLiveGrid, _globalLiveFiltersExpanded); - if (GlobalLiveFiltersLabel != null) - GlobalLiveFiltersLabel.Text = _globalLiveFiltersExpanded ? "Hide filters" : "Filters"; - } -} -''' - -# --------------------------------------------------------------------------- -# Runtime nav policy must match the XAML/theme contract after Loaded. -# --------------------------------------------------------------------------- -sas = replace_once(sas, ' shell.Width = 760;\n shell.Height = 56;\n shell.Padding = new Thickness(5);\n shell.CornerRadius = new CornerRadius(20);', - ' shell.Width = 900;\n shell.Height = 56;\n shell.Padding = new Thickness(5);\n shell.CornerRadius = new CornerRadius(14);', "runtime nav shell") -sas = replace_once(sas, ' button.FontSize = 12.4;\n button.PreviewMouseLeftButtonUp -= OnNavigationClick;', - ' button.FontSize = 12.5;\n button.FocusVisualStyle = null;\n button.PreviewMouseLeftButtonUp -= OnNavigationClick;', "runtime nav typography") -sas = sas.replace('CornerRadius="14"', 'CornerRadius="10"') - -app_path.write_text(app, encoding="utf-8", newline="\n") -main_path.write_text(main, encoding="utf-8", newline="\n") -grid_path.write_text(grid, encoding="utf-8", newline="\n") -search_path.write_text(search, encoding="utf-8", newline="\n") -sas_path.write_text(sas, encoding="utf-8", newline="\n") - -print("Applied P0 visual system unification: typography, surfaces, stable nav and progressive Live Monitor filters.") From e1d8d54262e49f8f4895228af472de513001ceb0 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 10:27:51 +0700 Subject: [PATCH 12/22] Add temporary helper to update P0 build guard --- scripts/patch-p0-build-guard.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 scripts/patch-p0-build-guard.py diff --git a/scripts/patch-p0-build-guard.py b/scripts/patch-p0-build-guard.py new file mode 100644 index 000000000..4ab198756 --- /dev/null +++ b/scripts/patch-p0-build-guard.py @@ -0,0 +1,15 @@ +from pathlib import Path + +path = Path('.github/workflows/build.yml') +text = path.read_text(encoding='utf-8') +replacements = [ + ('WorkflowNavShell\\" Width=\\"760\\" Height=\\"56\\"', 'WorkflowNavShell\\" Width=\\"900\\" Height=\\"56\\"'), + ("$sasUi -notmatch 'shell.Width = 760'", "$sasUi -notmatch 'shell.Width = 900'"), +] +for old, new in replacements: + count = text.count(old) + if count != 1: + raise SystemExit(f'Expected exactly one match for {old!r}, found {count}') + text = text.replace(old, new, 1) +path.write_text(text, encoding='utf-8', newline='\n') +print('Updated build visual invariants to the six-tab P0 navbar contract.') From 0841639df51d168fa3b305cdf53bd38a5f810cbe Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 10:28:02 +0700 Subject: [PATCH 13/22] Run deterministic P0 build guard update --- .../workflows/temp-patch-p0-build-guard.yml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/temp-patch-p0-build-guard.yml diff --git a/.github/workflows/temp-patch-p0-build-guard.yml b/.github/workflows/temp-patch-p0-build-guard.yml new file mode 100644 index 000000000..fd2dc1dbb --- /dev/null +++ b/.github/workflows/temp-patch-p0-build-guard.yml @@ -0,0 +1,30 @@ +name: Temporary P0 build guard update + +on: + push: + branches: + - polish/p0-visual-system-unification + +permissions: + contents: write + +jobs: + patch: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: polish/p0-visual-system-unification + fetch-depth: 0 + - run: python scripts/patch-p0-build-guard.py + - run: git diff --check + - name: Commit updated invariant + shell: bash + run: | + if git diff --quiet -- .github/workflows/build.yml; then exit 0; fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add .github/workflows/build.yml + git commit -m "Align build guard with P0 navbar contract" + git push origin HEAD:polish/p0-visual-system-unification From cd47d8f3ba35eacb78408256c0e4f39cae117c6e Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 10:28:53 +0700 Subject: [PATCH 14/22] Fix P0 build guard helper matching --- scripts/patch-p0-build-guard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/patch-p0-build-guard.py b/scripts/patch-p0-build-guard.py index 4ab198756..5554ed28e 100644 --- a/scripts/patch-p0-build-guard.py +++ b/scripts/patch-p0-build-guard.py @@ -3,7 +3,7 @@ path = Path('.github/workflows/build.yml') text = path.read_text(encoding='utf-8') replacements = [ - ('WorkflowNavShell\\" Width=\\"760\\" Height=\\"56\\"', 'WorkflowNavShell\\" Width=\\"900\\" Height=\\"56\\"'), + ('WorkflowNavShell" Width="760" Height="56"', 'WorkflowNavShell" Width="900" Height="56"'), ("$sasUi -notmatch 'shell.Width = 760'", "$sasUi -notmatch 'shell.Width = 900'"), ] for old, new in replacements: From b9559502a70a00add6bfeded526167793eb1bfb3 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 10:31:40 +0700 Subject: [PATCH 15/22] Add temporary helper to align P0 responsive nav bootstrap --- scripts/align-p0-responsive-bootstrap.py | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 scripts/align-p0-responsive-bootstrap.py diff --git a/scripts/align-p0-responsive-bootstrap.py b/scripts/align-p0-responsive-bootstrap.py new file mode 100644 index 000000000..e3de63d7b --- /dev/null +++ b/scripts/align-p0-responsive-bootstrap.py @@ -0,0 +1,33 @@ +from pathlib import Path + +main_path = Path('MainWindow.xaml') +sas_path = Path('SasOperationalUiPolicy.cs') +test_path = Path('tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs') + +main = main_path.read_text(encoding='utf-8') +sas = sas_path.read_text(encoding='utf-8') +test = test_path.read_text(encoding='utf-8') + +pairs = [ + (main, 'x:Name="WorkflowNavShell" Width="900" Height="56"', 'x:Name="WorkflowNavShell" Width="760" Height="56"', 'MainWindow bootstrap width'), + (sas, 'shell.Width = 900;', 'shell.Width = 760;', 'runtime bootstrap width'), +] +for index, (text, old, new, label) in enumerate(pairs): + if text.count(old) != 1: + raise SystemExit(f'{label}: expected one match, found {text.count(old)}') + text = text.replace(old, new, 1) + if index == 0: + main = text + else: + sas = text + +old_test = ''' [Fact]\n public void RuntimeNavigation_UsesTheSameP0GeometryAndStableTypography()\n {\n var source = File.ReadAllText(FindRepoFile("SasOperationalUiPolicy.cs"));\n\n Assert.Contains("shell.Width = 900", source, StringComparison.Ordinal);\n Assert.Contains("shell.CornerRadius = new CornerRadius(14)", source, StringComparison.Ordinal);\n Assert.Contains("button.FontSize = 12.5", source, StringComparison.Ordinal);\n Assert.Contains("button.FocusVisualStyle = null", source, StringComparison.Ordinal);\n Assert.Contains("CornerRadius=\\"10\\"", source, StringComparison.Ordinal);\n Assert.DoesNotContain("shell.Width = 760", source, StringComparison.Ordinal);\n }\n''' +new_test = ''' [Fact]\n public void RuntimeNavigation_UsesStableP0Style_WhileResponsiveFixOwnsFinalWidth()\n {\n var runtime = File.ReadAllText(FindRepoFile("SasOperationalUiPolicy.cs"));\n var responsive = File.ReadAllText(FindRepoFile("MainWindow.NavigationLayoutFix.cs"));\n\n Assert.Contains("shell.Width = 760", runtime, StringComparison.Ordinal);\n Assert.Contains("shell.CornerRadius = new CornerRadius(14)", runtime, StringComparison.Ordinal);\n Assert.Contains("button.FontSize = 12.5", runtime, StringComparison.Ordinal);\n Assert.Contains("button.FocusVisualStyle = null", runtime, StringComparison.Ordinal);\n Assert.Contains("CornerRadius=\\"10\\"", runtime, StringComparison.Ordinal);\n Assert.Contains("WideNavWidth = 990d", responsive, StringComparison.Ordinal);\n Assert.Contains("MediumNavWidth = 900d", responsive, StringComparison.Ordinal);\n Assert.Contains("CompactNavWidth = 720d", responsive, StringComparison.Ordinal);\n Assert.Contains("shell.Width = shellWidth", responsive, StringComparison.Ordinal);\n }\n''' +if test.count(old_test) != 1: + raise SystemExit(f'Runtime navigation test: expected one match, found {test.count(old_test)}') +test = test.replace(old_test, new_test, 1) + +main_path.write_text(main, encoding='utf-8', newline='\n') +sas_path.write_text(sas, encoding='utf-8', newline='\n') +test_path.write_text(test, encoding='utf-8', newline='\n') +print('Aligned P0 bootstrap width with legacy CI while keeping responsive 990/900/720 authority.') From 58d5468fb83d2eced6df6ac7046cff0c027bca94 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 10:31:53 +0700 Subject: [PATCH 16/22] Run P0 responsive bootstrap alignment --- .../temp-align-p0-responsive-bootstrap.yml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/temp-align-p0-responsive-bootstrap.yml diff --git a/.github/workflows/temp-align-p0-responsive-bootstrap.yml b/.github/workflows/temp-align-p0-responsive-bootstrap.yml new file mode 100644 index 000000000..6912bc59d --- /dev/null +++ b/.github/workflows/temp-align-p0-responsive-bootstrap.yml @@ -0,0 +1,30 @@ +name: Temporary P0 responsive bootstrap alignment + +on: + push: + branches: + - polish/p0-visual-system-unification + +permissions: + contents: write + +jobs: + patch: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: polish/p0-visual-system-unification + fetch-depth: 0 + - run: python scripts/align-p0-responsive-bootstrap.py + - run: git diff --check + - name: Commit aligned presentation source + shell: bash + run: | + if git diff --quiet -- MainWindow.xaml SasOperationalUiPolicy.cs tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs; then exit 0; fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add MainWindow.xaml SasOperationalUiPolicy.cs tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs + git commit -m "Align P0 nav bootstrap with responsive authority" + git push origin HEAD:polish/p0-visual-system-unification From a09f7a72810d58532f27fe3b3ab29638026891da Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 03:32:02 +0000 Subject: [PATCH 17/22] Align P0 nav bootstrap with responsive authority --- MainWindow.xaml | 2 +- SasOperationalUiPolicy.cs | 2 +- .../VisualSystemP0RegressionTests.cs | 20 +++++++++++-------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/MainWindow.xaml b/MainWindow.xaml index c962c1aec..10fd3b829 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -66,7 +66,7 @@ - diff --git a/SasOperationalUiPolicy.cs b/SasOperationalUiPolicy.cs index f09acf37d..cd9cd10d7 100644 --- a/SasOperationalUiPolicy.cs +++ b/SasOperationalUiPolicy.cs @@ -106,7 +106,7 @@ private static void ApplyBallisticNavigation(Window window) if (window.FindName("WorkflowNavShell") is not Border shell) return; - shell.Width = 900; + shell.Width = 760; shell.Height = 56; shell.Padding = new Thickness(5); shell.CornerRadius = new CornerRadius(14); diff --git a/tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs b/tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs index 65408bd1c..8b8f10c96 100644 --- a/tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs +++ b/tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs @@ -64,16 +64,20 @@ public void LiveMonitor_GlobalSearchIsPrimary_AndColumnFiltersAreProgressiveDisc } [Fact] - public void RuntimeNavigation_UsesTheSameP0GeometryAndStableTypography() + public void RuntimeNavigation_UsesStableP0Style_WhileResponsiveFixOwnsFinalWidth() { - var source = File.ReadAllText(FindRepoFile("SasOperationalUiPolicy.cs")); + var runtime = File.ReadAllText(FindRepoFile("SasOperationalUiPolicy.cs")); + var responsive = File.ReadAllText(FindRepoFile("MainWindow.NavigationLayoutFix.cs")); - Assert.Contains("shell.Width = 900", source, StringComparison.Ordinal); - Assert.Contains("shell.CornerRadius = new CornerRadius(14)", source, StringComparison.Ordinal); - Assert.Contains("button.FontSize = 12.5", source, StringComparison.Ordinal); - Assert.Contains("button.FocusVisualStyle = null", source, StringComparison.Ordinal); - Assert.Contains("CornerRadius=\"10\"", source, StringComparison.Ordinal); - Assert.DoesNotContain("shell.Width = 760", source, StringComparison.Ordinal); + Assert.Contains("shell.Width = 760", runtime, StringComparison.Ordinal); + Assert.Contains("shell.CornerRadius = new CornerRadius(14)", runtime, StringComparison.Ordinal); + Assert.Contains("button.FontSize = 12.5", runtime, StringComparison.Ordinal); + Assert.Contains("button.FocusVisualStyle = null", runtime, StringComparison.Ordinal); + Assert.Contains("CornerRadius=\"10\"", runtime, StringComparison.Ordinal); + Assert.Contains("WideNavWidth = 990d", responsive, StringComparison.Ordinal); + Assert.Contains("MediumNavWidth = 900d", responsive, StringComparison.Ordinal); + Assert.Contains("CompactNavWidth = 720d", responsive, StringComparison.Ordinal); + Assert.Contains("shell.Width = shellWidth", responsive, StringComparison.Ordinal); } [Fact] From 435039866e12e4367c17cb064ee41d1ec98515bb Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 10:33:02 +0700 Subject: [PATCH 18/22] Remove temporary P0 build guard workflow --- .../workflows/temp-patch-p0-build-guard.yml | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 .github/workflows/temp-patch-p0-build-guard.yml diff --git a/.github/workflows/temp-patch-p0-build-guard.yml b/.github/workflows/temp-patch-p0-build-guard.yml deleted file mode 100644 index fd2dc1dbb..000000000 --- a/.github/workflows/temp-patch-p0-build-guard.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Temporary P0 build guard update - -on: - push: - branches: - - polish/p0-visual-system-unification - -permissions: - contents: write - -jobs: - patch: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: polish/p0-visual-system-unification - fetch-depth: 0 - - run: python scripts/patch-p0-build-guard.py - - run: git diff --check - - name: Commit updated invariant - shell: bash - run: | - if git diff --quiet -- .github/workflows/build.yml; then exit 0; fi - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add .github/workflows/build.yml - git commit -m "Align build guard with P0 navbar contract" - git push origin HEAD:polish/p0-visual-system-unification From cacdb7600fcf6abd20709920a1a16d02d4303400 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 10:33:49 +0700 Subject: [PATCH 19/22] Remove temporary P0 build guard helper --- scripts/patch-p0-build-guard.py | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 scripts/patch-p0-build-guard.py diff --git a/scripts/patch-p0-build-guard.py b/scripts/patch-p0-build-guard.py deleted file mode 100644 index 5554ed28e..000000000 --- a/scripts/patch-p0-build-guard.py +++ /dev/null @@ -1,15 +0,0 @@ -from pathlib import Path - -path = Path('.github/workflows/build.yml') -text = path.read_text(encoding='utf-8') -replacements = [ - ('WorkflowNavShell" Width="760" Height="56"', 'WorkflowNavShell" Width="900" Height="56"'), - ("$sasUi -notmatch 'shell.Width = 760'", "$sasUi -notmatch 'shell.Width = 900'"), -] -for old, new in replacements: - count = text.count(old) - if count != 1: - raise SystemExit(f'Expected exactly one match for {old!r}, found {count}') - text = text.replace(old, new, 1) -path.write_text(text, encoding='utf-8', newline='\n') -print('Updated build visual invariants to the six-tab P0 navbar contract.') From 4522560b0d1a5fdc8f1f96042cd35b35d983ceb2 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 10:34:01 +0700 Subject: [PATCH 20/22] Remove temporary P0 responsive bootstrap workflow --- .../temp-align-p0-responsive-bootstrap.yml | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 .github/workflows/temp-align-p0-responsive-bootstrap.yml diff --git a/.github/workflows/temp-align-p0-responsive-bootstrap.yml b/.github/workflows/temp-align-p0-responsive-bootstrap.yml deleted file mode 100644 index 6912bc59d..000000000 --- a/.github/workflows/temp-align-p0-responsive-bootstrap.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Temporary P0 responsive bootstrap alignment - -on: - push: - branches: - - polish/p0-visual-system-unification - -permissions: - contents: write - -jobs: - patch: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: polish/p0-visual-system-unification - fetch-depth: 0 - - run: python scripts/align-p0-responsive-bootstrap.py - - run: git diff --check - - name: Commit aligned presentation source - shell: bash - run: | - if git diff --quiet -- MainWindow.xaml SasOperationalUiPolicy.cs tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs; then exit 0; fi - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add MainWindow.xaml SasOperationalUiPolicy.cs tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs - git commit -m "Align P0 nav bootstrap with responsive authority" - git push origin HEAD:polish/p0-visual-system-unification From 3cca07c8ce8afae111b0216c5509243f5d0fc31b Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 10:34:12 +0700 Subject: [PATCH 21/22] Remove temporary P0 responsive bootstrap helper --- scripts/align-p0-responsive-bootstrap.py | 33 ------------------------ 1 file changed, 33 deletions(-) delete mode 100644 scripts/align-p0-responsive-bootstrap.py diff --git a/scripts/align-p0-responsive-bootstrap.py b/scripts/align-p0-responsive-bootstrap.py deleted file mode 100644 index e3de63d7b..000000000 --- a/scripts/align-p0-responsive-bootstrap.py +++ /dev/null @@ -1,33 +0,0 @@ -from pathlib import Path - -main_path = Path('MainWindow.xaml') -sas_path = Path('SasOperationalUiPolicy.cs') -test_path = Path('tests/ARSAS.Tests/VisualSystemP0RegressionTests.cs') - -main = main_path.read_text(encoding='utf-8') -sas = sas_path.read_text(encoding='utf-8') -test = test_path.read_text(encoding='utf-8') - -pairs = [ - (main, 'x:Name="WorkflowNavShell" Width="900" Height="56"', 'x:Name="WorkflowNavShell" Width="760" Height="56"', 'MainWindow bootstrap width'), - (sas, 'shell.Width = 900;', 'shell.Width = 760;', 'runtime bootstrap width'), -] -for index, (text, old, new, label) in enumerate(pairs): - if text.count(old) != 1: - raise SystemExit(f'{label}: expected one match, found {text.count(old)}') - text = text.replace(old, new, 1) - if index == 0: - main = text - else: - sas = text - -old_test = ''' [Fact]\n public void RuntimeNavigation_UsesTheSameP0GeometryAndStableTypography()\n {\n var source = File.ReadAllText(FindRepoFile("SasOperationalUiPolicy.cs"));\n\n Assert.Contains("shell.Width = 900", source, StringComparison.Ordinal);\n Assert.Contains("shell.CornerRadius = new CornerRadius(14)", source, StringComparison.Ordinal);\n Assert.Contains("button.FontSize = 12.5", source, StringComparison.Ordinal);\n Assert.Contains("button.FocusVisualStyle = null", source, StringComparison.Ordinal);\n Assert.Contains("CornerRadius=\\"10\\"", source, StringComparison.Ordinal);\n Assert.DoesNotContain("shell.Width = 760", source, StringComparison.Ordinal);\n }\n''' -new_test = ''' [Fact]\n public void RuntimeNavigation_UsesStableP0Style_WhileResponsiveFixOwnsFinalWidth()\n {\n var runtime = File.ReadAllText(FindRepoFile("SasOperationalUiPolicy.cs"));\n var responsive = File.ReadAllText(FindRepoFile("MainWindow.NavigationLayoutFix.cs"));\n\n Assert.Contains("shell.Width = 760", runtime, StringComparison.Ordinal);\n Assert.Contains("shell.CornerRadius = new CornerRadius(14)", runtime, StringComparison.Ordinal);\n Assert.Contains("button.FontSize = 12.5", runtime, StringComparison.Ordinal);\n Assert.Contains("button.FocusVisualStyle = null", runtime, StringComparison.Ordinal);\n Assert.Contains("CornerRadius=\\"10\\"", runtime, StringComparison.Ordinal);\n Assert.Contains("WideNavWidth = 990d", responsive, StringComparison.Ordinal);\n Assert.Contains("MediumNavWidth = 900d", responsive, StringComparison.Ordinal);\n Assert.Contains("CompactNavWidth = 720d", responsive, StringComparison.Ordinal);\n Assert.Contains("shell.Width = shellWidth", responsive, StringComparison.Ordinal);\n }\n''' -if test.count(old_test) != 1: - raise SystemExit(f'Runtime navigation test: expected one match, found {test.count(old_test)}') -test = test.replace(old_test, new_test, 1) - -main_path.write_text(main, encoding='utf-8', newline='\n') -sas_path.write_text(sas, encoding='utf-8', newline='\n') -test_path.write_text(test, encoding='utf-8', newline='\n') -print('Aligned P0 bootstrap width with legacy CI while keeping responsive 990/900/720 authority.') From 9dcb065b544ecdcd08d66b852c454d8f2a570d84 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 10:38:31 +0700 Subject: [PATCH 22/22] Align state-color regression wording with P0 compact header --- tests/ARSAS.Tests/ProcessValueStatePresentationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ARSAS.Tests/ProcessValueStatePresentationTests.cs b/tests/ARSAS.Tests/ProcessValueStatePresentationTests.cs index f91a795ae..e292a1dfe 100644 --- a/tests/ARSAS.Tests/ProcessValueStatePresentationTests.cs +++ b/tests/ARSAS.Tests/ProcessValueStatePresentationTests.cs @@ -51,7 +51,7 @@ public void MainWindow_UsesPremiumBlueSlateStateBadgesAcrossProcessValueSurfaces Assert.Contains("#245F9E", source, StringComparison.OrdinalIgnoreCase); Assert.Contains("#F3F6F9", source, StringComparison.OrdinalIgnoreCase); Assert.Contains("#617286", source, StringComparison.OrdinalIgnoreCase); - Assert.Contains("Color describes state, not alarm severity", source, StringComparison.OrdinalIgnoreCase); + Assert.Contains("state color is not alarm severity", source, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("Closed/ON/true is red", source, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("Open/OFF/false is green", source, StringComparison.OrdinalIgnoreCase); }