From 01c29bb6f60263e5c07caf541980e803fc39e43f Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Wed, 15 Apr 2026 10:01:20 +0200 Subject: [PATCH] feat(installer): async mode detection + mode-aware DI wiring --- src/ClaudeDo.Installer/App.xaml.cs | 65 +++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/src/ClaudeDo.Installer/App.xaml.cs b/src/ClaudeDo.Installer/App.xaml.cs index 093f7af..10cb77c 100644 --- a/src/ClaudeDo.Installer/App.xaml.cs +++ b/src/ClaudeDo.Installer/App.xaml.cs @@ -1,3 +1,6 @@ +using System.IO; +using System.Net.Http; +using System.Reflection; using System.Windows; using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Pages.InstallPage; @@ -15,16 +18,45 @@ public partial class App : Application { private ServiceProvider? _services; - protected override void OnStartup(StartupEventArgs e) + protected override async void OnStartup(StartupEventArgs e) { base.OnStartup(e); _services = BuildServices(); - // TODO(Task 11): replace with async InstallModeDetector - Window mainWindow = new WizardWindow + var context = _services.GetRequiredService(); + context.InstallerVersion = GetInstallerVersion(); + + // Default install dir for detection — on upgrade we stay where we were. + var detector = _services.GetRequiredService(); + var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + DetectedState state; + try { - DataContext = _services.GetRequiredService() + state = await detector.DetectAsync(context.InstallDirectory, cts.Token); + } + catch (OperationCanceledException) + { + state = new DetectedState(InstallerMode.FreshInstall, null, null, null); + } + + context.Mode = state.Mode; + context.InstalledVersion = state.Existing?.Version; + context.LatestVersion = state.LatestVersion; + if (state.Existing is not null) + context.InstallDirectory = state.Existing.InstallDir; + + Window mainWindow = state.Mode switch + { + InstallerMode.FreshInstall or InstallerMode.Update => new WizardWindow + { + DataContext = _services.GetRequiredService() + }, + InstallerMode.Config => new SettingsWindow + { + DataContext = _services.GetRequiredService() + }, + _ => throw new InvalidOperationException($"Unknown installer mode: {state.Mode}") }; DarkTitleBar.Apply(mainWindow); @@ -37,6 +69,13 @@ public partial class App : Application base.OnExit(e); } + private static string GetInstallerVersion() + { + var infoAttr = Assembly.GetExecutingAssembly() + .GetCustomAttribute(); + return infoAttr?.InformationalVersion ?? "0.0.0"; + } + private static ServiceProvider BuildServices() { var sc = new ServiceCollection(); @@ -46,6 +85,11 @@ public partial class App : Application sc.AddSingleton(); sc.AddSingleton(); + // HTTP + release client + sc.AddSingleton(_ => new HttpClient { Timeout = TimeSpan.FromSeconds(15) }); + sc.AddSingleton(sp => new ReleaseClient(sp.GetRequiredService())); + sc.AddSingleton(); + // Pages sc.AddSingleton(); sc.AddSingleton(); @@ -53,11 +97,22 @@ public partial class App : Application sc.AddSingleton(); sc.AddSingleton(); - // Steps (registration order = execution order) + // Steps — execution order matters for the FreshInstall pipeline (IEnumerable). + // Double-registered as both IInstallStep and concrete type so Task 15's Update pipeline + // can pull them out individually via GetRequiredService(). + sc.AddSingleton(); + sc.AddSingleton(sp => sp.GetRequiredService()); sc.AddSingleton(); sc.AddSingleton(); sc.AddSingleton(); sc.AddSingleton(); + sc.AddSingleton(); + sc.AddSingleton(sp => sp.GetRequiredService()); + + // Stop/Start — NOT registered as IInstallStep (not part of default FreshInstall pipeline). + // Pulled by Update flow + Repair/Uninstall. + sc.AddSingleton(); + sc.AddSingleton(); // ViewModels sc.AddSingleton();