Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- **Traceroute live output** — ConsoleView with hop-by-hop results and explanations
(gateway detection, ISP backbone, filtered nodes, destination reached).

### Fixed
- **DashboardView** — replaced 30+ hardcoded hex colors with StaticResource tokens
(Surface1, Surface2, Border1, TextPrimary, TextSecondary, Info).
- **AppBlockerView** — full structural modernization (Display header, Card wrappers,
button styles, DataGrid accessibility, Background, margins).
- **DnsHostsView** — DataGrid grid-lines removed, accessibility name added, text token.
- **ObservableCollection → BulkObservableCollection** — AppAlerts, AppBlocker,
ShortcutCleaner now use single-notification ReplaceWith() instead of N Add() events.
- **Missing toast notifications** — added on Drivers, Services, ShortcutCleaner,
DeepCleanup (3 operations), NetworkRepair.
- **UninstallerView** — hardcoded `#6366F1` replaced with `{StaticResource Accent}`.

## [1.10.1] - 2026-05-26

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Verify version numbers and release dates.

Four distinct versions ([1.10.1], [1.10.0], [1.9.1], [1.9.0]) all share the same release date (2026-05-26), which is unusual and may indicate:

  • Version numbering mistakes
  • Releases that should be consolidated
  • Missing date corrections

Additionally, the content under [1.10.1] (lines 24-34) describes "UI uniformity audit" which sounds more like a minor feature/refactor than a patch-level bug fix, potentially misaligning with semantic versioning conventions where x.x.PATCH should represent backwards-compatible bug fixes only.

Consider:

  • Consolidating same-day releases if they represent a single logical release
  • Updating dates if releases actually occurred on different days
  • Reviewing whether [1.10.1] changes are truly patch-level or should be a minor version bump

