fix(ui): surface worker connect/restart failures instead of no-opting

WorkerConnectionModalViewModel's StartWorker/RerunInstaller swallowed
missing-exe and Process.Start failures behind an empty catch and closed
the dialog anyway, looking like success. Now both show an inline error
and keep the dialog open; only a real launch closes it.

IslandsShellViewModel.RestartWorkerAsync never checked whether the
relaunched worker actually stayed up, and its status property was never
bound in any view. RestartWorkerService now fails if the new process
exits within a short survival window, and the outcome routes through
the footer error/success strip instead of the dead RestartWorkerStatus.

InstallArtifactLocator.Find() is now virtual and its two subclasses
unsealed so tests can fake exe resolution without touching the real
filesystem/registry.
This commit is contained in:
mika kuns
2026-09-10 13:34:32 +02:00
parent 46a0a27620
commit b4b6225432
8 changed files with 194 additions and 29 deletions
@@ -1,9 +1,10 @@
namespace ClaudeDo.Ui.Services;
// Two named types because DI resolves them by type, not by key.
public sealed class InstallerLocator() : InstallArtifactLocator("uninstaller", "ClaudeDo.Installer.exe");
// Two named types because DI resolves them by type, not by key. Not sealed: tests subclass
// these to fake Find() without touching the real filesystem/registry.
public class InstallerLocator() : InstallArtifactLocator("uninstaller", "ClaudeDo.Installer.exe");
public sealed class WorkerLocator() : InstallArtifactLocator("worker", "ClaudeDo.Worker.exe");
public class WorkerLocator() : InstallArtifactLocator("worker", "ClaudeDo.Worker.exe");
/// <summary>
/// Locates an executable inside a ClaudeDo install: walk up from the running
@@ -14,7 +15,7 @@ public abstract class InstallArtifactLocator(string subdir, string exeName)
{
private const string InstallJson = "install.json";
public string? Find()
public virtual string? Find()
=> FindByWalkingUp(AppContext.BaseDirectory)
?? (OperatingSystem.IsWindows() ? FindByRegistry() : null);