feat(claude-do): merge feat(ui): Hauptfenster-Close mit offenen Mission-Control-Ses
ClaudeDo-Task: a963f289b38449a3a8d0c52fc9bfd9e7
This commit is contained in:
@@ -714,6 +714,7 @@
|
||||
"vm": {
|
||||
"connection": { "online": "Online", "connecting": "Verbinden…", "offline": "Offline" },
|
||||
"shell": { "restartingWorker": "Worker wird neu gestartet…", "unexpectedError": "Unerwarteter Fehler: {0}" },
|
||||
"mainWindow": { "closeConfirm": { "message": "{0} Mission-Control-Sitzung(en) laufen noch. Beim Beenden werden sie abgebrochen. Trotzdem schließen?" } },
|
||||
"agentStatus": { "idle": "Leerlauf", "queued": "In Warteschlange", "running": "Läuft", "review": "Prüfung", "children": "Wartet auf Teilaufgaben", "done": "Fertig", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen" },
|
||||
"taskStatus": { "idle": "Leerlauf", "queued": "In Warteschlange", "running": "Läuft", "waitingForReview": "Wartet auf Prüfung", "waitingForChildren": "Wartet auf Teilaufgaben", "done": "Fertig", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen", "parked": "Geparkt", "interactive": "Interaktiv" },
|
||||
"failureReason": { "maxTurns": "Turn-Limit erreicht", "timeout": "Zeitüberschreitung", "error": "Fehler", "cancelled": "Abgebrochen", "unknown": "Grund unbekannt" },
|
||||
|
||||
@@ -714,6 +714,7 @@
|
||||
"vm": {
|
||||
"connection": { "online": "Online", "connecting": "Connecting…", "offline": "Offline" },
|
||||
"shell": { "restartingWorker": "Restarting worker…", "unexpectedError": "Unexpected error: {0}" },
|
||||
"mainWindow": { "closeConfirm": { "message": "{0} Mission Control session(s) are still running. Closing the app will end them. Close anyway?" } },
|
||||
"agentStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "review": "Review", "children": "Waiting for Subtasks", "done": "Done", "failed": "Failed", "cancelled": "Cancelled" },
|
||||
"taskStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "waitingForReview": "Waiting for Review", "waitingForChildren": "Waiting for Subtasks", "done": "Done", "failed": "Failed", "cancelled": "Cancelled", "parked": "Parked", "interactive": "Interactive" },
|
||||
"failureReason": { "maxTurns": "Turn limit reached", "timeout": "Timed out", "error": "Error", "cancelled": "Cancelled", "unknown": "Unknown reason" },
|
||||
|
||||
@@ -520,6 +520,16 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
|
||||
Dialogs.ShowMissionControl(MissionControl);
|
||||
}
|
||||
|
||||
/// <summary>Number of Mission Control ConPTY panes currently open — the main window's
|
||||
/// close guard asks for confirmation whenever this is greater than zero.</summary>
|
||||
public int OpenMissionControlSessionCount => MissionControl?.ConPtySessions.Count ?? 0;
|
||||
|
||||
// Pure decision extracted for testability — closing the main window kills every open
|
||||
// Mission Control pane's `claude` process (ConPtyPaneViewModel.Dispose), so ask first
|
||||
// rather than let it happen silently.
|
||||
internal static bool RequiresCloseConfirmation(int openMissionControlSessionCount) =>
|
||||
openMissionControlSessionCount > 0;
|
||||
|
||||
private void SyncInteractiveSessionChips()
|
||||
{
|
||||
if (MissionControl is null || Tasks is null) return;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.ViewModels;
|
||||
using ClaudeDo.Ui.ViewModels.Islands;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
@@ -13,6 +16,8 @@ namespace ClaudeDo.Ui.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private bool _forceClose;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -21,6 +26,29 @@ public partial class MainWindow : Window
|
||||
UpdateMaxIcon();
|
||||
}
|
||||
|
||||
// Closing the main window kills every open Mission Control ConPTY session without
|
||||
// warning (App.axaml.cs: ShutdownMode.OnMainWindowClose). Ask first — never silently.
|
||||
protected override void OnClosing(WindowClosingEventArgs e)
|
||||
{
|
||||
if (!_forceClose
|
||||
&& DataContext is IslandsShellViewModel { Dialogs: { } dialogs } vm
|
||||
&& IslandsShellViewModel.RequiresCloseConfirmation(vm.OpenMissionControlSessionCount))
|
||||
{
|
||||
e.Cancel = true;
|
||||
_ = ConfirmCloseWithOpenSessionsAsync(dialogs, vm.OpenMissionControlSessionCount);
|
||||
return;
|
||||
}
|
||||
base.OnClosing(e);
|
||||
}
|
||||
|
||||
private async Task ConfirmCloseWithOpenSessionsAsync(IDialogService dialogs, int openSessionCount)
|
||||
{
|
||||
var confirmed = await dialogs.ConfirmAsync(Loc.T("vm.mainWindow.closeConfirm.message", openSessionCount));
|
||||
if (!confirmed) return;
|
||||
_forceClose = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using ClaudeDo.Ui.ViewModels;
|
||||
using Xunit;
|
||||
|
||||
namespace ClaudeDo.Ui.Tests;
|
||||
|
||||
public class MainWindowCloseConfirmDecisionTests
|
||||
{
|
||||
[Fact]
|
||||
public void True_when_at_least_one_mission_control_session_is_open()
|
||||
{
|
||||
Assert.True(IslandsShellViewModel.RequiresCloseConfirmation(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void True_when_multiple_mission_control_sessions_are_open()
|
||||
{
|
||||
Assert.True(IslandsShellViewModel.RequiresCloseConfirmation(3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void False_when_no_mission_control_sessions_are_open()
|
||||
{
|
||||
Assert.False(IslandsShellViewModel.RequiresCloseConfirmation(0));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user