From eff1045e63f56a631989cd59fada8ab801383d9d Mon Sep 17 00:00:00 2001 From: mika kuns Date: Mon, 20 Apr 2026 09:54:13 +0200 Subject: [PATCH] General Changes --- .../ViewModels/MainWindowViewModel.cs | 8 +++++ .../ViewModels/TaskDetailViewModel.cs | 35 +++++++++++++++++++ .../ViewModels/TaskItemViewModel.cs | 7 +++- .../ViewModels/TaskListViewModel.cs | 10 ++++-- 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/ClaudeDo.Ui/ViewModels/MainWindowViewModel.cs b/src/ClaudeDo.Ui/ViewModels/MainWindowViewModel.cs index 5bc714a..4a65e0d 100644 --- a/src/ClaudeDo.Ui/ViewModels/MainWindowViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/MainWindowViewModel.cs @@ -28,6 +28,7 @@ public partial class MainWindowViewModel : ViewModelBase, IDisposable public StatusBarViewModel StatusBar { get; } private readonly Action _onTaskChanged; + private readonly Action _onTaskSubtasksChanged; public MainWindowViewModel( IDbContextFactory dbFactory, @@ -45,13 +46,20 @@ public partial class MainWindowViewModel : ViewModelBase, IDisposable StatusBar = statusBar; _onTaskChanged = taskId => _ = TaskList.RefreshSingleAsync(taskId); + _onTaskSubtasksChanged = taskId => + { + if (TaskDetail.CurrentTaskId == taskId) + _ = TaskDetail.RefreshSubtasksFromDbAsync(); + }; TaskList.SelectedTaskChanged += OnSelectedTaskChanged; + TaskList.TaskSubtasksChanged += _onTaskSubtasksChanged; TaskDetail.TaskChanged += _onTaskChanged; } public void Dispose() { TaskList.SelectedTaskChanged -= OnSelectedTaskChanged; + TaskList.TaskSubtasksChanged -= _onTaskSubtasksChanged; TaskDetail.TaskChanged -= _onTaskChanged; } diff --git a/src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs b/src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs index 68a48cf..8ae0b36 100644 --- a/src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs @@ -51,6 +51,7 @@ public partial class TaskDetailViewModel : ViewModelBase public ObservableCollection Subtasks { get; } = new(); private string? _taskId; + public string? CurrentTaskId => _taskId; private string? _listId; private bool _isLoading; // Cancels an in-flight LoadAsync when a new TaskUpdated event arrives @@ -267,11 +268,13 @@ public partial class TaskDetailViewModel : ViewModelBase var vm = SubtaskItemViewModel.From(entity); vm.PropertyChanged += OnSubtaskPropertyChanged; Subtasks.Add(vm); + TaskChanged?.Invoke(_taskId); } [RelayCommand] private async Task RemoveSubtask(SubtaskItemViewModel item) { + if (_taskId is null) return; if (!string.IsNullOrEmpty(item.Id)) { using var context = _dbFactory.CreateDbContext(); @@ -280,6 +283,7 @@ public partial class TaskDetailViewModel : ViewModelBase } item.PropertyChanged -= OnSubtaskPropertyChanged; Subtasks.Remove(item); + TaskChanged?.Invoke(_taskId); } private async void OnSubtaskPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) @@ -301,6 +305,8 @@ public partial class TaskDetailViewModel : ViewModelBase OrderNum = Subtasks.IndexOf(vm), CreatedAt = orig?.CreatedAt ?? DateTime.UtcNow, }); + if (e.PropertyName == nameof(SubtaskItemViewModel.Completed)) + TaskChanged?.Invoke(_taskId); } catch (Exception ex) { @@ -309,6 +315,35 @@ public partial class TaskDetailViewModel : ViewModelBase } } + public async Task RefreshSubtasksFromDbAsync() + { + if (_taskId is null) return; + + List subtasks; + using (var context = _dbFactory.CreateDbContext()) + { + var subtaskRepo = new SubtaskRepository(context); + subtasks = await subtaskRepo.GetByTaskIdAsync(_taskId); + } + + _isLoading = true; + try + { + foreach (var old in Subtasks) old.PropertyChanged -= OnSubtaskPropertyChanged; + Subtasks.Clear(); + foreach (var s in subtasks) + { + var vm = SubtaskItemViewModel.From(s); + vm.PropertyChanged += OnSubtaskPropertyChanged; + Subtasks.Add(vm); + } + } + finally + { + _isLoading = false; + } + } + public void SetAgentFromPath(string path) { var existing = AvailableAgents.FirstOrDefault(a => a.Path == path); diff --git a/src/ClaudeDo.Ui/ViewModels/TaskItemViewModel.cs b/src/ClaudeDo.Ui/ViewModels/TaskItemViewModel.cs index bc2a70d..c6c44ca 100644 --- a/src/ClaudeDo.Ui/ViewModels/TaskItemViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/TaskItemViewModel.cs @@ -32,13 +32,15 @@ public partial class TaskItemViewModel : ViewModelBase private readonly Func? _runNow; private readonly Func _canRunNow; private readonly Func? _toggleDone; + private readonly Action? _onSubtasksChanged; private readonly IDbContextFactory _dbFactory; private bool _subtasksLoaded; public TaskItemViewModel(TaskEntity entity, IReadOnlyList tags, Func? runNow, Func canRunNow, IDbContextFactory dbFactory, int subtaskCount, - Func? toggleDone = null) + Func? toggleDone = null, + Action? onSubtasksChanged = null) { Entity = entity; Id = entity.Id; @@ -52,6 +54,7 @@ public partial class TaskItemViewModel : ViewModelBase _runNow = runNow; _canRunNow = canRunNow; _toggleDone = toggleDone; + _onSubtasksChanged = onSubtasksChanged; _dbFactory = dbFactory; _subtaskCount = subtaskCount; _hasSubtasks = subtaskCount > 0; @@ -154,6 +157,8 @@ public partial class TaskItemViewModel : ViewModelBase entity.Completed = vm.Completed; await context.SaveChangesAsync(); } + + _onSubtasksChanged?.Invoke(Id); } public async Task RefreshSubtasksAsync(int newCount) diff --git a/src/ClaudeDo.Ui/ViewModels/TaskListViewModel.cs b/src/ClaudeDo.Ui/ViewModels/TaskListViewModel.cs index 0ea3727..0bb6ecf 100644 --- a/src/ClaudeDo.Ui/ViewModels/TaskListViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/TaskListViewModel.cs @@ -29,10 +29,14 @@ public partial class TaskListViewModel : ViewModelBase [ObservableProperty] private string _inlineAddTitle = ""; public event Action? SelectedTaskChanged; + public event Action? TaskSubtasksChanged; partial void OnSelectedTaskChanged(TaskItemViewModel? value) => SelectedTaskChanged?.Invoke(value); + private void NotifySubtasksChanged(string taskId) => + TaskSubtasksChanged?.Invoke(taskId); + public TaskListViewModel(IDbContextFactory dbFactory, WorkerClient worker, Func editorFactory, Action showMessage) { @@ -101,7 +105,7 @@ public partial class TaskListViewModel : ViewModelBase var tags = await taskRepo.GetEffectiveTagsAsync(e.Id); subtaskCounts.TryGetValue(e.Id, out var count); Tasks.Add(new TaskItemViewModel(e, tags, RunNowAsync, () => _worker.IsConnected, - _dbFactory, count, ToggleDoneAsync)); + _dbFactory, count, ToggleDoneAsync, NotifySubtasksChanged)); } } catch (Exception ex) @@ -143,7 +147,7 @@ public partial class TaskListViewModel : ViewModelBase await taskRepo.AddAsync(entity); var tags = await taskRepo.GetEffectiveTagsAsync(entity.Id); var vm = new TaskItemViewModel(entity, tags, RunNowAsync, () => _worker.IsConnected, - _dbFactory, 0, ToggleDoneAsync); + _dbFactory, 0, ToggleDoneAsync, NotifySubtasksChanged); Tasks.Add(vm); SelectedTask = vm; InlineAddTitle = ""; @@ -195,7 +199,7 @@ public partial class TaskListViewModel : ViewModelBase var tags = await taskRepo.GetEffectiveTagsAsync(saved.Id); Tasks.Add(new TaskItemViewModel(saved, tags, RunNowAsync, () => _worker.IsConnected, - _dbFactory, 0, ToggleDoneAsync)); + _dbFactory, 0, ToggleDoneAsync, NotifySubtasksChanged)); // Auto wake-queue if agent+queued if (saved.Status == TaskStatus.Queued &&