From b1bd91292f413346facb29834e608711f1ef47d3 Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Thu, 25 Jun 2026 15:05:40 +0200 Subject: [PATCH] feat(ui): open Mission Control from the title bar --- src/ClaudeDo.App/App.axaml.cs | 5 +++++ src/ClaudeDo.App/Program.cs | 2 -- src/ClaudeDo.Ui/Design/IslandStyles.axaml | 2 ++ src/ClaudeDo.Ui/Services/IDialogService.cs | 4 ++++ src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs | 13 ++++++++++++- src/ClaudeDo.Ui/Views/MainWindow.axaml | 5 +++++ src/ClaudeDo.Ui/Views/MainWindow.axaml.cs | 6 ++++++ src/ClaudeDo.Ui/Views/WindowDialogService.cs | 10 ++++++++++ 8 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/ClaudeDo.App/App.axaml.cs b/src/ClaudeDo.App/App.axaml.cs index 09bc866..1c2317d 100644 --- a/src/ClaudeDo.App/App.axaml.cs +++ b/src/ClaudeDo.App/App.axaml.cs @@ -1,5 +1,6 @@ using System; using Avalonia; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using ClaudeDo.Ui.Services; @@ -32,6 +33,10 @@ public partial class App : Application FocusClearing.Install(); + // The main window is authoritative — closing it shuts the app down even if the + // modeless Mission Control window is still open. + desktop.ShutdownMode = ShutdownMode.OnMainWindowClose; + desktop.MainWindow = new MainWindow { DataContext = services.GetRequiredService(), diff --git a/src/ClaudeDo.App/Program.cs b/src/ClaudeDo.App/Program.cs index 7951f69..2575faa 100644 --- a/src/ClaudeDo.App/Program.cs +++ b/src/ClaudeDo.App/Program.cs @@ -168,8 +168,6 @@ sealed class Program shell.ConflictResolverFactory = sp.GetRequiredService>(); sp.GetRequiredService().Handler = shell.RequestConflictResolutionAsync; - var missionControl = sp.GetRequiredService(); - missionControl.OpenInApp = id => _ = shell.RevealTaskAsync(id); return shell; }); diff --git a/src/ClaudeDo.Ui/Design/IslandStyles.axaml b/src/ClaudeDo.Ui/Design/IslandStyles.axaml index dc8645f..6ef55cc 100644 --- a/src/ClaudeDo.Ui/Design/IslandStyles.axaml +++ b/src/ClaudeDo.Ui/Design/IslandStyles.axaml @@ -21,6 +21,8 @@ M4 9 H16 V11 H4 Z M4 4 H16 V6 H4 Z M4 14 H16 V16 H4 Z M4 4 H6 V16 H4 Z M14 4 H16 V16 H14 Z + + M3 3 H9 V9 H3 Z M11 3 H17 V9 H11 Z M3 11 H9 V17 H3 Z M11 11 H17 V17 H11 Z M4 7 H13 V9 H4 Z M4 14 H13 V16 H4 Z M4 7 H6 V16 H4 Z M11 7 H13 V16 H11 Z M7 4 H16 V6 H7 Z M14 4 H16 V13 H14 Z M4 5 L5 4 L16 15 L15 16 Z M15 4 L16 5 L5 16 L4 15 Z diff --git a/src/ClaudeDo.Ui/Services/IDialogService.cs b/src/ClaudeDo.Ui/Services/IDialogService.cs index 090fc09..69e3b54 100644 --- a/src/ClaudeDo.Ui/Services/IDialogService.cs +++ b/src/ClaudeDo.Ui/Services/IDialogService.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using ClaudeDo.Ui.ViewModels; using ClaudeDo.Ui.ViewModels.Conflicts; using ClaudeDo.Ui.ViewModels.Modals; @@ -28,4 +29,7 @@ public interface IDialogService /// Modal error notice with a single dismiss button. Task ShowErrorAsync(string message); + + /// Show (or re-show + focus) the modeless Mission Control window. Lazily created; hides on close. + void ShowMissionControl(MissionControlViewModel vm); } diff --git a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs index bd6342c..7cef067 100644 --- a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs @@ -20,6 +20,7 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable public TasksIslandViewModel? Tasks { get; } public DetailsIslandViewModel? Details { get; } public IWorkerClient? Worker { get; } + public MissionControlViewModel? MissionControl { get; } public UpdateCheckService UpdateCheck => _updateCheck; public string ConnectionText => @@ -206,9 +207,12 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable Func worktreesOverviewVmFactory, Func weeklyReportVmFactory, Func mergeVmFactory, - Func repoImportVmFactory) + Func repoImportVmFactory, + MissionControlViewModel missionControl) { Lists = lists; Tasks = tasks; Details = details; Worker = worker; + MissionControl = missionControl; + MissionControl.OpenInApp = id => _ = RevealTaskAsync(id); _updateCheck = updateCheck; _installerLocator = installerLocator; _workerLocator = workerLocator; @@ -312,6 +316,13 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable if (InlineUpdateStatus == text) InlineUpdateStatus = null; } + [RelayCommand] + private void OpenMissionControl() + { + if (Dialogs is not null && MissionControl is not null) + Dialogs.ShowMissionControl(MissionControl); + } + [RelayCommand] private async Task OpenAbout() { diff --git a/src/ClaudeDo.Ui/Views/MainWindow.axaml b/src/ClaudeDo.Ui/Views/MainWindow.axaml index 21c3728..b895b2a 100644 --- a/src/ClaudeDo.Ui/Views/MainWindow.axaml +++ b/src/ClaudeDo.Ui/Views/MainWindow.axaml @@ -88,6 +88,11 @@ + diff --git a/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs b/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs index d96f06a..6cf2380 100644 --- a/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs +++ b/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs @@ -42,6 +42,12 @@ public partial class MainWindow : Window if (DataContext is IslandsShellViewModel vm) { vm.Dialogs = new WindowDialogService(this); + vm.BringToFront = () => + { + if (WindowState == WindowState.Minimized) + WindowState = WindowState.Normal; + Activate(); + }; } } diff --git a/src/ClaudeDo.Ui/Views/WindowDialogService.cs b/src/ClaudeDo.Ui/Views/WindowDialogService.cs index 497009d..83b95ec 100644 --- a/src/ClaudeDo.Ui/Views/WindowDialogService.cs +++ b/src/ClaudeDo.Ui/Views/WindowDialogService.cs @@ -8,6 +8,7 @@ using ClaudeDo.Ui.ViewModels; using ClaudeDo.Ui.ViewModels.Conflicts; using ClaudeDo.Ui.ViewModels.Modals; using ClaudeDo.Ui.Views.Conflicts; +using ClaudeDo.Ui.Views.MissionControl; using ClaudeDo.Ui.Views.Modals; namespace ClaudeDo.Ui.Views; @@ -22,6 +23,7 @@ namespace ClaudeDo.Ui.Views; public sealed class WindowDialogService : IDialogService { private readonly Window _owner; + private MissionControlWindow? _missionControl; public WindowDialogService(Window owner) => _owner = owner; @@ -111,6 +113,14 @@ public sealed class WindowDialogService : IDialogService await dlg.ShowDialog(_owner); } + public void ShowMissionControl(MissionControlViewModel vm) + { + _missionControl ??= new MissionControlWindow { DataContext = vm }; + if (!_missionControl.IsVisible) + _missionControl.Show(); // modeless, independent top-level window + _missionControl.Activate(); // bring to front / focus + } + public Task ConfirmAsync(string message) { var tcs = new TaskCompletionSource();