diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4fecf40d..b3ac4501 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
end-to-start, eliminating O(n²) array shifting (CQ-001).
- **Shortcut Cleaner** — COM objects (`IShellLink`, `IPersistFile`) now
released via `Marshal.ReleaseComObject` in finally block (SEC-006).
+- **Models** — deduplicated `FormatSize` from `DiskUsageEntry`, `InstalledApp`,
+ and `ProcessEntry`; all now use `CleanupCategory.HumanSize` (CQ-002).
+- **Console** — batch-remove excess lines from end-to-start instead of
+ repeated `RemoveAt(0)`, reducing O(n) per append to amortized O(1) (CQ-008).
### Security
- **Speed Test** — improved download integrity comment and added
diff --git a/SysManager/SysManager.Tests/ProcessManagerServiceTests.cs b/SysManager/SysManager.Tests/ProcessManagerServiceTests.cs
index ee67a502..ae3d1985 100644
--- a/SysManager/SysManager.Tests/ProcessManagerServiceTests.cs
+++ b/SysManager/SysManager.Tests/ProcessManagerServiceTests.cs
@@ -117,7 +117,7 @@ public void OpenFileLocation_NonExistentPath_DoesNotThrow()
public void ProcessEntry_MemoryDisplay_FormatsCorrectly()
{
var entry = new ProcessEntry { MemoryBytes = 52_428_800 }; // 50 MB
- Assert.Equal("50.0 MB", entry.MemoryDisplay);
+ Assert.Equal("50 MB", entry.MemoryDisplay);
}
[Fact]
diff --git a/SysManager/SysManager/Models/DiskUsageEntry.cs b/SysManager/SysManager/Models/DiskUsageEntry.cs
index 8f05ebe6..f83fd728 100644
--- a/SysManager/SysManager/Models/DiskUsageEntry.cs
+++ b/SysManager/SysManager/Models/DiskUsageEntry.cs
@@ -21,13 +21,5 @@ public partial class DiskUsageEntry : ObservableObject
[ObservableProperty] private bool _isAccessDenied;
/// Formatted size for display.
- public string SizeDisplay => FormatSize(SizeBytes);
-
- private static string FormatSize(long bytes) => bytes switch
- {
- >= 1L << 30 => $"{bytes / (double)(1L << 30):F1} GB",
- >= 1L << 20 => $"{bytes / (double)(1L << 20):F1} MB",
- >= 1L << 10 => $"{bytes / (double)(1L << 10):F1} KB",
- _ => $"{bytes} B"
- };
+ public string SizeDisplay => CleanupCategory.HumanSize(SizeBytes);
}
diff --git a/SysManager/SysManager/Models/InstalledApp.cs b/SysManager/SysManager/Models/InstalledApp.cs
index 839ff7e7..af493cda 100644
--- a/SysManager/SysManager/Models/InstalledApp.cs
+++ b/SysManager/SysManager/Models/InstalledApp.cs
@@ -23,13 +23,5 @@ public partial class InstalledApp : ObservableObject
[ObservableProperty] private ImageSource? _icon;
/// Formatted size for display.
- public string SizeDisplay => SizeBytes > 0 ? FormatSize(SizeBytes) : "—";
-
- private static string FormatSize(long bytes) => bytes switch
- {
- >= 1L << 30 => $"{bytes / (double)(1L << 30):F1} GB",
- >= 1L << 20 => $"{bytes / (double)(1L << 20):F1} MB",
- >= 1L << 10 => $"{bytes / (double)(1L << 10):F1} KB",
- _ => $"{bytes} B"
- };
+ public string SizeDisplay => SizeBytes > 0 ? CleanupCategory.HumanSize(SizeBytes) : "—";
}
diff --git a/SysManager/SysManager/Models/ProcessEntry.cs b/SysManager/SysManager/Models/ProcessEntry.cs
index 42611cac..9727933d 100644
--- a/SysManager/SysManager/Models/ProcessEntry.cs
+++ b/SysManager/SysManager/Models/ProcessEntry.cs
@@ -33,13 +33,5 @@ public partial class ProcessEntry : ObservableObject
&& System.IO.File.Exists(FilePath);
/// Formatted memory for display.
- public string MemoryDisplay => FormatSize(MemoryBytes);
-
- private static string FormatSize(long bytes) => bytes switch
- {
- >= 1L << 30 => $"{bytes / (double)(1L << 30):F1} GB",
- >= 1L << 20 => $"{bytes / (double)(1L << 20):F1} MB",
- >= 1L << 10 => $"{bytes / (double)(1L << 10):F1} KB",
- _ => $"{bytes} B"
- };
+ public string MemoryDisplay => CleanupCategory.HumanSize(MemoryBytes);
}
diff --git a/SysManager/SysManager/ViewModels/ConsoleViewModel.cs b/SysManager/SysManager/ViewModels/ConsoleViewModel.cs
index 88126016..3c7d0b00 100644
--- a/SysManager/SysManager/ViewModels/ConsoleViewModel.cs
+++ b/SysManager/SysManager/ViewModels/ConsoleViewModel.cs
@@ -36,7 +36,12 @@ public void Append(PowerShellLine line)
lock (_gate)
{
Lines.Add(line);
- while (Lines.Count > MaxLines) Lines.RemoveAt(0);
+ if (Lines.Count > MaxLines)
+ {
+ var excess = Lines.Count - MaxLines;
+ for (int i = excess - 1; i >= 0; i--)
+ Lines.RemoveAt(i);
+ }
}
}