diff --git a/BuildMonitor.Test/HistoryRunsTests.cs b/BuildMonitor.Test/HistoryRunsTests.cs
new file mode 100644
index 0000000..db030db
--- /dev/null
+++ b/BuildMonitor.Test/HistoryRunsTests.cs
@@ -0,0 +1,63 @@
+// Copyright (c) 2023-2026 ktsu-dev contributors
+
+namespace ktsu.BuildMonitor.Test;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+///
+/// Tests for .
+///
+///
+/// The History column draws its indicators left to right, and a column narrower than all of them
+/// clips on the right. The newest runs therefore have to come first, so clipping drops the oldest.
+///
+[TestClass]
+public sealed class HistoryRunsTests
+{
+ private static readonly DateTimeOffset Start = new(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
+
+ ///
+ /// Builds runs with the given statuses, most recent first, an hour apart.
+ ///
+ private static List RunsNewestFirst(params RunStatus[] statuses) =>
+ [.. statuses.Select((status, i) => new Run { Status = status, Started = Start.AddHours(-i) })];
+
+ [TestMethod]
+ public void TheNewestRunIsDrawnFirst()
+ {
+ // Arrange
+ List branchRuns = RunsNewestFirst(RunStatus.Running, RunStatus.Success, RunStatus.Success, RunStatus.Success, RunStatus.Success);
+
+ // Act
+ List history = BuildMonitor.SelectHistoryRuns(branchRuns);
+
+ // Assert
+ CollectionAssert.AreEqual(branchRuns, history);
+ }
+
+ [TestMethod]
+ public void OnlyTheFiveNewestRunsAreShown()
+ {
+ // Arrange
+ List branchRuns = RunsNewestFirst(RunStatus.Running, RunStatus.Failure, RunStatus.Success, RunStatus.Success, RunStatus.Success, RunStatus.Failure);
+
+ // Act
+ List history = BuildMonitor.SelectHistoryRuns(branchRuns);
+
+ // Assert
+ CollectionAssert.AreEqual(branchRuns.Take(5).ToList(), history);
+ }
+
+ [TestMethod]
+ public void CanceledRunsAreSkipped()
+ {
+ // Arrange
+ List branchRuns = RunsNewestFirst(RunStatus.Canceled, RunStatus.Success, RunStatus.Failure);
+
+ // Act
+ List history = BuildMonitor.SelectHistoryRuns(branchRuns);
+
+ // Assert
+ CollectionAssert.AreEqual(branchRuns.Skip(1).ToList(), history);
+ }
+}
diff --git a/BuildMonitor/BuildMonitor.cs b/BuildMonitor/BuildMonitor.cs
index 41606a9..adc05de 100644
--- a/BuildMonitor/BuildMonitor.cs
+++ b/BuildMonitor/BuildMonitor.cs
@@ -749,17 +749,27 @@ internal static bool ShouldShowBuildBranch(Build build, BranchName branch)
return shouldShow;
}
- private static void ShowBranchHistory(List branchRuns)
+ ///
+ /// Picks the runs the History column shows from a branch's runs, most recent first.
+ ///
+ ///
+ /// The indicators are drawn left to right, and a column too narrow for all of them clips on the
+ /// right, so the newest come first: clipping then drops the oldest runs rather than the newest.
+ ///
+ /// The branch's runs, most recent first.
+ /// Up to five runs that were not canceled, in the order they are drawn.
+ internal static List SelectHistoryRuns(IEnumerable branchRuns)
{
const int recentRuns = 5;
- List runs = [.. branchRuns
+ return [.. branchRuns
.Where(r => r.Status != RunStatus.Canceled)
.Take(recentRuns)];
+ }
- runs.Reverse();
-
- foreach (Run? run in runs)
+ private static void ShowBranchHistory(List branchRuns)
+ {
+ foreach (Run? run in SelectHistoryRuns(branchRuns))
{
ImGui.SameLine();
ImGuiWidgets.ColorIndicator(GetStatusColor(run.Status), true);
diff --git a/README.md b/README.md
index 89ef5c6..37cfc46 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ The main interface displays a table of all your builds grouped by workflow and b
- **Status**: Current status (Pending, Running, Success, Failure, Canceled)
- **Last Run**: Timestamp of when the build started
- **Duration**: How long the build has been running or took to complete
-- **History**: Visual history of the last 5 builds on this branch
+- **History**: Visual history of the last 5 builds on this branch, newest first
- **Progress**: Progress bar for ongoing builds
- **ETA**: Estimated time to completion for ongoing builds
- **Errors**: Error messages from failed builds (click to view full details)