diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/LogVisualizerViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/LogVisualizerViewModel.cs index a979dd52..88d86bd8 100644 --- a/src/ClaudeDo.Ui/ViewModels/Modals/LogVisualizerViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Modals/LogVisualizerViewModel.cs @@ -45,7 +45,7 @@ public sealed partial class LogVisualizerViewModel : ViewModelBase IEnumerable items = WarnErrorOnly ? _all.Where(e => e.Level is WorkerLogLevel.Warn or WorkerLogLevel.Error) : _all; - foreach (var e in items) + foreach (var e in items.Reverse()) Rows.Add(new LogVisualizerRow(e.TimestampUtc.ToLocalTime().ToString("HH:mm:ss"), e.Message, e.Level)); StatusText = Rows.Count == 0 ? Loc.T("modals.logVisualizer.empty") diff --git a/src/ClaudeDo.Ui/Views/Modals/LogVisualizerView.axaml b/src/ClaudeDo.Ui/Views/Modals/LogVisualizerView.axaml index 8965880f..7994f223 100644 --- a/src/ClaudeDo.Ui/Views/Modals/LogVisualizerView.axaml +++ b/src/ClaudeDo.Ui/Views/Modals/LogVisualizerView.axaml @@ -36,7 +36,7 @@ Command="{Binding RefreshCommand}"/> - + diff --git a/tests/ClaudeDo.Ui.Tests/ViewModels/LogVisualizerViewModelTests.cs b/tests/ClaudeDo.Ui.Tests/ViewModels/LogVisualizerViewModelTests.cs index abd7572b..d70ca229 100644 --- a/tests/ClaudeDo.Ui.Tests/ViewModels/LogVisualizerViewModelTests.cs +++ b/tests/ClaudeDo.Ui.Tests/ViewModels/LogVisualizerViewModelTests.cs @@ -16,14 +16,17 @@ public class LogVisualizerViewModelTests private static WorkerLogEntry E(WorkerLogLevel lvl, string msg) => new(msg, lvl, new DateTime(2026, 6, 23, 8, 0, 0, DateTimeKind.Utc)); + private static WorkerLogEntry E(WorkerLogLevel lvl, string msg, DateTime timestampUtc) + => new(msg, lvl, timestampUtc); + [Fact] - public async Task Refresh_populates_rows_from_worker() + public async Task Refresh_populates_rows_from_worker_newest_first() { var vm = new LogVisualizerViewModel(new FakeClient(new[] { E(WorkerLogLevel.Info, "a"), E(WorkerLogLevel.Error, "b") })); await vm.RefreshAsync(); - Assert.Equal(new[] { "a", "b" }, vm.Rows.Select(r => r.Message)); + Assert.Equal(new[] { "b", "a" }, vm.Rows.Select(r => r.Message)); } [Fact] @@ -35,7 +38,7 @@ public class LogVisualizerViewModelTests vm.WarnErrorOnly = true; - Assert.Equal(new[] { "w", "e" }, vm.Rows.Select(r => r.Message)); + Assert.Equal(new[] { "e", "w" }, vm.Rows.Select(r => r.Message)); } [Fact] @@ -48,4 +51,38 @@ public class LogVisualizerViewModelTests Assert.Empty(vm.Rows); Assert.False(string.IsNullOrEmpty(vm.StatusText)); } + + [Fact] + public async Task Refresh_orders_newest_entry_first() + { + var t0 = new DateTime(2026, 6, 23, 8, 0, 0, DateTimeKind.Utc); + var vm = new LogVisualizerViewModel(new FakeClient(new[] + { + E(WorkerLogLevel.Info, "oldest", t0), + E(WorkerLogLevel.Info, "middle", t0.AddMinutes(1)), + E(WorkerLogLevel.Info, "newest", t0.AddMinutes(2)), + })); + + await vm.RefreshAsync(); + + Assert.Equal(new[] { "newest", "middle", "oldest" }, vm.Rows.Select(r => r.Message)); + } + + [Fact] + public async Task Refresh_orders_newest_entry_first_with_warn_error_only() + { + var t0 = new DateTime(2026, 6, 23, 8, 0, 0, DateTimeKind.Utc); + var vm = new LogVisualizerViewModel(new FakeClient(new[] + { + E(WorkerLogLevel.Warn, "oldest", t0), + E(WorkerLogLevel.Info, "skipped", t0.AddMinutes(1)), + E(WorkerLogLevel.Error, "middle", t0.AddMinutes(2)), + E(WorkerLogLevel.Warn, "newest", t0.AddMinutes(3)), + })); + await vm.RefreshAsync(); + + vm.WarnErrorOnly = true; + + Assert.Equal(new[] { "newest", "middle", "oldest" }, vm.Rows.Select(r => r.Message)); + } }