Also applies to: 47-47

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` at line 22, The changelog shows four releases ([1.10.1],
[1.10.0], [1.9.1], [1.9.0]) all with the same date and [1.10.1] contains a "UI
uniformity audit" that may not be a patch; verify and correct the versioning and
dates: confirm the actual release dates for each tag and update each header to
the correct date or consolidate entries if they represent a single release, and
reevaluate whether the [1.10.1] UI changes should be promoted to a minor bump
(e.g., 1.11.0) instead of a patch to preserve semantic versioning; adjust the
headers and move the UI audit under the appropriate version section accordingly.


### Fixed
- **UI uniformity audit** — replaced all remaining CheckBoxes with purple ToggleSwitch on:
Performance (5 toggles), Logs (5 severity filters), Ping targets, Process Manager,
Expand All @@ -38,6 +52,13 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- **Startup Manager hide system** — toggle to filter out Windows/Microsoft system entries.
- **Filter chip styles** — reusable green/amber/red radio pill components.

## [1.9.1] - 2026-05-26

### Fixed
- **Startup Manager columns** — reduced fixed widths to prevent last column overflow.
- **Startup Manager icons** — use extracted executable path for more accurate icon resolution.
- **Windows Update live output** — increased MinHeight/MaxHeight for better visibility.

## [1.9.0] - 2026-05-26

### Added
Expand Down
10 changes: 5 additions & 5 deletions SysManager/SysManager/ViewModels/AppAlertsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
// Author: laurentiu021 · https://github.com/laurentiu021/SystemManager
// License: MIT

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Serilog;
using SysManager.Helpers;
using SysManager.Models;
using SysManager.Services;

Expand All @@ -22,7 +22,7 @@ public sealed partial class AppAlertsViewModel : ViewModelBase
private readonly AppAlertService _service;
private readonly Dispatcher _dispatcher;

public ObservableCollection<AppInstallEntry> Alerts { get; } = new();
public BulkObservableCollection<AppInstallEntry> Alerts { get; } = new();

[ObservableProperty] private bool _isMonitoring;
[ObservableProperty] private string _monitorStatus = "Click Start to begin monitoring for new installations.";
Expand Down Expand Up @@ -90,13 +90,13 @@ private void RefreshInstalledApps()
try
{
var apps = AppAlertService.GetRegistryApps();
Alerts.Clear();
foreach (var app in apps.OrderBy(a => a.Name))
var sorted = apps.OrderBy(a => a.Name).ToList();
foreach (var app in sorted)
{
app.DetectedAt = DateTime.Now;
app.IsAcknowledged = true;
Alerts.Add(app);
}
Alerts.ReplaceWith(sorted);
AlertCount = Alerts.Count;
UnacknowledgedCount = 0;
MonitorStatus = $"Loaded {AlertCount} currently installed applications.";
Expand Down
7 changes: 2 additions & 5 deletions SysManager/SysManager/ViewModels/AppBlockerViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Author: laurentiu021 · https://github.com/laurentiu021/SystemManager
// License: MIT

using System.Collections.ObjectModel;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
Expand All @@ -19,7 +18,7 @@ namespace SysManager.ViewModels;
/// </summary>
public sealed partial class AppBlockerViewModel : ViewModelBase
{
public ObservableCollection<BlockedApp> BlockedApps { get; } = new();
public BulkObservableCollection<BlockedApp> BlockedApps { get; } = new();

[ObservableProperty] private string _newExeName = "";
[ObservableProperty] private string _blockStatus = "Enter an executable name and click Block to prevent it from running.";
Expand All @@ -42,10 +41,8 @@ private void RelaunchAsAdmin()
[RelayCommand]
private void RefreshList()
{
BlockedApps.Clear();
var apps = AppBlockerService.GetBlockedApps();
foreach (var a in apps)
BlockedApps.Add(a);
BlockedApps.ReplaceWith(apps);
BlockedCount = BlockedApps.Count;
BlockStatus = BlockedCount == 0
? "No applications are currently blocked."
Expand Down
3 changes: 3 additions & 0 deletions SysManager/SysManager/ViewModels/DeepCleanupViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ private async Task ScanCoreAsync()
var total = cats.Sum(c => c.TotalSizeBytes);
ScanSummary = $"Found {FormatHelper.FormatSize(total)} across {cats.Count} categories. Untick anything you want to keep.";
ScanStatusLine = "Scan complete.";
ToastService.Instance.Show("Deep cleanup scan complete", $"{FormatHelper.FormatSize(total)} across {cats.Count} categories");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid double toast notifications after a successful clean.

CleanAsync shows a completion toast (Line 231) and then calls ScanCoreAsync (Line 233), which now always shows another scan-complete toast (Line 190). This creates two back-to-back toasts for one user action.

Suggested minimal adjustment
-private async Task ScanCoreAsync()
+private async Task ScanCoreAsync(bool showToast = true)
 {
     ...
-    ToastService.Instance.Show("Deep cleanup scan complete", $"{FormatHelper.FormatSize(total)} across {cats.Count} categories");
+    if (showToast)
+        ToastService.Instance.Show("Deep cleanup scan complete", $"{FormatHelper.FormatSize(total)} across {cats.Count} categories");
     ...
 }
 
 ...
-await ScanCoreAsync();
+await ScanCoreAsync(showToast: false);

Also applies to: 231-233

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SysManager/SysManager/ViewModels/DeepCleanupViewModel.cs` at line 190,
ScanCoreAsync always shows a completion toast (ToastService.Instance.Show) which
causes a duplicate notification when CleanAsync finishes and then calls
ScanCoreAsync; modify ScanCoreAsync to take an optional boolean parameter (e.g.,
bool suppressToast = false) and only call ToastService.Instance.Show when
suppressToast is false, then update CleanAsync to call ScanCoreAsync with
suppressToast = true so only the CleanAsync completion toast is shown.

Log.Information("Deep cleanup scan completed: {Size} across {Count} categories",
FormatHelper.FormatSize(total), cats.Count);
OnPropertyChanged(nameof(TotalSelectedBytes));
Expand Down Expand Up @@ -227,6 +228,7 @@ private async Task CleanAsync()
var result = await _cleanup.CleanAsync(Categories, progress, _cleanCts.Token);
CleanSummary = result.Summary;
CleanStatusLine = "Clean complete.";
ToastService.Instance.Show("Deep cleanup complete", result.Summary);
Log.Information("Deep cleanup completed");
await ScanCoreAsync();
}
Expand Down Expand Up @@ -293,6 +295,7 @@ private async Task ScanLargeFilesAsync()
ct: _largeCts.Token);
LargeFiles.ReplaceWith(list);
LargeScanStatus = $"Found {list.Count} files ≥ {MinSizeMB} MB in {SelectedLocation.Label.Trim()}.";
ToastService.Instance.Show("Large file scan complete", $"{list.Count} files found ≥ {MinSizeMB} MB");
Log.Information("Large file scan completed: {Count} files ≥ {MinSize} MB",
list.Count, MinSizeMB);
}
Expand Down
2 changes: 1 addition & 1 deletion SysManager/SysManager/ViewModels/DriversViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Author: laurentiu021 · https://github.com/laurentiu021/SystemManager
// License: MIT

