From 5acc896d5c438c7d82ab5bf03b83dc1255a15d33 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Mon, 20 Apr 2026 11:47:10 +0200 Subject: [PATCH] fix(ui): wire delete confirm, close-details, uppercase eyebrow, explorer button - A1: list-name eyebrow runs through UpperCase converter. - D2: + Open-in-explorer icon button in AgentStrip (Process.Start on worktree path). - D4: DeleteTaskCommand prompts inline confirm Window before deleting; shell wires Details.CloseDetail to clear Tasks.SelectedTask and Details.DeleteFromList to reload the current list. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Islands/DetailsIslandViewModel.cs | 24 +++++++++ .../ViewModels/IslandsShellViewModel.cs | 6 +++ .../Views/Islands/AgentStripView.axaml | 4 ++ .../Views/Islands/DetailsIslandView.axaml.cs | 50 +++++++++++++++++++ src/ClaudeDo.Ui/Views/MainWindow.axaml | 2 +- 5 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs index dcfe566..f2a2d72 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs @@ -78,6 +78,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase // Set by the view so OpenWorktreeCommand can show the modal as a dialog public Func? ShowWorktreeModal { get; set; } + // Set by the view so DeleteTaskCommand can prompt yes/no before deleting + public Func>? ConfirmAsync { get; set; } + public DetailsIslandViewModel(IDbContextFactory dbFactory, WorkerClient worker, IServiceProvider services) { _dbFactory = dbFactory; @@ -186,10 +189,26 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase private bool CanOpenWorktree() => WorktreePath != null; + [RelayCommand(CanExecute = nameof(CanOpenWorktree))] + private void OpenInExplorer() + { + if (WorktreePath == null) return; + try + { + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo + { + FileName = WorktreePath, + UseShellExecute = true, + }); + } + catch { /* explorer open is best-effort */ } + } + partial void OnWorktreePathChanged(string? value) { OpenDiffCommand.NotifyCanExecuteChanged(); OpenWorktreeCommand.NotifyCanExecuteChanged(); + OpenInExplorerCommand.NotifyCanExecuteChanged(); } [RelayCommand] @@ -212,6 +231,11 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase { if (Task == null) return; var row = Task; + if (ConfirmAsync != null) + { + 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); diff --git a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs index 4fb4cd7..25b7e8d 100644 --- a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs @@ -42,6 +42,12 @@ public sealed partial class IslandsShellViewModel : ViewModelBase Lists = lists; Tasks = tasks; Details = details; Lists.SelectionChanged += (_, _) => Tasks.LoadForList(Lists.SelectedList); Tasks.SelectionChanged += (_, _) => Details.Bind(Tasks.SelectedTask); + Details.CloseDetail = () => Tasks.SelectedTask = null; + Details.DeleteFromList = _ => + { + Tasks.LoadForList(Lists.SelectedList); + return System.Threading.Tasks.Task.CompletedTask; + }; _ = Lists.LoadAsync(); } } diff --git a/src/ClaudeDo.Ui/Views/Islands/AgentStripView.axaml b/src/ClaudeDo.Ui/Views/Islands/AgentStripView.axaml index 8847643..439613e 100644 --- a/src/ClaudeDo.Ui/Views/Islands/AgentStripView.axaml +++ b/src/ClaudeDo.Ui/Views/Islands/AgentStripView.axaml @@ -124,6 +124,10 @@ diff --git a/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml.cs b/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml.cs index 81935cd..d359015 100644 --- a/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml.cs +++ b/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml.cs @@ -1,5 +1,8 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Media; using ClaudeDo.Ui.ViewModels.Islands; using ClaudeDo.Ui.Views.Modals; @@ -32,9 +35,56 @@ public partial class DetailsIslandView : UserControl var modal = new WorktreeModalView { DataContext = worktreeVm }; await modal.ShowDialog(owner); }; + + vm.ConfirmAsync = ShowConfirmAsync; } } + private async System.Threading.Tasks.Task ShowConfirmAsync(string message) + { + var owner = TopLevel.GetTopLevel(this) as Window; + if (owner == null) return false; + + var tcs = new TaskCompletionSource(); + + var cancel = new Button { Content = "Cancel", MinWidth = 90 }; + var confirm = new Button { Content = "Delete", MinWidth = 90, Classes = { "danger" } }; + + var dialog = new Window + { + Title = "Confirm", + 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 = { cancel, confirm } + } + } + } + }; + + cancel.Click += (_, _) => { tcs.TrySetResult(false); dialog.Close(); }; + confirm.Click += (_, _) => { tcs.TrySetResult(true); dialog.Close(); }; + dialog.Closed += (_, _) => tcs.TrySetResult(false); + + _ = dialog.ShowDialog(owner); + return await tcs.Task; + } + private void NotesLostFocus(object? sender, RoutedEventArgs e) { if (DataContext is DetailsIslandViewModel vm) diff --git a/src/ClaudeDo.Ui/Views/MainWindow.axaml b/src/ClaudeDo.Ui/Views/MainWindow.axaml index e110114..470e225 100644 --- a/src/ClaudeDo.Ui/Views/MainWindow.axaml +++ b/src/ClaudeDo.Ui/Views/MainWindow.axaml @@ -46,7 +46,7 @@ Foreground="{DynamicResource TextFaintBrush}" VerticalAlignment="Center"/> -