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
@@ -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(); }