diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs index 461710b..29cc32f 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs @@ -139,6 +139,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase // Set by the view so DeleteTaskCommand can prompt yes/no before deleting public Func>? ConfirmAsync { get; set; } + // Set by the view so DeleteTaskCommand can show an error message + public Func? ShowErrorAsync { get; set; } + public DetailsIslandViewModel(IDbContextFactory dbFactory, WorkerClient worker, IServiceProvider services) { _dbFactory = dbFactory; @@ -537,9 +540,20 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase var ok = await ConfirmAsync($"Delete \"{row.Title}\"? This cannot be undone."); if (!ok) return; } - await using var ctx = _dbFactory.CreateDbContext(); - var repo = new TaskRepository(ctx); - await repo.DeleteAsync(row.Id); + try + { + await using var ctx = _dbFactory.CreateDbContext(); + var repo = new TaskRepository(ctx); + await repo.DeleteAsync(row.Id); + } + catch (DbUpdateException ex) when ( + ex.Message.Contains("FOREIGN KEY", StringComparison.OrdinalIgnoreCase) + || ex.InnerException?.Message.Contains("FOREIGN KEY", StringComparison.OrdinalIgnoreCase) == true) + { + if (ShowErrorAsync != null) + await ShowErrorAsync("This task has child tasks. Discard the planning session or delete child tasks first."); + return; + } if (DeleteFromList != null) await DeleteFromList(row); CloseDetail?.Invoke(); diff --git a/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml.cs b/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml.cs index 9d345a8..62016b7 100644 --- a/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml.cs +++ b/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml.cs @@ -45,9 +45,49 @@ public partial class DetailsIslandView : UserControl }; vm.ConfirmAsync = ShowConfirmAsync; + vm.ShowErrorAsync = ShowErrorDialogAsync; } } + private async System.Threading.Tasks.Task ShowErrorDialogAsync(string message) + { + var owner = TopLevel.GetTopLevel(this) as Window; + if (owner == 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;