From c4f74a7aeae3f17f19592f86a2b44c2968894c59 Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Tue, 23 Jun 2026 09:05:17 +0200 Subject: [PATCH] feat(ui): Log Visualizer overlay reachable from a clickable footer log line --- src/ClaudeDo.Localization/locales/de.json | 9 +++ src/ClaudeDo.Localization/locales/en.json | 9 +++ src/ClaudeDo.Ui/Services/IDialogService.cs | 1 + .../Services/Interfaces/IWorkerClient.cs | 1 + src/ClaudeDo.Ui/Services/WorkerClient.cs | 3 + .../ViewModels/IslandsShellViewModel.cs | 9 +++ .../Modals/LogVisualizerViewModel.cs | 58 ++++++++++++++++++ src/ClaudeDo.Ui/Views/MainWindow.axaml | 31 +++++++--- .../Views/Modals/LogVisualizerView.axaml | 59 +++++++++++++++++++ .../Views/Modals/LogVisualizerView.axaml.cs | 10 ++++ src/ClaudeDo.Ui/Views/WindowDialogService.cs | 7 +++ tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs | 1 + .../ViewModels/LogVisualizerViewModelTests.cs | 51 ++++++++++++++++ .../UiVm/TasksIslandViewModelPlanningTests.cs | 1 + 14 files changed, 241 insertions(+), 9 deletions(-) create mode 100644 src/ClaudeDo.Ui/ViewModels/Modals/LogVisualizerViewModel.cs create mode 100644 src/ClaudeDo.Ui/Views/Modals/LogVisualizerView.axaml create mode 100644 src/ClaudeDo.Ui/Views/Modals/LogVisualizerView.axaml.cs create mode 100644 tests/ClaudeDo.Ui.Tests/ViewModels/LogVisualizerViewModelTests.cs diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json index 2407f09..db873dc 100644 --- a/src/ClaudeDo.Localization/locales/de.json +++ b/src/ClaudeDo.Localization/locales/de.json @@ -234,6 +234,15 @@ "reviewResetTip": "Alle Änderungen verwerfen und die Aufgabe auf Leerlauf zurücksetzen" }, "modals": { + "logVisualizer": { + "title": "WORKER-LOGS — LETZTE 30 MIN", + "warnErrorOnly": "Nur Warnungen & Fehler", + "refresh": "Aktualisieren", + "empty": "Keine Logs in den letzten 30 Minuten.", + "count": "{0} Einträge", + "footerHint": "logs", + "openTooltip": "Aktuelle Worker-Logs anzeigen" + }, "about": { "title": "ÜBER", "version": "Version", diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json index 89bfeea..f1043aa 100644 --- a/src/ClaudeDo.Localization/locales/en.json +++ b/src/ClaudeDo.Localization/locales/en.json @@ -234,6 +234,15 @@ "reviewResetTip": "Discard all changes and reset the task to Idle" }, "modals": { + "logVisualizer": { + "title": "WORKER LOGS — LAST 30 MIN", + "warnErrorOnly": "Warnings & errors only", + "refresh": "Refresh", + "empty": "No logs in the last 30 minutes.", + "count": "{0} entries", + "footerHint": "logs", + "openTooltip": "View recent worker logs" + }, "about": { "title": "ABOUT", "version": "Version", diff --git a/src/ClaudeDo.Ui/Services/IDialogService.cs b/src/ClaudeDo.Ui/Services/IDialogService.cs index 94fe0a2..090fc09 100644 --- a/src/ClaudeDo.Ui/Services/IDialogService.cs +++ b/src/ClaudeDo.Ui/Services/IDialogService.cs @@ -21,6 +21,7 @@ public interface IDialogService Task ShowWorktreesOverviewAsync(WorktreesOverviewModalViewModel vm); Task ShowWorkerConnectionAsync(WorkerConnectionModalViewModel vm); Task ShowConflictResolverAsync(ConflictResolverViewModel vm); + Task ShowLogVisualizerAsync(LogVisualizerViewModel vm); /// Modal yes/no confirmation. Returns true only when confirmed. Task ConfirmAsync(string message); diff --git a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs index d357001..ee10573 100644 --- a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs +++ b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs @@ -85,6 +85,7 @@ public interface IWorkerClient : INotifyPropertyChanged Task UpdateDailyNoteAsync(string id, string text); Task DeleteDailyNoteAsync(string id); Task GetLastPrepLogAsync(); + Task> GetRecentLogsAsync(); Task> GetPrimeSchedulesAsync(); Task UpsertPrimeScheduleAsync(PrimeScheduleDto dto); diff --git a/src/ClaudeDo.Ui/Services/WorkerClient.cs b/src/ClaudeDo.Ui/Services/WorkerClient.cs index 9d12f5c..9f88fcc 100644 --- a/src/ClaudeDo.Ui/Services/WorkerClient.cs +++ b/src/ClaudeDo.Ui/Services/WorkerClient.cs @@ -388,6 +388,9 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC public async Task GetLastPrepLogAsync() => await TryInvokeAsync("GetLastPrepLog") ?? string.Empty; + public async Task> GetRecentLogsAsync() + => await TryInvokeAsync>("GetRecentLogs") ?? new List(); + public async Task UpdateListAsync(UpdateListDto dto) { await _hub.InvokeAsync("UpdateList", dto); diff --git a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs index c97d891..c5b1d38 100644 --- a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs @@ -290,6 +290,15 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable if (Dialogs is not null) await Dialogs.ShowAboutAsync(vm); } + [RelayCommand] + private async Task OpenLogVisualizer() + { + if (Dialogs is null || Worker is null) return; + var vm = new LogVisualizerViewModel(Worker); + await vm.RefreshAsync(); + await Dialogs.ShowLogVisualizerAsync(vm); + } + private bool _connectionPromptShown; internal bool DecideShowConnectionPrompt(bool isOffline) diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/LogVisualizerViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/LogVisualizerViewModel.cs new file mode 100644 index 0000000..a979dd5 --- /dev/null +++ b/src/ClaudeDo.Ui/ViewModels/Modals/LogVisualizerViewModel.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using ClaudeDo.Data.Models; +using ClaudeDo.Ui.Localization; +using ClaudeDo.Ui.Services; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; + +namespace ClaudeDo.Ui.ViewModels.Modals; + +/// +/// Log Visualizer overlay — shows the worker's last 30 min of log records (all levels), +/// fetched once on open via with a manual +/// Refresh and a "warnings & errors only" filter. +/// +public sealed partial class LogVisualizerViewModel : ViewModelBase +{ + private readonly IWorkerClient _worker; + private IReadOnlyList _all = Array.Empty(); + + public ObservableCollection Rows { get; } = new(); + + [ObservableProperty] private bool _warnErrorOnly; + [ObservableProperty] private string _statusText = ""; + + public Action? CloseAction { get; set; } + + public LogVisualizerViewModel(IWorkerClient worker) => _worker = worker; + + [RelayCommand] + public async Task RefreshAsync() + { + _all = await _worker.GetRecentLogsAsync(); + Apply(); + } + + partial void OnWarnErrorOnlyChanged(bool value) => Apply(); + + private void Apply() + { + Rows.Clear(); + IEnumerable items = WarnErrorOnly + ? _all.Where(e => e.Level is WorkerLogLevel.Warn or WorkerLogLevel.Error) + : _all; + foreach (var e in items) + Rows.Add(new LogVisualizerRow(e.TimestampUtc.ToLocalTime().ToString("HH:mm:ss"), e.Message, e.Level)); + StatusText = Rows.Count == 0 + ? Loc.T("modals.logVisualizer.empty") + : Loc.T("modals.logVisualizer.count", Rows.Count); + } + + [RelayCommand] private void Close() => CloseAction?.Invoke(); +} + +public sealed record LogVisualizerRow(string Time, string Message, WorkerLogLevel Level); diff --git a/src/ClaudeDo.Ui/Views/MainWindow.axaml b/src/ClaudeDo.Ui/Views/MainWindow.axaml index 5318f25..21c3728 100644 --- a/src/ClaudeDo.Ui/Views/MainWindow.axaml +++ b/src/ClaudeDo.Ui/Views/MainWindow.axaml @@ -215,15 +215,28 @@ - - + + + + + + + + + + + + + + + +