using System.Collections.ObjectModel;
using System.Globalization;
using System.Text.Json;
using CommunityToolkit.Mvvm.ComponentModel;
Expand Down Expand Up @@ -69,6 +68,7 @@ await _runner.RunScriptViaPwshAsync(@"
Summary = $"{_allDrivers.Count} drivers found" +
(HideSystemDrivers ? $" ({DriverCount} shown, system drivers hidden)." : ".");
StatusMessage = "Done";
ToastService.Instance.Show("Driver scan complete", $"{_allDrivers.Count} drivers found");
}
catch (OperationCanceledException) { StatusMessage = "Cancelled."; }
catch (InvalidOperationException ex) { StatusMessage = ex.Message; }
Expand Down
3 changes: 3 additions & 0 deletions SysManager/SysManager/ViewModels/NetworkRepairViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ private async Task RunRepairAsync(
? $"✓ {r.ToolName} completed successfully."
: $"✗ {r.ToolName} failed: {r.Output}";
if (r.Success)
{
ToastService.Instance.Show("Network repair complete", $"{r.ToolName} completed successfully");
Log.Information("Network repair completed: {Tool}", r.ToolName);
}
else
Log.Warning("Network repair failed: {Tool}", r.ToolName);
if (r.NeedsReboot && r.Success)
Expand Down
2 changes: 1 addition & 1 deletion SysManager/SysManager/ViewModels/ServicesViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Author: laurentiu021 · https://github.com/laurentiu021/SystemManager
// License: MIT

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
Expand Down Expand Up @@ -89,6 +88,7 @@ private void ApplyFilterCore()
RunningCount = _allServices.Count(s => s.Status == "Running");
ApplyFilter();
StatusMessage = $"Loaded {TotalCount} services ({RunningCount} running).";
ToastService.Instance.Show("Services refreshed", $"{TotalCount} services ({RunningCount} running)");
}

[RelayCommand]
Expand Down
12 changes: 6 additions & 6 deletions SysManager/SysManager/ViewModels/ShortcutCleanerViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
// Author: laurentiu021 · https://github.com/laurentiu021/SystemManager
// License: MIT

using System.Collections.ObjectModel;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Serilog;
using SysManager.Helpers;
using SysManager.Models;
using SysManager.Services;

Expand All @@ -20,7 +20,7 @@ public sealed partial class ShortcutCleanerViewModel : ViewModelBase
private readonly ShortcutCleanerService _service;
private CancellationTokenSource? _cts;

public ObservableCollection<BrokenShortcut> BrokenShortcuts { get; } = new();
public BulkObservableCollection<BrokenShortcut> BrokenShortcuts { get; } = new();

[ObservableProperty] private string _scanStatus = "Click Scan to find broken shortcuts.";
[ObservableProperty] private string _currentLocation = "";
Expand Down Expand Up @@ -53,7 +53,7 @@ private async Task ScanAsync()
// PropertyChanged lambda leaks across rescans.
foreach (var old in BrokenShortcuts)
old.PropertyChanged -= OnShortcutPropertyChanged;
BrokenShortcuts.Clear();
BrokenShortcuts.ReplaceWith(Array.Empty<BrokenShortcut>());
BrokenCount = 0;
SelectedCount = 0;
ScanStatus = "Scanning...";
Expand All @@ -66,10 +66,8 @@ private async Task ScanAsync()
var results = await _service.ScanAsync(progress, _cts.Token);

foreach (var s in results)
{
s.PropertyChanged += OnShortcutPropertyChanged;
BrokenShortcuts.Add(s);
}
BrokenShortcuts.ReplaceWith(results);

BrokenCount = BrokenShortcuts.Count;
SelectedCount = BrokenShortcuts.Count(x => x.IsSelected);
Expand All @@ -78,6 +76,7 @@ private async Task ScanAsync()
: $"Found {BrokenCount} broken shortcut{(BrokenCount == 1 ? "" : "s")}.";
CurrentLocation = "";
Log.Information("Shortcut scan completed: {Count} broken shortcuts found", BrokenCount);
ToastService.Instance.Show("Shortcut scan complete", $"{BrokenCount} broken shortcut{(BrokenCount == 1 ? "" : "s")} found");
}
catch (OperationCanceledException) { ScanStatus = "Scan cancelled."; }
catch (System.IO.IOException ex) { ScanStatus = $"Scan failed: {ex.Message}"; }
Expand Down Expand Up @@ -116,6 +115,7 @@ private void DeleteSelected()
BrokenCount = BrokenShortcuts.Count;
SelectedCount = BrokenShortcuts.Count(x => x.IsSelected);
ScanStatus = $"Deleted {deleted} shortcut{(deleted == 1 ? "" : "s")}. {BrokenCount} remaining.";
ToastService.Instance.Show("Shortcuts deleted", $"{deleted} shortcut{(deleted == 1 ? "" : "s")} removed");
Log.Information("Deleted {Count} broken shortcuts (recycle bin: {RecycleBin})", deleted, MoveToRecycleBin);
}

