diff --git a/src/ClaudeDo.App/App.axaml.cs b/src/ClaudeDo.App/App.axaml.cs index 0aaead40..5d7c6733 100644 --- a/src/ClaudeDo.App/App.axaml.cs +++ b/src/ClaudeDo.App/App.axaml.cs @@ -3,7 +3,9 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; +using Avalonia.Threading; using ClaudeDo.Ui; +using ClaudeDo.Ui.Localization; using ClaudeDo.Ui.Services; using ClaudeDo.Ui.ViewModels; using ClaudeDo.Ui.Views; @@ -40,9 +42,22 @@ public partial class App : Application // modeless Mission Control window is still open. desktop.ShutdownMode = ShutdownMode.OnMainWindowClose; + var shell = services.GetRequiredService(); + + // Last-resort backstop: an exception escaping a dispatcher job kills the process, and + // ClaudeDo hosts third-party UI (the ConPTY terminal control) whose async void key and + // render handlers have done exactly that — one bad keystroke took the whole app down, + // losing every open session. Swallowing is the lesser evil here: the failing job is + // already dead either way, and the user still gets told via the footer error strip. + Dispatcher.UIThread.UnhandledException += (_, e) => + { + e.Handled = true; + shell.FlashFooterError(Loc.T("vm.shell.unexpectedError", e.Exception.Message)); + }; + desktop.MainWindow = new MainWindow { - DataContext = services.GetRequiredService(), + DataContext = shell, }; // Kick off the SignalR retry loop — reconnects indefinitely if the worker diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json index e025438e..8921d76e 100644 --- a/src/ClaudeDo.Localization/locales/de.json +++ b/src/ClaudeDo.Localization/locales/de.json @@ -642,7 +642,7 @@ }, "vm": { "connection": { "online": "Online", "connecting": "Verbinden…", "offline": "Offline" }, - "shell": { "restartingWorker": "Worker wird neu gestartet…" }, + "shell": { "restartingWorker": "Worker wird neu gestartet…", "unexpectedError": "Unerwarteter Fehler: {0}" }, "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" }, "planningBadge": { "active": "PLANUNG", "finalized": "GEPLANT" }, diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json index e8116ff6..372d2d9a 100644 --- a/src/ClaudeDo.Localization/locales/en.json +++ b/src/ClaudeDo.Localization/locales/en.json @@ -642,7 +642,7 @@ }, "vm": { "connection": { "online": "Online", "connecting": "Connecting…", "offline": "Offline" }, - "shell": { "restartingWorker": "Restarting worker…" }, + "shell": { "restartingWorker": "Restarting worker…", "unexpectedError": "Unexpected error: {0}" }, "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" }, "planningBadge": { "active": "PLANNING", "finalized": "PLANNED" },