diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json index bd683e49..b8e5ce65 100644 --- a/src/ClaudeDo.Localization/locales/de.json +++ b/src/ClaudeDo.Localization/locales/de.json @@ -866,7 +866,7 @@ }, "vm": { "connection": { "online": "Online", "connecting": "Verbinden…", "offline": "Offline" }, - "shell": { "unexpectedError": "Unerwarteter Fehler: {0}", "shutdownConfirm": "Dadurch wird der ClaudeDo-Worker beendet (laufende Task-Runs und interaktive Sitzungen brechen ab) und die App geschlossen. Fortfahren?" }, + "shell": { "unexpectedError": "Unerwarteter Fehler: {0}", "updatingClaudeCli": "Claude CLI wird aktualisiert…", "shutdownConfirm": "Dadurch wird der ClaudeDo-Worker beendet (laufende Task-Runs und interaktive Sitzungen brechen ab) und die App geschlossen. Fortfahren?" }, "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", "waiting": "Wartet" }, diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json index db1f11e4..fd586aa7 100644 --- a/src/ClaudeDo.Localization/locales/en.json +++ b/src/ClaudeDo.Localization/locales/en.json @@ -866,7 +866,7 @@ }, "vm": { "connection": { "online": "Online", "connecting": "Connecting…", "offline": "Offline" }, - "shell": { "unexpectedError": "Unexpected error: {0}", "shutdownConfirm": "This stops the ClaudeDo worker (any running task or interactive session is interrupted) and closes the app. Continue?" }, + "shell": { "unexpectedError": "Unexpected error: {0}", "updatingClaudeCli": "Updating Claude CLI…", "shutdownConfirm": "This stops the ClaudeDo worker (any running task or interactive session is interrupted) and closes the app. Continue?" }, "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", "waiting": "Waiting" }, diff --git a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs index f1e58c0a..8536360a 100644 --- a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs @@ -206,6 +206,9 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable // paint a good outcome red, which reads as a failure. public void FlashFooterSuccess(string message) => FlashFooter(message, WorkerLogLevel.Success); + // Same strip, for neutral in-progress text (e.g. a CLI's progress lines). + public void FlashFooterInfo(string message) => FlashFooter(message, WorkerLogLevel.Info); + private void FlashFooter(string message, WorkerLogLevel level) { if (level == WorkerLogLevel.Error) @@ -749,8 +752,10 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable { _updatingClaudeCli = true; UpdateClaudeCliCommand.NotifyCanExecuteChanged(); + var finished = false; try { + FlashFooterInfo(Loc.T("vm.shell.updatingClaudeCli")); var dto = Worker is null ? null : await Worker.GetClaudeBinAsync(); if (dto is null) { @@ -758,18 +763,24 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable return; } - var (success, message) = await RunClaudeUpdateAsync(dto); + // `finished` is only touched on the UI thread: a queued line can't overwrite the final flash. + var (success, message) = await RunClaudeUpdateAsync(dto, line => Dispatcher.UIThread.Post(() => + { + if (!finished) FlashFooterInfo(line); + })); + finished = true; if (success) FlashFooterSuccess(message); else FlashFooterError(message); } finally { + finished = true; _updatingClaudeCli = false; UpdateClaudeCliCommand.NotifyCanExecuteChanged(); } } - private static async Task<(bool Success, string Message)> RunClaudeUpdateAsync(ClaudeBinDto dto) + private static async Task<(bool Success, string Message)> RunClaudeUpdateAsync(ClaudeBinDto dto, Action onStdoutLine) { var resolved = ClaudeDo.Data.Environment.ExecutableResolver.Resolve(dto.Value); if (resolved is null) return (false, $"Update failed: '{dto.Value}' not found."); @@ -785,7 +796,12 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable using var process = new System.Diagnostics.Process { StartInfo = psi }; var stdout = new System.Text.StringBuilder(); var stderr = new System.Text.StringBuilder(); - process.OutputDataReceived += (_, e) => { if (e.Data is not null) stdout.AppendLine(e.Data); }; + process.OutputDataReceived += (_, e) => + { + if (e.Data is null) return; + stdout.AppendLine(e.Data); + if (!string.IsNullOrWhiteSpace(e.Data)) onStdoutLine(e.Data.Trim()); + }; process.ErrorDataReceived += (_, e) => { if (e.Data is not null) stderr.AppendLine(e.Data); }; try { process.Start(); }