Expand Down
27 changes: 14 additions & 13 deletions SysManager/SysManager/Views/AppBlockerView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
d:DataContext="{d:DesignInstance vm:AppBlockerViewModel}"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
mc:Ignorable="d"
Background="{StaticResource Surface0}">

<Grid Margin="24">
<Grid Margin="28,24,28,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
Expand All @@ -20,10 +21,8 @@

<!-- Header -->
<StackPanel Grid.Row="0" Margin="0,0,0,16">
<TextBlock Text="Application Blocker" FontSize="20" FontWeight="SemiBold"
Foreground="{StaticResource TextPrimary}" />
<TextBlock Text="{Binding BlockStatus}" Opacity="0.7" Margin="0,4,0,0"
Foreground="{StaticResource TextSecondary}" />
<TextBlock Text="Application Blocker" Style="{StaticResource Display}" />
<TextBlock Text="{Binding BlockStatus}" Style="{StaticResource Subtle}" Margin="0,4,0,0" />
</StackPanel>

<!-- Elevation banner: not elevated -->
Expand Down Expand Up @@ -61,26 +60,28 @@
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,0,0,12">
<TextBox Text="{Binding NewExeName, UpdateSourceTrigger=PropertyChanged}" Width="250" Padding="6,4"
VerticalAlignment="Center" Margin="0,0,8,0" />
<Button Content="Block" Command="{Binding BlockAppCommand}" Padding="12,6" Margin="0,0,8,0" />
<Button Content="Browse..." Command="{Binding BrowseForExeCommand}" Padding="8,6" />
<Button Content="Block" Command="{Binding BlockAppCommand}" Style="{StaticResource PrimaryButton}" Padding="12,6" Margin="0,0,8,0" />
<Button Content="Browse..." Command="{Binding BrowseForExeCommand}" Style="{StaticResource SecondaryButton}" Padding="8,6" />
</StackPanel>

<!-- Toolbar -->
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="0,0,0,12">
<Button Content="Unblock Selected" Command="{Binding UnblockSelectedCommand}" Padding="12,6" Margin="0,0,8,0" />
<Button Content="Refresh" Command="{Binding RefreshListCommand}" Padding="8,6" Margin="0,0,16,0" />
<Button Content="Unblock Selected" Command="{Binding UnblockSelectedCommand}" Style="{StaticResource SecondaryButton}" Padding="12,6" Margin="0,0,8,0" />
<Button Content="Refresh" Command="{Binding RefreshListCommand}" Style="{StaticResource GhostButton}" Padding="8,6" Margin="0,0,16,0" />
<Separator Margin="0,0,16,0" />
<Button Content="Select All" Command="{Binding SelectAllCommand}" Padding="8,6" Margin="0,0,4,0" />
<Button Content="Deselect All" Command="{Binding DeselectAllCommand}" Padding="8,6" />
<Button Content="Select All" Command="{Binding SelectAllCommand}" Style="{StaticResource GhostButton}" Padding="8,6" Margin="0,0,4,0" />
<Button Content="Deselect All" Command="{Binding DeselectAllCommand}" Style="{StaticResource GhostButton}" Padding="8,6" />
</StackPanel>

<!-- Blocked apps list -->
<DataGrid Grid.Row="4" ItemsSource="{Binding BlockedApps}"
AutoGenerateColumns="False" IsReadOnly="False"
CanUserAddRows="False" CanUserDeleteRows="False"
CanUserResizeColumns="True"
HeadersVisibility="Column" GridLinesVisibility="Horizontal"
HeadersVisibility="Column" GridLinesVisibility="None"
BorderThickness="1" BorderBrush="{StaticResource Border1}"
Background="Transparent"
AutomationProperties.Name="Data table"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<DataGrid.Columns>
Expand Down
Loading
Loading