feat(ui): stream claude update progress into the footer strip

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-10-01 10:24:07 +02:00
co-authored by Claude Sonnet 5.5
parent 3dc735b2af
commit 2b19c8c49d
3 changed files with 21 additions and 5 deletions
+1 -1
View File
@@ -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" },
+1 -1
View File
@@ -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" },
@@ -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<string> 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(); }