From 4c2314cc46fc35d456c8459ed7d325784a58c21d Mon Sep 17 00:00:00 2001 From: KeyffMS <124252104+KeyffMS@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:16:26 +0200 Subject: [PATCH 1/7] Add temporary UI source snapshot workflow --- .github/workflows/source-snapshot.yml | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/source-snapshot.yml diff --git a/.github/workflows/source-snapshot.yml b/.github/workflows/source-snapshot.yml new file mode 100644 index 00000000..0ca2784f --- /dev/null +++ b/.github/workflows/source-snapshot.yml @@ -0,0 +1,28 @@ +name: Temporary source snapshot + +on: + push: + branches: [agent/final-ui-cleanup] + +permissions: + contents: read + +jobs: + snapshot: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Upload UI source files + uses: actions/upload-artifact@v4 + with: + name: SightAdapt-ui-source-snapshot + path: | + src/SightAdapt/ConfigurationForm.cs + src/SightAdapt/ModernTheme.cs + src/SightAdapt/ModernSelectorComboBoxColumn.cs + src/SightAdapt/SightAdapt.csproj + tests/SightAdapt.Tests/ArchitectureComplianceTests.cs + if-no-files-found: error + retention-days: 1 From 341fcd0ce929c237361ae6095927378128cf340e Mon Sep 17 00:00:00 2001 From: KeyffMS <124252104+KeyffMS@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:17:48 +0200 Subject: [PATCH 2/7] Run temporary source snapshot for pull request --- .github/workflows/source-snapshot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/source-snapshot.yml b/.github/workflows/source-snapshot.yml index 0ca2784f..d89f961f 100644 --- a/.github/workflows/source-snapshot.yml +++ b/.github/workflows/source-snapshot.yml @@ -1,8 +1,8 @@ name: Temporary source snapshot on: - push: - branches: [agent/final-ui-cleanup] + pull_request: + branches: [main] permissions: contents: read From 502d7c2e3b0d864c7d4bd5903ad1ea0cb6915d13 Mon Sep 17 00:00:00 2001 From: KeyffMS <124252104+KeyffMS@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:24:36 +0200 Subject: [PATCH 3/7] Apply final UI cleanup through guarded workflow --- .github/workflows/source-snapshot.yml | 367 ++++++++++++++++++++++++-- 1 file changed, 351 insertions(+), 16 deletions(-) diff --git a/.github/workflows/source-snapshot.yml b/.github/workflows/source-snapshot.yml index d89f961f..858562a4 100644 --- a/.github/workflows/source-snapshot.yml +++ b/.github/workflows/source-snapshot.yml @@ -1,28 +1,363 @@ -name: Temporary source snapshot +name: Temporary final UI patch on: pull_request: branches: [main] permissions: - contents: read + contents: write + pull-requests: read jobs: - snapshot: + patch: + if: >- + github.event.pull_request.head.repo.full_name == github.repository && + github.head_ref == 'agent/final-ui-cleanup' runs-on: ubuntu-latest + steps: - - name: Check out repository + - name: Check out pull-request branch uses: actions/checkout@v4 - - - name: Upload UI source files - uses: actions/upload-artifact@v4 with: - name: SightAdapt-ui-source-snapshot - path: | - src/SightAdapt/ConfigurationForm.cs - src/SightAdapt/ModernTheme.cs - src/SightAdapt/ModernSelectorComboBoxColumn.cs - src/SightAdapt/SightAdapt.csproj - tests/SightAdapt.Tests/ArchitectureComplianceTests.cs - if-no-files-found: error - retention-days: 1 + ref: ${{ github.head_ref }} + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Apply final UI cleanup + shell: bash + run: | + python - <<'PY' + from pathlib import Path + + def replace_required(text: str, old: str, new: str, label: str) -> str: + if old not in text: + if new in text: + return text + raise RuntimeError(f"Required source block not found: {label}") + return text.replace(old, new, 1) + + path = Path('src/SightAdapt/ConfigurationForm.cs') + source = path.read_text(encoding='utf-8') + source = replace_required( + source, + ''' private readonly Func _getCurrentApplication;\n private readonly ToggleSwitch _automaticModeSwitch;''', + ''' private readonly Func _getCurrentApplication;\n private readonly Action\n _showVisualProfileManager;\n private readonly ToggleSwitch _automaticModeSwitch;''', + 'configuration manager field') + source = replace_required( + source, + ''' public ConfigurationForm(\n SettingsCoordinator settingsCoordinator,\n Func getCurrentApplication)\n {\n _settingsCoordinator = settingsCoordinator ??\n throw new ArgumentNullException(nameof(settingsCoordinator));\n _getCurrentApplication = getCurrentApplication ??\n throw new ArgumentNullException(nameof(getCurrentApplication));''', + ''' public ConfigurationForm(\n SettingsCoordinator settingsCoordinator,\n Func getCurrentApplication,\n Action?\n showVisualProfileManager = null)\n {\n _settingsCoordinator = settingsCoordinator ??\n throw new ArgumentNullException(nameof(settingsCoordinator));\n _getCurrentApplication = getCurrentApplication ??\n throw new ArgumentNullException(nameof(getCurrentApplication));\n _showVisualProfileManager = showVisualProfileManager ??\n VisualProfileManagerForm.ShowManager;''', + 'configuration constructor') + source = replace_required( + source, + ''' private SightAdaptSettings Settings => _settingsCoordinator.Current;\n\n public void RefreshProfiles()''', + ''' private SightAdaptSettings Settings => _settingsCoordinator.Current;\n\n internal int RefreshGeneration { get; private set; }\n\n public void RefreshProfiles()''', + 'configuration refresh counter') + source = replace_required( + source, + ''' finally\n {\n _refreshing = false;\n }\n }\n\n private Control CreateRootLayout()''', + ''' finally\n {\n _refreshing = false;\n }\n\n RefreshGeneration++;\n }\n\n private Control CreateRootLayout()''', + 'configuration refresh generation') + source = replace_required( + source, + ''' private void ManageVisualProfiles()\n {\n VisualProfileManagerForm.ShowManager(this, _settingsCoordinator);\n RefreshProfiles();\n }''', + ''' internal void ManageVisualProfiles()\n {\n _showVisualProfileManager(this, _settingsCoordinator);\n }''', + 'configuration manager refresh') + path.write_text(source, encoding='utf-8', newline='\n') + + path = Path('src/SightAdapt/ModernTheme.cs') + source = path.read_text(encoding='utf-8') + source = replace_required( + source, + ''' public static readonly Color Surface = Color.FromArgb(29, 34, 45);\n public static readonly Color SurfaceRaised = Color.FromArgb(36, 42, 55);''', + ''' public static readonly Color Surface = Color.FromArgb(29, 34, 45);\n public static readonly Color SurfaceAlternate = Color.FromArgb(32, 38, 50);\n public static readonly Color SurfaceRaised = Color.FromArgb(36, 42, 55);''', + 'alternate surface token') + source = replace_required( + source, + ''' public static readonly Color Danger = Color.FromArgb(255, 111, 128);\n public static readonly Color DangerSoft = Color.FromArgb(84, 43, 54);\n public static readonly Color Selection = Color.FromArgb(52, 67, 105);''', + ''' public static readonly Color Danger = Color.FromArgb(255, 111, 128);\n public static readonly Color DangerSoft = Color.FromArgb(84, 43, 54);\n public static readonly Color DangerHover = Color.FromArgb(96, 47, 59);\n public static readonly Color DangerPressed = Color.FromArgb(105, 47, 60);\n public static readonly Color DangerBorder = Color.FromArgb(120, 58, 70);\n public static readonly Color Selection = Color.FromArgb(52, 67, 105);''', + 'danger theme tokens') + source = source.replace( + ' BackColor = Color.FromArgb(32, 38, 50),', + ' BackColor = SurfaceAlternate,') + source = replace_required( + source, + ''' private (Color Background, Color Border, Color Foreground) ResolveColors()\n {\n if (!Enabled)\n {\n return (AppTheme.Surface, AppTheme.Border, AppTheme.TextMuted);\n }\n\n return VisualStyle switch\n {\n ModernButtonStyle.Primary => (\n _pressed ? AppTheme.AccentPressed : _hovered ? AppTheme.AccentHover : AppTheme.Accent,\n _pressed ? AppTheme.AccentPressed : AppTheme.AccentHover,\n Color.White),\n ModernButtonStyle.Danger => (\n _pressed ? Color.FromArgb(105, 47, 60) : _hovered ? Color.FromArgb(96, 47, 59) : AppTheme.DangerSoft,\n _hovered ? AppTheme.Danger : Color.FromArgb(120, 58, 70),\n _hovered ? Color.White : AppTheme.Danger),\n ModernButtonStyle.Ghost => (\n _pressed ? AppTheme.SurfaceRaised : _hovered ? AppTheme.SurfaceHover : AppTheme.WindowBackground,\n _hovered ? AppTheme.Border : AppTheme.WindowBackground,\n AppTheme.TextSecondary),\n _ => (\n _pressed ? AppTheme.SurfaceHover : _hovered ? AppTheme.SurfaceHover : AppTheme.SurfaceRaised,\n _hovered ? AppTheme.Accent : AppTheme.Border,\n AppTheme.TextPrimary),\n };\n }''', + ''' private (Color Background, Color Border, Color Foreground) ResolveColors()\n {\n return ResolveColors(\n VisualStyle,\n Enabled,\n _hovered,\n _pressed);\n }\n\n internal static (\n Color Background,\n Color Border,\n Color Foreground) ResolveColors(\n ModernButtonStyle visualStyle,\n bool enabled,\n bool hovered,\n bool pressed)\n {\n if (!enabled)\n {\n return (\n AppTheme.Surface,\n AppTheme.Border,\n AppTheme.TextMuted);\n }\n\n return visualStyle switch\n {\n ModernButtonStyle.Primary => (\n pressed\n ? AppTheme.AccentPressed\n : hovered\n ? AppTheme.AccentHover\n : AppTheme.Accent,\n pressed\n ? AppTheme.AccentPressed\n : AppTheme.AccentHover,\n Color.White),\n ModernButtonStyle.Danger => (\n pressed\n ? AppTheme.DangerPressed\n : hovered\n ? AppTheme.DangerHover\n : AppTheme.DangerSoft,\n hovered\n ? AppTheme.Danger\n : AppTheme.DangerBorder,\n hovered\n ? Color.White\n : AppTheme.Danger),\n ModernButtonStyle.Ghost => (\n pressed\n ? AppTheme.SurfaceRaised\n : hovered\n ? AppTheme.SurfaceHover\n : AppTheme.WindowBackground,\n hovered\n ? AppTheme.Border\n : AppTheme.WindowBackground,\n AppTheme.TextSecondary),\n _ => (\n pressed || hovered\n ? AppTheme.SurfaceHover\n : AppTheme.SurfaceRaised,\n hovered\n ? AppTheme.Accent\n : AppTheme.Border,\n AppTheme.TextPrimary),\n };\n }''', + 'button color resolution') + path.write_text(source, encoding='utf-8', newline='\n') + + path = Path('src/SightAdapt/ModernSelectorComboBoxColumn.cs') + source = path.read_text(encoding='utf-8') + source = replace_required( + source, + ' cellStyle.Font ?? AppTheme.CreateUiFont(9.5f),', + ''' ResolvePaintFont(\n cellStyle.Font,\n DataGridView?.Font),''', + 'selector paint font') + source = replace_required( + source, + ''' }\n}\n\ninternal sealed class ModernSelectorEditingControl :''', + ''' }\n\n internal static Font ResolvePaintFont(\n Font? cellStyleFont,\n Font? gridFont)\n {\n return cellStyleFont ??\n gridFont ??\n Control.DefaultFont;\n }\n}\n\ninternal sealed class ModernSelectorEditingControl :''', + 'selector font helper') + source = replace_required( + source, + ''' private readonly ListBox _list;\n private readonly ToolStripDropDown _dropDown;''', + ''' private readonly Font _defaultFont;\n private readonly ListBox _list;\n private readonly ToolStripDropDown _dropDown;''', + 'selector owned font field') + source = replace_required( + source, + ''' public ModernSelectorEditingControl()\n {\n AccessibleRole = AccessibleRole.ComboBox;''', + ''' public ModernSelectorEditingControl()\n {\n _defaultFont = AppTheme.CreateUiFont(9.5f);\n\n AccessibleRole = AccessibleRole.ComboBox;''', + 'selector owned font initialization') + source = source.replace( + ' Font = AppTheme.CreateUiFont(9.5f);', + ' Font = _defaultFont;', + 1) + source = source.replace( + ' Font = AppTheme.CreateUiFont(9.5f),', + ' Font = _defaultFont,', + 1) + source = source.replace( + ' Font = style.Font ?? AppTheme.CreateUiFont(9.5f);', + ' Font = style.Font ?? _defaultFont;') + source = source.replace( + ' Font = dataGridViewCellStyle.Font ?? AppTheme.CreateUiFont(9.5f);', + ' Font = dataGridViewCellStyle.Font ?? _defaultFont;') + source = replace_required( + source, + ''' if (disposing)\n {\n _dropDown.Dispose();\n _list.Dispose();\n }''', + ''' if (disposing)\n {\n _dropDown.Dispose();\n _list.Dispose();\n _defaultFont.Dispose();\n }''', + 'selector owned font disposal') + path.write_text(source, encoding='utf-8', newline='\n') + + project = Path('src/SightAdapt/SightAdapt.csproj') + source = project.read_text(encoding='utf-8') + source = source.replace('0.5.0.33', '0.5.0.34') + project.write_text(source, encoding='utf-8', newline='\n') + + Path('tests/SightAdapt.Tests/ConfigurationProfileManagerRefreshTests.cs').write_text(r'''using System.Windows.Forms; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + namespace SightAdapt.Tests; + + [TestClass] + public sealed class ConfigurationProfileManagerRefreshTests + { + [TestMethod] + public void ClosingManagerWithoutChangesDoesNotRefreshConfiguration() + { + RunOnSta(() => + { + using var temporaryDirectory = new TemporaryDirectory(); + var coordinator = CreateCoordinator(temporaryDirectory.Path); + var managerCalls = 0; + using var form = new ConfigurationForm( + coordinator, + () => null, + (_, receivedCoordinator) => + { + Assert.AreSame(coordinator, receivedCoordinator); + managerCalls++; + }); + var generation = form.RefreshGeneration; + + form.ManageVisualProfiles(); + + Assert.AreEqual(1, managerCalls); + Assert.AreEqual(generation, form.RefreshGeneration); + }); + } + + [TestMethod] + public void ManagerMutationRefreshesConfigurationExactlyOnce() + { + RunOnSta(() => + { + using var temporaryDirectory = new TemporaryDirectory(); + var coordinator = CreateCoordinator(temporaryDirectory.Path); + using var form = new ConfigurationForm( + coordinator, + () => null, + (_, receivedCoordinator) => + { + var result = receivedCoordinator.Commit(settings => + VisualProfileManagementService.Create( + settings, + "Reader").Id); + Assert.IsTrue(result.Succeeded); + }); + var generation = form.RefreshGeneration; + + form.ManageVisualProfiles(); + + Assert.AreEqual(generation + 1, form.RefreshGeneration); + Assert.IsTrue(coordinator.Current.VisualProfiles.Any( + profile => profile.Name == "Reader")); + }); + } + + private static SettingsCoordinator CreateCoordinator(string directory) + { + return new SettingsCoordinator( + new SettingsStore(Path.Combine(directory, "settings.json"))); + } + + private static void RunOnSta(Action action) + { + Exception? failure = null; + var thread = new Thread(() => + { + try + { + action(); + } + catch (Exception exception) + { + failure = exception; + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + + Assert.IsTrue( + thread.Join(TimeSpan.FromSeconds(10)), + "The configuration refresh test did not finish in time."); + if (failure is not null) + { + Assert.Fail(failure.ToString()); + } + } + + private sealed class TemporaryDirectory : IDisposable + { + public TemporaryDirectory() + { + Path = System.IO.Path.Combine( + System.IO.Path.GetTempPath(), + "SightAdapt.Tests", + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(Path); + } + + public string Path { get; } + + public void Dispose() + { + if (Directory.Exists(Path)) + { + Directory.Delete(Path, recursive: true); + } + } + } + } + '''.replace(' ', ''), encoding='utf-8', newline='\n') + + Path('tests/SightAdapt.Tests/ThemeTokenTests.cs').write_text(r'''using System.Windows.Forms; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + namespace SightAdapt.Tests; + + [TestClass] + public sealed class ThemeTokenTests + { + [TestMethod] + public void DangerButtonStatesUseSemanticThemeTokens() + { + var resting = ModernButton.ResolveColors( + ModernButtonStyle.Danger, + enabled: true, + hovered: false, + pressed: false); + var hovered = ModernButton.ResolveColors( + ModernButtonStyle.Danger, + enabled: true, + hovered: true, + pressed: false); + var pressed = ModernButton.ResolveColors( + ModernButtonStyle.Danger, + enabled: true, + hovered: false, + pressed: true); + + Assert.AreEqual(AppTheme.DangerSoft, resting.Background); + Assert.AreEqual(AppTheme.DangerBorder, resting.Border); + Assert.AreEqual(AppTheme.DangerHover, hovered.Background); + Assert.AreEqual(AppTheme.Danger, hovered.Border); + Assert.AreEqual(AppTheme.DangerPressed, pressed.Background); + } + + [TestMethod] + public void GridUsesAlternateSurfaceThemeToken() + { + RunOnSta(() => + { + using var grid = new DataGridView(); + AppTheme.StyleGrid(grid); + Assert.AreEqual( + AppTheme.SurfaceAlternate, + grid.AlternatingRowsDefaultCellStyle.BackColor); + }); + } + + [TestMethod] + public void SelectorPaintFontResolutionReusesExistingFonts() + { + using var cellStyleFont = AppTheme.CreateUiFont(9.5f); + using var gridFont = AppTheme.CreateUiFont(10f); + + Assert.AreSame( + cellStyleFont, + ModernSelectorComboBoxCell.ResolvePaintFont( + cellStyleFont, + gridFont)); + Assert.AreSame( + gridFont, + ModernSelectorComboBoxCell.ResolvePaintFont(null, gridFont)); + Assert.AreSame( + Control.DefaultFont, + ModernSelectorComboBoxCell.ResolvePaintFont(null, null)); + } + + private static void RunOnSta(Action action) + { + Exception? failure = null; + var thread = new Thread(() => + { + try + { + action(); + } + catch (Exception exception) + { + failure = exception; + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + + Assert.IsTrue( + thread.Join(TimeSpan.FromSeconds(10)), + "The theme test did not finish in time."); + if (failure is not null) + { + Assert.Fail(failure.ToString()); + } + } + } + '''.replace(' ', ''), encoding='utf-8', newline='\n') + PY + + git diff --check + + - name: Commit UI cleanup + shell: bash + run: | + if git diff --quiet; then + echo "UI cleanup is already applied." + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add src/SightAdapt tests/SightAdapt.Tests + git commit -m "Apply final UI cleanup" + git push origin "HEAD:${{ github.head_ref }}" From 5391d142886d8745f6c14da0162f617cf7291217 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 19:24:45 +0000 Subject: [PATCH 4/7] Apply final UI cleanup --- src/SightAdapt/ConfigurationForm.cs | 17 ++- .../ModernSelectorComboBoxColumn.cs | 25 +++- src/SightAdapt/ModernTheme.cs | 73 +++++++++-- src/SightAdapt/SightAdapt.csproj | 6 +- ...ConfigurationProfileManagerRefreshTests.cs | 115 ++++++++++++++++++ tests/SightAdapt.Tests/ThemeTokenTests.cs | 92 ++++++++++++++ 6 files changed, 303 insertions(+), 25 deletions(-) create mode 100644 tests/SightAdapt.Tests/ConfigurationProfileManagerRefreshTests.cs create mode 100644 tests/SightAdapt.Tests/ThemeTokenTests.cs diff --git a/src/SightAdapt/ConfigurationForm.cs b/src/SightAdapt/ConfigurationForm.cs index cf76062f..0fb33471 100644 --- a/src/SightAdapt/ConfigurationForm.cs +++ b/src/SightAdapt/ConfigurationForm.cs @@ -7,6 +7,8 @@ internal sealed class ConfigurationForm : Form { private readonly SettingsCoordinator _settingsCoordinator; private readonly Func _getCurrentApplication; + private readonly Action + _showVisualProfileManager; private readonly ToggleSwitch _automaticModeSwitch; private readonly Label _automaticModeStateLabel; private readonly Label _profileCountLabel; @@ -17,12 +19,16 @@ internal sealed class ConfigurationForm : Form public ConfigurationForm( SettingsCoordinator settingsCoordinator, - Func getCurrentApplication) + Func getCurrentApplication, + Action? + showVisualProfileManager = null) { _settingsCoordinator = settingsCoordinator ?? throw new ArgumentNullException(nameof(settingsCoordinator)); _getCurrentApplication = getCurrentApplication ?? throw new ArgumentNullException(nameof(getCurrentApplication)); + _showVisualProfileManager = showVisualProfileManager ?? + VisualProfileManagerForm.ShowManager; Text = ProductInfo.WindowTitle; StartPosition = FormStartPosition.CenterScreen; @@ -62,6 +68,8 @@ public ConfigurationForm( private SightAdaptSettings Settings => _settingsCoordinator.Current; + internal int RefreshGeneration { get; private set; } + public void RefreshProfiles() { if (IsDisposed) @@ -88,6 +96,8 @@ public void RefreshProfiles() { _refreshing = false; } + + RefreshGeneration++; } private Control CreateRootLayout() @@ -628,10 +638,9 @@ private void EditSelectedVisualProfile() } } - private void ManageVisualProfiles() + internal void ManageVisualProfiles() { - VisualProfileManagerForm.ShowManager(this, _settingsCoordinator); - RefreshProfiles(); + _showVisualProfileManager(this, _settingsCoordinator); } private ApplicationProfile? GetSelectedApplicationProfile() diff --git a/src/SightAdapt/ModernSelectorComboBoxColumn.cs b/src/SightAdapt/ModernSelectorComboBoxColumn.cs index 98d6c234..c2b61b3c 100644 --- a/src/SightAdapt/ModernSelectorComboBoxColumn.cs +++ b/src/SightAdapt/ModernSelectorComboBoxColumn.cs @@ -155,7 +155,9 @@ protected override void Paint( graphics, Rectangle.Inflate(cellBounds, -7, -6), formattedValue?.ToString() ?? string.Empty, - cellStyle.Font ?? AppTheme.CreateUiFont(9.5f), + ResolvePaintFont( + cellStyle.Font, + DataGridView?.Font), foreground, selected, focused: @@ -163,12 +165,22 @@ protected override void Paint( DataGridView?.CurrentCellAddress.X == ColumnIndex && DataGridView.CurrentCellAddress.Y == rowIndex); } + + internal static Font ResolvePaintFont( + Font? cellStyleFont, + Font? gridFont) + { + return cellStyleFont ?? + gridFont ?? + Control.DefaultFont; + } } internal sealed class ModernSelectorEditingControl : Control, IDataGridViewEditingControl { + private readonly Font _defaultFont; private readonly ListBox _list; private readonly ToolStripDropDown _dropDown; private ModernSelectorOption[] _options = []; @@ -177,10 +189,12 @@ internal sealed class ModernSelectorEditingControl : public ModernSelectorEditingControl() { + _defaultFont = AppTheme.CreateUiFont(9.5f); + AccessibleRole = AccessibleRole.ComboBox; BackColor = AppTheme.SurfaceRaised; Cursor = Cursors.Hand; - Font = AppTheme.CreateUiFont(9.5f); + Font = _defaultFont; ForeColor = AppTheme.TextPrimary; TabStop = true; SetStyle( @@ -196,7 +210,7 @@ public ModernSelectorEditingControl() BackColor = AppTheme.SurfaceRaised, BorderStyle = BorderStyle.None, DrawMode = DrawMode.OwnerDrawFixed, - Font = AppTheme.CreateUiFont(9.5f), + Font = _defaultFont, ForeColor = AppTheme.TextPrimary, IntegralHeight = false, ItemHeight = 34, @@ -248,7 +262,7 @@ public void Configure( { _options = options.ToArray(); EditingControlValueChanged = false; - Font = style.Font ?? AppTheme.CreateUiFont(9.5f); + Font = style.Font ?? _defaultFont; ForeColor = AppTheme.TextPrimary; BackColor = AppTheme.SurfaceRaised; SelectByValue(selectedId); @@ -261,7 +275,7 @@ public void Configure( public void ApplyCellStyleToEditingControl( DataGridViewCellStyle dataGridViewCellStyle) { - Font = dataGridViewCellStyle.Font ?? AppTheme.CreateUiFont(9.5f); + Font = dataGridViewCellStyle.Font ?? _defaultFont; ForeColor = AppTheme.TextPrimary; BackColor = AppTheme.SurfaceRaised; } @@ -397,6 +411,7 @@ protected override void Dispose(bool disposing) { _dropDown.Dispose(); _list.Dispose(); + _defaultFont.Dispose(); } base.Dispose(disposing); diff --git a/src/SightAdapt/ModernTheme.cs b/src/SightAdapt/ModernTheme.cs index 8efd4276..a9de410f 100644 --- a/src/SightAdapt/ModernTheme.cs +++ b/src/SightAdapt/ModernTheme.cs @@ -14,6 +14,7 @@ internal static class AppTheme public static readonly Color WindowBackground = Color.FromArgb(20, 23, 31); public static readonly Color HeaderBackground = Color.FromArgb(24, 28, 38); public static readonly Color Surface = Color.FromArgb(29, 34, 45); + public static readonly Color SurfaceAlternate = Color.FromArgb(32, 38, 50); public static readonly Color SurfaceRaised = Color.FromArgb(36, 42, 55); public static readonly Color SurfaceHover = Color.FromArgb(45, 53, 69); public static readonly Color Border = Color.FromArgb(54, 63, 81); @@ -28,6 +29,9 @@ internal static class AppTheme public static readonly Color SuccessSoft = Color.FromArgb(30, 76, 67); public static readonly Color Danger = Color.FromArgb(255, 111, 128); public static readonly Color DangerSoft = Color.FromArgb(84, 43, 54); + public static readonly Color DangerHover = Color.FromArgb(96, 47, 59); + public static readonly Color DangerPressed = Color.FromArgb(105, 47, 60); + public static readonly Color DangerBorder = Color.FromArgb(120, 58, 70); public static readonly Color Selection = Color.FromArgb(52, 67, 105); public static Font CreateUiFont(float size = 9.5f, FontStyle style = FontStyle.Regular) @@ -109,7 +113,7 @@ public static void StyleGrid(DataGridView grid) grid.AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle { - BackColor = Color.FromArgb(32, 38, 50), + BackColor = SurfaceAlternate, ForeColor = TextPrimary, SelectionBackColor = Selection, SelectionForeColor = TextPrimary, @@ -243,28 +247,71 @@ protected override void OnPaint(PaintEventArgs eventArgs) private (Color Background, Color Border, Color Foreground) ResolveColors() { - if (!Enabled) + return ResolveColors( + VisualStyle, + Enabled, + _hovered, + _pressed); + } + + internal static ( + Color Background, + Color Border, + Color Foreground) ResolveColors( + ModernButtonStyle visualStyle, + bool enabled, + bool hovered, + bool pressed) + { + if (!enabled) { - return (AppTheme.Surface, AppTheme.Border, AppTheme.TextMuted); + return ( + AppTheme.Surface, + AppTheme.Border, + AppTheme.TextMuted); } - return VisualStyle switch + return visualStyle switch { ModernButtonStyle.Primary => ( - _pressed ? AppTheme.AccentPressed : _hovered ? AppTheme.AccentHover : AppTheme.Accent, - _pressed ? AppTheme.AccentPressed : AppTheme.AccentHover, + pressed + ? AppTheme.AccentPressed + : hovered + ? AppTheme.AccentHover + : AppTheme.Accent, + pressed + ? AppTheme.AccentPressed + : AppTheme.AccentHover, Color.White), ModernButtonStyle.Danger => ( - _pressed ? Color.FromArgb(105, 47, 60) : _hovered ? Color.FromArgb(96, 47, 59) : AppTheme.DangerSoft, - _hovered ? AppTheme.Danger : Color.FromArgb(120, 58, 70), - _hovered ? Color.White : AppTheme.Danger), + pressed + ? AppTheme.DangerPressed + : hovered + ? AppTheme.DangerHover + : AppTheme.DangerSoft, + hovered + ? AppTheme.Danger + : AppTheme.DangerBorder, + hovered + ? Color.White + : AppTheme.Danger), ModernButtonStyle.Ghost => ( - _pressed ? AppTheme.SurfaceRaised : _hovered ? AppTheme.SurfaceHover : AppTheme.WindowBackground, - _hovered ? AppTheme.Border : AppTheme.WindowBackground, + pressed + ? AppTheme.SurfaceRaised + : hovered + ? AppTheme.SurfaceHover + : AppTheme.WindowBackground, + hovered + ? AppTheme.Border + : AppTheme.WindowBackground, AppTheme.TextSecondary), _ => ( - _pressed ? AppTheme.SurfaceHover : _hovered ? AppTheme.SurfaceHover : AppTheme.SurfaceRaised, - _hovered ? AppTheme.Accent : AppTheme.Border, + pressed || hovered + ? AppTheme.SurfaceHover + : AppTheme.SurfaceRaised, + hovered + ? AppTheme.Accent + : AppTheme.Border, AppTheme.TextPrimary), }; } diff --git a/src/SightAdapt/SightAdapt.csproj b/src/SightAdapt/SightAdapt.csproj index 61c91af9..8e63bdf3 100644 --- a/src/SightAdapt/SightAdapt.csproj +++ b/src/SightAdapt/SightAdapt.csproj @@ -16,10 +16,10 @@ Copyright © $(Company) MIT https://github.com/KeyffMS/SightAdapt - 0.5.0.33 + 0.5.0.34 0.5.0.0 - 0.5.0.33 - 0.5.0.33 + 0.5.0.34 + 0.5.0.34 x64 x64 10.0.19041.0 diff --git a/tests/SightAdapt.Tests/ConfigurationProfileManagerRefreshTests.cs b/tests/SightAdapt.Tests/ConfigurationProfileManagerRefreshTests.cs new file mode 100644 index 00000000..dc4e27f7 --- /dev/null +++ b/tests/SightAdapt.Tests/ConfigurationProfileManagerRefreshTests.cs @@ -0,0 +1,115 @@ +using System.Windows.Forms; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace SightAdapt.Tests; + +[TestClass] +public sealed class ConfigurationProfileManagerRefreshTests +{ + [TestMethod] + public void ClosingManagerWithoutChangesDoesNotRefreshConfiguration() + { + RunOnSta(() => + { + using var temporaryDirectory = new TemporaryDirectory(); + var coordinator = CreateCoordinator(temporaryDirectory.Path); + var managerCalls = 0; + using var form = new ConfigurationForm( + coordinator, + () => null, + (_, receivedCoordinator) => + { +Assert.AreSame(coordinator, receivedCoordinator); +managerCalls++; + }); + var generation = form.RefreshGeneration; + + form.ManageVisualProfiles(); + + Assert.AreEqual(1, managerCalls); + Assert.AreEqual(generation, form.RefreshGeneration); + }); + } + + [TestMethod] + public void ManagerMutationRefreshesConfigurationExactlyOnce() + { + RunOnSta(() => + { + using var temporaryDirectory = new TemporaryDirectory(); + var coordinator = CreateCoordinator(temporaryDirectory.Path); + using var form = new ConfigurationForm( + coordinator, + () => null, + (_, receivedCoordinator) => + { +var result = receivedCoordinator.Commit(settings => + VisualProfileManagementService.Create( + settings, + "Reader").Id); +Assert.IsTrue(result.Succeeded); + }); + var generation = form.RefreshGeneration; + + form.ManageVisualProfiles(); + + Assert.AreEqual(generation + 1, form.RefreshGeneration); + Assert.IsTrue(coordinator.Current.VisualProfiles.Any( + profile => profile.Name == "Reader")); + }); + } + + private static SettingsCoordinator CreateCoordinator(string directory) + { + return new SettingsCoordinator( + new SettingsStore(Path.Combine(directory, "settings.json"))); + } + + private static void RunOnSta(Action action) + { + Exception? failure = null; + var thread = new Thread(() => + { + try + { + action(); + } + catch (Exception exception) + { + failure = exception; + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + + Assert.IsTrue( + thread.Join(TimeSpan.FromSeconds(10)), + "The configuration refresh test did not finish in time."); + if (failure is not null) + { + Assert.Fail(failure.ToString()); + } + } + + private sealed class TemporaryDirectory : IDisposable + { + public TemporaryDirectory() + { + Path = System.IO.Path.Combine( + System.IO.Path.GetTempPath(), + "SightAdapt.Tests", + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(Path); + } + + public string Path { get; } + + public void Dispose() + { + if (Directory.Exists(Path)) + { + Directory.Delete(Path, recursive: true); + } + } + } +} diff --git a/tests/SightAdapt.Tests/ThemeTokenTests.cs b/tests/SightAdapt.Tests/ThemeTokenTests.cs new file mode 100644 index 00000000..8624281b --- /dev/null +++ b/tests/SightAdapt.Tests/ThemeTokenTests.cs @@ -0,0 +1,92 @@ +using System.Windows.Forms; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace SightAdapt.Tests; + +[TestClass] +public sealed class ThemeTokenTests +{ + [TestMethod] + public void DangerButtonStatesUseSemanticThemeTokens() + { + var resting = ModernButton.ResolveColors( + ModernButtonStyle.Danger, + enabled: true, + hovered: false, + pressed: false); + var hovered = ModernButton.ResolveColors( + ModernButtonStyle.Danger, + enabled: true, + hovered: true, + pressed: false); + var pressed = ModernButton.ResolveColors( + ModernButtonStyle.Danger, + enabled: true, + hovered: false, + pressed: true); + + Assert.AreEqual(AppTheme.DangerSoft, resting.Background); + Assert.AreEqual(AppTheme.DangerBorder, resting.Border); + Assert.AreEqual(AppTheme.DangerHover, hovered.Background); + Assert.AreEqual(AppTheme.Danger, hovered.Border); + Assert.AreEqual(AppTheme.DangerPressed, pressed.Background); + } + + [TestMethod] + public void GridUsesAlternateSurfaceThemeToken() + { + RunOnSta(() => + { + using var grid = new DataGridView(); + AppTheme.StyleGrid(grid); + Assert.AreEqual( + AppTheme.SurfaceAlternate, + grid.AlternatingRowsDefaultCellStyle.BackColor); + }); + } + + [TestMethod] + public void SelectorPaintFontResolutionReusesExistingFonts() + { + using var cellStyleFont = AppTheme.CreateUiFont(9.5f); + using var gridFont = AppTheme.CreateUiFont(10f); + + Assert.AreSame( + cellStyleFont, + ModernSelectorComboBoxCell.ResolvePaintFont( + cellStyleFont, + gridFont)); + Assert.AreSame( + gridFont, + ModernSelectorComboBoxCell.ResolvePaintFont(null, gridFont)); + Assert.AreSame( + Control.DefaultFont, + ModernSelectorComboBoxCell.ResolvePaintFont(null, null)); + } + + private static void RunOnSta(Action action) + { + Exception? failure = null; + var thread = new Thread(() => + { + try + { + action(); + } + catch (Exception exception) + { + failure = exception; + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + + Assert.IsTrue( + thread.Join(TimeSpan.FromSeconds(10)), + "The theme test did not finish in time."); + if (failure is not null) + { + Assert.Fail(failure.ToString()); + } + } +} From 74cc031ccc3713041e486a90d5b43420554a432e Mon Sep 17 00:00:00 2001 From: KeyffMS <124252104+KeyffMS@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:25:16 +0200 Subject: [PATCH 5/7] Remove temporary UI patch workflow --- .github/workflows/source-snapshot.yml | 363 -------------------------- 1 file changed, 363 deletions(-) delete mode 100644 .github/workflows/source-snapshot.yml diff --git a/.github/workflows/source-snapshot.yml b/.github/workflows/source-snapshot.yml deleted file mode 100644 index 858562a4..00000000 --- a/.github/workflows/source-snapshot.yml +++ /dev/null @@ -1,363 +0,0 @@ -name: Temporary final UI patch - -on: - pull_request: - branches: [main] - -permissions: - contents: write - pull-requests: read - -jobs: - patch: - if: >- - github.event.pull_request.head.repo.full_name == github.repository && - github.head_ref == 'agent/final-ui-cleanup' - runs-on: ubuntu-latest - - steps: - - name: Check out pull-request branch - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Apply final UI cleanup - shell: bash - run: | - python - <<'PY' - from pathlib import Path - - def replace_required(text: str, old: str, new: str, label: str) -> str: - if old not in text: - if new in text: - return text - raise RuntimeError(f"Required source block not found: {label}") - return text.replace(old, new, 1) - - path = Path('src/SightAdapt/ConfigurationForm.cs') - source = path.read_text(encoding='utf-8') - source = replace_required( - source, - ''' private readonly Func _getCurrentApplication;\n private readonly ToggleSwitch _automaticModeSwitch;''', - ''' private readonly Func _getCurrentApplication;\n private readonly Action\n _showVisualProfileManager;\n private readonly ToggleSwitch _automaticModeSwitch;''', - 'configuration manager field') - source = replace_required( - source, - ''' public ConfigurationForm(\n SettingsCoordinator settingsCoordinator,\n Func getCurrentApplication)\n {\n _settingsCoordinator = settingsCoordinator ??\n throw new ArgumentNullException(nameof(settingsCoordinator));\n _getCurrentApplication = getCurrentApplication ??\n throw new ArgumentNullException(nameof(getCurrentApplication));''', - ''' public ConfigurationForm(\n SettingsCoordinator settingsCoordinator,\n Func getCurrentApplication,\n Action?\n showVisualProfileManager = null)\n {\n _settingsCoordinator = settingsCoordinator ??\n throw new ArgumentNullException(nameof(settingsCoordinator));\n _getCurrentApplication = getCurrentApplication ??\n throw new ArgumentNullException(nameof(getCurrentApplication));\n _showVisualProfileManager = showVisualProfileManager ??\n VisualProfileManagerForm.ShowManager;''', - 'configuration constructor') - source = replace_required( - source, - ''' private SightAdaptSettings Settings => _settingsCoordinator.Current;\n\n public void RefreshProfiles()''', - ''' private SightAdaptSettings Settings => _settingsCoordinator.Current;\n\n internal int RefreshGeneration { get; private set; }\n\n public void RefreshProfiles()''', - 'configuration refresh counter') - source = replace_required( - source, - ''' finally\n {\n _refreshing = false;\n }\n }\n\n private Control CreateRootLayout()''', - ''' finally\n {\n _refreshing = false;\n }\n\n RefreshGeneration++;\n }\n\n private Control CreateRootLayout()''', - 'configuration refresh generation') - source = replace_required( - source, - ''' private void ManageVisualProfiles()\n {\n VisualProfileManagerForm.ShowManager(this, _settingsCoordinator);\n RefreshProfiles();\n }''', - ''' internal void ManageVisualProfiles()\n {\n _showVisualProfileManager(this, _settingsCoordinator);\n }''', - 'configuration manager refresh') - path.write_text(source, encoding='utf-8', newline='\n') - - path = Path('src/SightAdapt/ModernTheme.cs') - source = path.read_text(encoding='utf-8') - source = replace_required( - source, - ''' public static readonly Color Surface = Color.FromArgb(29, 34, 45);\n public static readonly Color SurfaceRaised = Color.FromArgb(36, 42, 55);''', - ''' public static readonly Color Surface = Color.FromArgb(29, 34, 45);\n public static readonly Color SurfaceAlternate = Color.FromArgb(32, 38, 50);\n public static readonly Color SurfaceRaised = Color.FromArgb(36, 42, 55);''', - 'alternate surface token') - source = replace_required( - source, - ''' public static readonly Color Danger = Color.FromArgb(255, 111, 128);\n public static readonly Color DangerSoft = Color.FromArgb(84, 43, 54);\n public static readonly Color Selection = Color.FromArgb(52, 67, 105);''', - ''' public static readonly Color Danger = Color.FromArgb(255, 111, 128);\n public static readonly Color DangerSoft = Color.FromArgb(84, 43, 54);\n public static readonly Color DangerHover = Color.FromArgb(96, 47, 59);\n public static readonly Color DangerPressed = Color.FromArgb(105, 47, 60);\n public static readonly Color DangerBorder = Color.FromArgb(120, 58, 70);\n public static readonly Color Selection = Color.FromArgb(52, 67, 105);''', - 'danger theme tokens') - source = source.replace( - ' BackColor = Color.FromArgb(32, 38, 50),', - ' BackColor = SurfaceAlternate,') - source = replace_required( - source, - ''' private (Color Background, Color Border, Color Foreground) ResolveColors()\n {\n if (!Enabled)\n {\n return (AppTheme.Surface, AppTheme.Border, AppTheme.TextMuted);\n }\n\n return VisualStyle switch\n {\n ModernButtonStyle.Primary => (\n _pressed ? AppTheme.AccentPressed : _hovered ? AppTheme.AccentHover : AppTheme.Accent,\n _pressed ? AppTheme.AccentPressed : AppTheme.AccentHover,\n Color.White),\n ModernButtonStyle.Danger => (\n _pressed ? Color.FromArgb(105, 47, 60) : _hovered ? Color.FromArgb(96, 47, 59) : AppTheme.DangerSoft,\n _hovered ? AppTheme.Danger : Color.FromArgb(120, 58, 70),\n _hovered ? Color.White : AppTheme.Danger),\n ModernButtonStyle.Ghost => (\n _pressed ? AppTheme.SurfaceRaised : _hovered ? AppTheme.SurfaceHover : AppTheme.WindowBackground,\n _hovered ? AppTheme.Border : AppTheme.WindowBackground,\n AppTheme.TextSecondary),\n _ => (\n _pressed ? AppTheme.SurfaceHover : _hovered ? AppTheme.SurfaceHover : AppTheme.SurfaceRaised,\n _hovered ? AppTheme.Accent : AppTheme.Border,\n AppTheme.TextPrimary),\n };\n }''', - ''' private (Color Background, Color Border, Color Foreground) ResolveColors()\n {\n return ResolveColors(\n VisualStyle,\n Enabled,\n _hovered,\n _pressed);\n }\n\n internal static (\n Color Background,\n Color Border,\n Color Foreground) ResolveColors(\n ModernButtonStyle visualStyle,\n bool enabled,\n bool hovered,\n bool pressed)\n {\n if (!enabled)\n {\n return (\n AppTheme.Surface,\n AppTheme.Border,\n AppTheme.TextMuted);\n }\n\n return visualStyle switch\n {\n ModernButtonStyle.Primary => (\n pressed\n ? AppTheme.AccentPressed\n : hovered\n ? AppTheme.AccentHover\n : AppTheme.Accent,\n pressed\n ? AppTheme.AccentPressed\n : AppTheme.AccentHover,\n Color.White),\n ModernButtonStyle.Danger => (\n pressed\n ? AppTheme.DangerPressed\n : hovered\n ? AppTheme.DangerHover\n : AppTheme.DangerSoft,\n hovered\n ? AppTheme.Danger\n : AppTheme.DangerBorder,\n hovered\n ? Color.White\n : AppTheme.Danger),\n ModernButtonStyle.Ghost => (\n pressed\n ? AppTheme.SurfaceRaised\n : hovered\n ? AppTheme.SurfaceHover\n : AppTheme.WindowBackground,\n hovered\n ? AppTheme.Border\n : AppTheme.WindowBackground,\n AppTheme.TextSecondary),\n _ => (\n pressed || hovered\n ? AppTheme.SurfaceHover\n : AppTheme.SurfaceRaised,\n hovered\n ? AppTheme.Accent\n : AppTheme.Border,\n AppTheme.TextPrimary),\n };\n }''', - 'button color resolution') - path.write_text(source, encoding='utf-8', newline='\n') - - path = Path('src/SightAdapt/ModernSelectorComboBoxColumn.cs') - source = path.read_text(encoding='utf-8') - source = replace_required( - source, - ' cellStyle.Font ?? AppTheme.CreateUiFont(9.5f),', - ''' ResolvePaintFont(\n cellStyle.Font,\n DataGridView?.Font),''', - 'selector paint font') - source = replace_required( - source, - ''' }\n}\n\ninternal sealed class ModernSelectorEditingControl :''', - ''' }\n\n internal static Font ResolvePaintFont(\n Font? cellStyleFont,\n Font? gridFont)\n {\n return cellStyleFont ??\n gridFont ??\n Control.DefaultFont;\n }\n}\n\ninternal sealed class ModernSelectorEditingControl :''', - 'selector font helper') - source = replace_required( - source, - ''' private readonly ListBox _list;\n private readonly ToolStripDropDown _dropDown;''', - ''' private readonly Font _defaultFont;\n private readonly ListBox _list;\n private readonly ToolStripDropDown _dropDown;''', - 'selector owned font field') - source = replace_required( - source, - ''' public ModernSelectorEditingControl()\n {\n AccessibleRole = AccessibleRole.ComboBox;''', - ''' public ModernSelectorEditingControl()\n {\n _defaultFont = AppTheme.CreateUiFont(9.5f);\n\n AccessibleRole = AccessibleRole.ComboBox;''', - 'selector owned font initialization') - source = source.replace( - ' Font = AppTheme.CreateUiFont(9.5f);', - ' Font = _defaultFont;', - 1) - source = source.replace( - ' Font = AppTheme.CreateUiFont(9.5f),', - ' Font = _defaultFont,', - 1) - source = source.replace( - ' Font = style.Font ?? AppTheme.CreateUiFont(9.5f);', - ' Font = style.Font ?? _defaultFont;') - source = source.replace( - ' Font = dataGridViewCellStyle.Font ?? AppTheme.CreateUiFont(9.5f);', - ' Font = dataGridViewCellStyle.Font ?? _defaultFont;') - source = replace_required( - source, - ''' if (disposing)\n {\n _dropDown.Dispose();\n _list.Dispose();\n }''', - ''' if (disposing)\n {\n _dropDown.Dispose();\n _list.Dispose();\n _defaultFont.Dispose();\n }''', - 'selector owned font disposal') - path.write_text(source, encoding='utf-8', newline='\n') - - project = Path('src/SightAdapt/SightAdapt.csproj') - source = project.read_text(encoding='utf-8') - source = source.replace('0.5.0.33', '0.5.0.34') - project.write_text(source, encoding='utf-8', newline='\n') - - Path('tests/SightAdapt.Tests/ConfigurationProfileManagerRefreshTests.cs').write_text(r'''using System.Windows.Forms; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - namespace SightAdapt.Tests; - - [TestClass] - public sealed class ConfigurationProfileManagerRefreshTests - { - [TestMethod] - public void ClosingManagerWithoutChangesDoesNotRefreshConfiguration() - { - RunOnSta(() => - { - using var temporaryDirectory = new TemporaryDirectory(); - var coordinator = CreateCoordinator(temporaryDirectory.Path); - var managerCalls = 0; - using var form = new ConfigurationForm( - coordinator, - () => null, - (_, receivedCoordinator) => - { - Assert.AreSame(coordinator, receivedCoordinator); - managerCalls++; - }); - var generation = form.RefreshGeneration; - - form.ManageVisualProfiles(); - - Assert.AreEqual(1, managerCalls); - Assert.AreEqual(generation, form.RefreshGeneration); - }); - } - - [TestMethod] - public void ManagerMutationRefreshesConfigurationExactlyOnce() - { - RunOnSta(() => - { - using var temporaryDirectory = new TemporaryDirectory(); - var coordinator = CreateCoordinator(temporaryDirectory.Path); - using var form = new ConfigurationForm( - coordinator, - () => null, - (_, receivedCoordinator) => - { - var result = receivedCoordinator.Commit(settings => - VisualProfileManagementService.Create( - settings, - "Reader").Id); - Assert.IsTrue(result.Succeeded); - }); - var generation = form.RefreshGeneration; - - form.ManageVisualProfiles(); - - Assert.AreEqual(generation + 1, form.RefreshGeneration); - Assert.IsTrue(coordinator.Current.VisualProfiles.Any( - profile => profile.Name == "Reader")); - }); - } - - private static SettingsCoordinator CreateCoordinator(string directory) - { - return new SettingsCoordinator( - new SettingsStore(Path.Combine(directory, "settings.json"))); - } - - private static void RunOnSta(Action action) - { - Exception? failure = null; - var thread = new Thread(() => - { - try - { - action(); - } - catch (Exception exception) - { - failure = exception; - } - }); - thread.SetApartmentState(ApartmentState.STA); - thread.Start(); - - Assert.IsTrue( - thread.Join(TimeSpan.FromSeconds(10)), - "The configuration refresh test did not finish in time."); - if (failure is not null) - { - Assert.Fail(failure.ToString()); - } - } - - private sealed class TemporaryDirectory : IDisposable - { - public TemporaryDirectory() - { - Path = System.IO.Path.Combine( - System.IO.Path.GetTempPath(), - "SightAdapt.Tests", - Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(Path); - } - - public string Path { get; } - - public void Dispose() - { - if (Directory.Exists(Path)) - { - Directory.Delete(Path, recursive: true); - } - } - } - } - '''.replace(' ', ''), encoding='utf-8', newline='\n') - - Path('tests/SightAdapt.Tests/ThemeTokenTests.cs').write_text(r'''using System.Windows.Forms; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - namespace SightAdapt.Tests; - - [TestClass] - public sealed class ThemeTokenTests - { - [TestMethod] - public void DangerButtonStatesUseSemanticThemeTokens() - { - var resting = ModernButton.ResolveColors( - ModernButtonStyle.Danger, - enabled: true, - hovered: false, - pressed: false); - var hovered = ModernButton.ResolveColors( - ModernButtonStyle.Danger, - enabled: true, - hovered: true, - pressed: false); - var pressed = ModernButton.ResolveColors( - ModernButtonStyle.Danger, - enabled: true, - hovered: false, - pressed: true); - - Assert.AreEqual(AppTheme.DangerSoft, resting.Background); - Assert.AreEqual(AppTheme.DangerBorder, resting.Border); - Assert.AreEqual(AppTheme.DangerHover, hovered.Background); - Assert.AreEqual(AppTheme.Danger, hovered.Border); - Assert.AreEqual(AppTheme.DangerPressed, pressed.Background); - } - - [TestMethod] - public void GridUsesAlternateSurfaceThemeToken() - { - RunOnSta(() => - { - using var grid = new DataGridView(); - AppTheme.StyleGrid(grid); - Assert.AreEqual( - AppTheme.SurfaceAlternate, - grid.AlternatingRowsDefaultCellStyle.BackColor); - }); - } - - [TestMethod] - public void SelectorPaintFontResolutionReusesExistingFonts() - { - using var cellStyleFont = AppTheme.CreateUiFont(9.5f); - using var gridFont = AppTheme.CreateUiFont(10f); - - Assert.AreSame( - cellStyleFont, - ModernSelectorComboBoxCell.ResolvePaintFont( - cellStyleFont, - gridFont)); - Assert.AreSame( - gridFont, - ModernSelectorComboBoxCell.ResolvePaintFont(null, gridFont)); - Assert.AreSame( - Control.DefaultFont, - ModernSelectorComboBoxCell.ResolvePaintFont(null, null)); - } - - private static void RunOnSta(Action action) - { - Exception? failure = null; - var thread = new Thread(() => - { - try - { - action(); - } - catch (Exception exception) - { - failure = exception; - } - }); - thread.SetApartmentState(ApartmentState.STA); - thread.Start(); - - Assert.IsTrue( - thread.Join(TimeSpan.FromSeconds(10)), - "The theme test did not finish in time."); - if (failure is not null) - { - Assert.Fail(failure.ToString()); - } - } - } - '''.replace(' ', ''), encoding='utf-8', newline='\n') - PY - - git diff --check - - - name: Commit UI cleanup - shell: bash - run: | - if git diff --quiet; then - echo "UI cleanup is already applied." - exit 0 - fi - - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add src/SightAdapt tests/SightAdapt.Tests - git commit -m "Apply final UI cleanup" - git push origin "HEAD:${{ github.head_ref }}" From e5b3d8d2d0f2821ce7a393d41b6c01fa89473113 Mon Sep 17 00:00:00 2001 From: KeyffMS <124252104+KeyffMS@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:27:29 +0200 Subject: [PATCH 6/7] Format configuration refresh regression tests --- ...ConfigurationProfileManagerRefreshTests.cs | 128 ++++++++++-------- 1 file changed, 73 insertions(+), 55 deletions(-) diff --git a/tests/SightAdapt.Tests/ConfigurationProfileManagerRefreshTests.cs b/tests/SightAdapt.Tests/ConfigurationProfileManagerRefreshTests.cs index dc4e27f7..20822686 100644 --- a/tests/SightAdapt.Tests/ConfigurationProfileManagerRefreshTests.cs +++ b/tests/SightAdapt.Tests/ConfigurationProfileManagerRefreshTests.cs @@ -11,23 +11,29 @@ public void ClosingManagerWithoutChangesDoesNotRefreshConfiguration() { RunOnSta(() => { - using var temporaryDirectory = new TemporaryDirectory(); - var coordinator = CreateCoordinator(temporaryDirectory.Path); - var managerCalls = 0; - using var form = new ConfigurationForm( - coordinator, - () => null, - (_, receivedCoordinator) => - { -Assert.AreSame(coordinator, receivedCoordinator); -managerCalls++; - }); - var generation = form.RefreshGeneration; + using var temporaryDirectory = + new TemporaryDirectory(); + var coordinator = CreateCoordinator( + temporaryDirectory.Path); + var managerCalls = 0; + using var form = new ConfigurationForm( + coordinator, + () => null, + (_, receivedCoordinator) => + { + Assert.AreSame( + coordinator, + receivedCoordinator); + managerCalls++; + }); + var generation = form.RefreshGeneration; - form.ManageVisualProfiles(); + form.ManageVisualProfiles(); - Assert.AreEqual(1, managerCalls); - Assert.AreEqual(generation, form.RefreshGeneration); + Assert.AreEqual(1, managerCalls); + Assert.AreEqual( + generation, + form.RefreshGeneration); }); } @@ -36,33 +42,43 @@ public void ManagerMutationRefreshesConfigurationExactlyOnce() { RunOnSta(() => { - using var temporaryDirectory = new TemporaryDirectory(); - var coordinator = CreateCoordinator(temporaryDirectory.Path); - using var form = new ConfigurationForm( - coordinator, - () => null, - (_, receivedCoordinator) => - { -var result = receivedCoordinator.Commit(settings => - VisualProfileManagementService.Create( - settings, - "Reader").Id); -Assert.IsTrue(result.Succeeded); - }); - var generation = form.RefreshGeneration; + using var temporaryDirectory = + new TemporaryDirectory(); + var coordinator = CreateCoordinator( + temporaryDirectory.Path); + using var form = new ConfigurationForm( + coordinator, + () => null, + (_, receivedCoordinator) => + { + var result = + receivedCoordinator.Commit(settings => + VisualProfileManagementService.Create( + settings, + "Reader").Id); + Assert.IsTrue(result.Succeeded); + }); + var generation = form.RefreshGeneration; - form.ManageVisualProfiles(); + form.ManageVisualProfiles(); - Assert.AreEqual(generation + 1, form.RefreshGeneration); - Assert.IsTrue(coordinator.Current.VisualProfiles.Any( - profile => profile.Name == "Reader")); + Assert.AreEqual( + generation + 1, + form.RefreshGeneration); + Assert.IsTrue( + coordinator.Current.VisualProfiles.Any( + profile => profile.Name == "Reader")); }); } - private static SettingsCoordinator CreateCoordinator(string directory) + private static SettingsCoordinator CreateCoordinator( + string directory) { return new SettingsCoordinator( - new SettingsStore(Path.Combine(directory, "settings.json"))); + new SettingsStore( + Path.Combine( + directory, + "settings.json"))); } private static void RunOnSta(Action action) @@ -70,24 +86,24 @@ private static void RunOnSta(Action action) Exception? failure = null; var thread = new Thread(() => { - try - { - action(); - } - catch (Exception exception) - { - failure = exception; - } + try + { + action(); + } + catch (Exception exception) + { + failure = exception; + } }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); Assert.IsTrue( - thread.Join(TimeSpan.FromSeconds(10)), - "The configuration refresh test did not finish in time."); + thread.Join(TimeSpan.FromSeconds(10)), + "The configuration refresh test did not finish in time."); if (failure is not null) { - Assert.Fail(failure.ToString()); + Assert.Fail(failure.ToString()); } } @@ -95,21 +111,23 @@ private sealed class TemporaryDirectory : IDisposable { public TemporaryDirectory() { - Path = System.IO.Path.Combine( - System.IO.Path.GetTempPath(), - "SightAdapt.Tests", - Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(Path); + Path = System.IO.Path.Combine( + System.IO.Path.GetTempPath(), + "SightAdapt.Tests", + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(Path); } public string Path { get; } public void Dispose() { - if (Directory.Exists(Path)) - { - Directory.Delete(Path, recursive: true); - } + if (Directory.Exists(Path)) + { + Directory.Delete( + Path, + recursive: true); + } } } } From ec6d18be5599790d87f2f8cf6026fc3d1dcdb152 Mon Sep 17 00:00:00 2001 From: KeyffMS <124252104+KeyffMS@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:27:54 +0200 Subject: [PATCH 7/7] Format theme regression tests --- tests/SightAdapt.Tests/ThemeTokenTests.cs | 103 +++++++++++++--------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/tests/SightAdapt.Tests/ThemeTokenTests.cs b/tests/SightAdapt.Tests/ThemeTokenTests.cs index 8624281b..6a7ab20a 100644 --- a/tests/SightAdapt.Tests/ThemeTokenTests.cs +++ b/tests/SightAdapt.Tests/ThemeTokenTests.cs @@ -10,26 +10,36 @@ public sealed class ThemeTokenTests public void DangerButtonStatesUseSemanticThemeTokens() { var resting = ModernButton.ResolveColors( - ModernButtonStyle.Danger, - enabled: true, - hovered: false, - pressed: false); + ModernButtonStyle.Danger, + enabled: true, + hovered: false, + pressed: false); var hovered = ModernButton.ResolveColors( - ModernButtonStyle.Danger, - enabled: true, - hovered: true, - pressed: false); + ModernButtonStyle.Danger, + enabled: true, + hovered: true, + pressed: false); var pressed = ModernButton.ResolveColors( - ModernButtonStyle.Danger, - enabled: true, - hovered: false, - pressed: true); + ModernButtonStyle.Danger, + enabled: true, + hovered: false, + pressed: true); - Assert.AreEqual(AppTheme.DangerSoft, resting.Background); - Assert.AreEqual(AppTheme.DangerBorder, resting.Border); - Assert.AreEqual(AppTheme.DangerHover, hovered.Background); - Assert.AreEqual(AppTheme.Danger, hovered.Border); - Assert.AreEqual(AppTheme.DangerPressed, pressed.Background); + Assert.AreEqual( + AppTheme.DangerSoft, + resting.Background); + Assert.AreEqual( + AppTheme.DangerBorder, + resting.Border); + Assert.AreEqual( + AppTheme.DangerHover, + hovered.Background); + Assert.AreEqual( + AppTheme.Danger, + hovered.Border); + Assert.AreEqual( + AppTheme.DangerPressed, + pressed.Background); } [TestMethod] @@ -37,31 +47,38 @@ public void GridUsesAlternateSurfaceThemeToken() { RunOnSta(() => { - using var grid = new DataGridView(); - AppTheme.StyleGrid(grid); - Assert.AreEqual( - AppTheme.SurfaceAlternate, - grid.AlternatingRowsDefaultCellStyle.BackColor); + using var grid = new DataGridView(); + AppTheme.StyleGrid(grid); + + Assert.AreEqual( + AppTheme.SurfaceAlternate, + grid.AlternatingRowsDefaultCellStyle.BackColor); }); } [TestMethod] public void SelectorPaintFontResolutionReusesExistingFonts() { - using var cellStyleFont = AppTheme.CreateUiFont(9.5f); - using var gridFont = AppTheme.CreateUiFont(10f); + using var cellStyleFont = + AppTheme.CreateUiFont(9.5f); + using var gridFont = + AppTheme.CreateUiFont(10f); Assert.AreSame( - cellStyleFont, - ModernSelectorComboBoxCell.ResolvePaintFont( - cellStyleFont, - gridFont)); + cellStyleFont, + ModernSelectorComboBoxCell.ResolvePaintFont( + cellStyleFont, + gridFont)); Assert.AreSame( - gridFont, - ModernSelectorComboBoxCell.ResolvePaintFont(null, gridFont)); + gridFont, + ModernSelectorComboBoxCell.ResolvePaintFont( + null, + gridFont)); Assert.AreSame( - Control.DefaultFont, - ModernSelectorComboBoxCell.ResolvePaintFont(null, null)); + Control.DefaultFont, + ModernSelectorComboBoxCell.ResolvePaintFont( + null, + null)); } private static void RunOnSta(Action action) @@ -69,24 +86,24 @@ private static void RunOnSta(Action action) Exception? failure = null; var thread = new Thread(() => { - try - { - action(); - } - catch (Exception exception) - { - failure = exception; - } + try + { + action(); + } + catch (Exception exception) + { + failure = exception; + } }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); Assert.IsTrue( - thread.Join(TimeSpan.FromSeconds(10)), - "The theme test did not finish in time."); + thread.Join(TimeSpan.FromSeconds(10)), + "The theme test did not finish in time."); if (failure is not null) { - Assert.Fail(failure.ToString()); + Assert.Fail(failure.ToString()); } } }