diff --git a/src/ClaudeDo.Ui/CLAUDE.md b/src/ClaudeDo.Ui/CLAUDE.md index 4713d92..c6b1787 100644 --- a/src/ClaudeDo.Ui/CLAUDE.md +++ b/src/ClaudeDo.Ui/CLAUDE.md @@ -17,7 +17,7 @@ MVVM with CommunityToolkit.Mvvm source generators: - **TaskEditorView** — Modal dialog for task create/edit - **ListEditorView** — Modal dialog for list create/edit - **StatusBarView** — Connection status indicator, active task display -- **ListSettingsModalView** — edits list name, working dir, default commit type, and per-list Model/SystemPrompt/AgentPath. Opened via context menu or gear button on a list row. +- **ListSettingsModalView** — edits list name, working dir, default commit type, and per-list Model/SystemPrompt/AgentPath; also deletes the list (and its tasks) via a confirmed "Delete list" button. Opened via context menu or gear button on a list row. - **RepoImportModalView** — bulk-creates lists from git repos discovered under chosen parent folders. Opened via the folder button beside "New list" in the Lists island, or the "Add repos as lists…" Help-menu item. Repos already wired to a list show as disabled/"(already added)". - **DetailsIslandView** — contains an "Agent settings (overrides)" expander with per-task Model/SystemPrompt/AgentPath, showing inherited effective values. Disabled while task is running. diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs index 77b45ee..744bdea 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs @@ -48,7 +48,8 @@ public sealed partial class ListsIslandViewModel : ViewModelBase var vm = _services.GetRequiredService(); await vm.LoadAsync(rawId, row.Name, row.WorkingDir, row.DefaultCommitType); await ShowListSettingsModal(vm); - await RefreshRowAsync(row.Id); + if (vm.Deleted) await LoadAsync(); + else await RefreshRowAsync(row.Id); } [RelayCommand] @@ -225,7 +226,8 @@ public sealed partial class ListsIslandViewModel : ViewModelBase var vm = _services.GetRequiredService(); await vm.LoadAsync(entity.Id, entity.Name, entity.WorkingDir, entity.DefaultCommitType); await ShowListSettingsModal(vm); - await RefreshRowAsync(item.Id); + if (vm.Deleted) await LoadAsync(); + else await RefreshRowAsync(item.Id); } } diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs index 862e917..9f7a635 100644 --- a/src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs @@ -1,17 +1,28 @@ using System.Collections.ObjectModel; +using ClaudeDo.Data; using ClaudeDo.Data.Models; +using ClaudeDo.Data.Repositories; using ClaudeDo.Ui.Services; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Microsoft.EntityFrameworkCore; namespace ClaudeDo.Ui.ViewModels.Modals; public sealed partial class ListSettingsModalViewModel : ViewModelBase { private readonly WorkerClient _worker; + private readonly IDbContextFactory _dbFactory; public string ListId { get; set; } = ""; + // True after the list was deleted, so the caller reloads the list nav instead of refreshing the row. + public bool Deleted { get; private set; } + + // Wired by the view to prompt yes/no before deleting and to surface a blocking-FK error. + public Func>? ConfirmAsync { get; set; } + public Func? ShowErrorAsync { get; set; } + [ObservableProperty] private string _name = ""; [ObservableProperty] private string _workingDir = ""; [ObservableProperty] private string _defaultCommitType = CommitTypeRegistry.DefaultType; @@ -29,9 +40,10 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase public Action? CloseAction { get; set; } - public ListSettingsModalViewModel(WorkerClient worker) + public ListSettingsModalViewModel(WorkerClient worker, IDbContextFactory dbFactory) { _worker = worker; + _dbFactory = dbFactory; } public async Task LoadAsync( @@ -78,6 +90,37 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase CloseAction?.Invoke(); } + [RelayCommand] + private async Task DeleteAsync() + { + var displayName = string.IsNullOrWhiteSpace(Name) ? "Untitled" : Name; + if (ConfirmAsync is not null) + { + var ok = await ConfirmAsync($"Delete list \"{displayName}\" and all its tasks? This cannot be undone."); + if (!ok) return; + } + + try + { + await using var ctx = await _dbFactory.CreateDbContextAsync(); + var lists = new ListRepository(ctx); + await lists.DeleteAsync(ListId); + } + catch (Exception ex) when ( + (ex is Microsoft.Data.Sqlite.SqliteException + || ex.InnerException is Microsoft.Data.Sqlite.SqliteException) + && (ex.Message.Contains("FOREIGN KEY", StringComparison.OrdinalIgnoreCase) + || ex.InnerException?.Message.Contains("FOREIGN KEY", StringComparison.OrdinalIgnoreCase) == true)) + { + if (ShowErrorAsync is not null) + await ShowErrorAsync("This list has planning sessions with child tasks. Discard those first, then delete the list."); + return; + } + + Deleted = true; + CloseAction?.Invoke(); + } + [RelayCommand] private void Cancel() => CloseAction?.Invoke(); diff --git a/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs b/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs index d14acad..5067bc5 100644 --- a/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs +++ b/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs @@ -26,6 +26,8 @@ public partial class ListsIslandView : UserControl { var window = new ListSettingsModalView { DataContext = modal }; modal.CloseAction = () => window.Close(); + modal.ConfirmAsync = ShowConfirmAsync; + modal.ShowErrorAsync = ShowErrorDialogAsync; var top = TopLevel.GetTopLevel(this) as Window; if (top is null) window.Show(); else await window.ShowDialog(top); @@ -93,6 +95,43 @@ public partial class ListsIslandView : UserControl private static System.Threading.Tasks.Task JumpToTaskAsync(IslandsShellViewModel s, string listId, string taskId) => JumpToTaskHelper.SelectAsync(s, listId, taskId); + private async System.Threading.Tasks.Task ShowErrorDialogAsync(string message) + { + var owner = TopLevel.GetTopLevel(this) as Window; + if (owner is null) return; + + var ok = new Button { Content = "OK", MinWidth = 90 }; + var dialog = new Window + { + Title = "Error", + Width = 360, + SizeToContent = SizeToContent.Height, + CanResize = false, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + ShowInTaskbar = false, + Background = this.FindResource("SurfaceBrush") as IBrush, + Content = new StackPanel + { + Spacing = 16, + Margin = new Thickness(20), + Children = + { + new TextBlock { Text = message, TextWrapping = TextWrapping.Wrap }, + new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 8, + HorizontalAlignment = HorizontalAlignment.Right, + Children = { ok }, + }, + }, + }, + }; + + ok.Click += (_, _) => dialog.Close(); + await dialog.ShowDialog(owner); + } + private async System.Threading.Tasks.Task ShowConfirmAsync(string message) { var owner = TopLevel.GetTopLevel(this) as Window; diff --git a/src/ClaudeDo.Ui/Views/Modals/ListSettingsModalView.axaml b/src/ClaudeDo.Ui/Views/Modals/ListSettingsModalView.axaml index 905aa35..0799bbc 100644 --- a/src/ClaudeDo.Ui/Views/Modals/ListSettingsModalView.axaml +++ b/src/ClaudeDo.Ui/Views/Modals/ListSettingsModalView.axaml @@ -42,6 +42,10 @@ + - -