From 5cc1ec98c046591a4134f91ba995d1a01fdbc106 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Wed, 5 Aug 2026 20:09:52 +0200 Subject: [PATCH] feat(installer): add SystemCheckPage with blocking-error gating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new wizard page (positioned right after Welcome, FreshInstall only) that auto-runs EnvironmentCheckService on entry and shows one row per check with status icon, localized title/message, and hint+help-link on failure/unknown. A "Recheck" button re-runs it, guarded against re-entrancy. IInstallerPage gets a BlocksNavigation default member; WizardViewModel's Next button now binds to CanGoNext, which the current page can veto (used here while a check run is in flight or a blocking Error+Failed result is present — Warnings and Unknown results never block). The summary line names the blocking checks so a disabled Next is self-explanatory. Wires up DI for the check pipeline (IProcessRunner, IPortOwnerResolver, per-run ClaudeCliLookup) and adds the checks.* / installer.systemCheck.* locale keys in en.json + de.json. Visual appearance is NOT verified — needs a manual pass in the running installer. --- src/ClaudeDo.Installer/App.xaml.cs | 26 +++ .../Core/Interfaces/IInstallerPage.cs | 3 + .../Pages/PathsPage/PathsPageViewModel.cs | 2 +- .../Pages/ServicePage/ServicePageViewModel.cs | 2 +- .../SystemCheckPage/SystemCheckPageView.xaml | 99 ++++++++++ .../SystemCheckPageView.xaml.cs | 8 + .../SystemCheckPageViewModel.cs | 160 ++++++++++++++++ .../UiSettingsPage/UiSettingsPageViewModel.cs | 2 +- .../Views/WizardViewModel.cs | 11 ++ .../Views/WizardWindow.xaml | 1 + src/ClaudeDo.Localization/locales/de.json | 45 +++++ src/ClaudeDo.Localization/locales/en.json | 45 +++++ .../ClaudeDo.Installer.Tests/FakeLocalizer.cs | 13 ++ .../PageResolverTests.cs | 43 +++++ .../SystemCheckPage/FakeEnvironmentCheck.cs | 35 ++++ .../SystemCheckPageViewModelTests.cs | 172 ++++++++++++++++++ .../WizardViewModelTests.cs | 53 ++++++ 17 files changed, 717 insertions(+), 3 deletions(-) create mode 100644 src/ClaudeDo.Installer/Pages/SystemCheckPage/SystemCheckPageView.xaml create mode 100644 src/ClaudeDo.Installer/Pages/SystemCheckPage/SystemCheckPageView.xaml.cs create mode 100644 src/ClaudeDo.Installer/Pages/SystemCheckPage/SystemCheckPageViewModel.cs create mode 100644 tests/ClaudeDo.Installer.Tests/FakeLocalizer.cs create mode 100644 tests/ClaudeDo.Installer.Tests/PageResolverTests.cs create mode 100644 tests/ClaudeDo.Installer.Tests/Pages/SystemCheckPage/FakeEnvironmentCheck.cs create mode 100644 tests/ClaudeDo.Installer.Tests/Pages/SystemCheckPage/SystemCheckPageViewModelTests.cs create mode 100644 tests/ClaudeDo.Installer.Tests/WizardViewModelTests.cs diff --git a/src/ClaudeDo.Installer/App.xaml.cs b/src/ClaudeDo.Installer/App.xaml.cs index 89207019..ef35f7da 100644 --- a/src/ClaudeDo.Installer/App.xaml.cs +++ b/src/ClaudeDo.Installer/App.xaml.cs @@ -4,13 +4,17 @@ using System.Linq; using System.Net.Http; using System.Reflection; using System.Windows; +using ClaudeDo.Installer.Checks; +using ClaudeDo.Installer.Checks.Interfaces; using ClaudeDo.Installer.Core; +using ClaudeDo.Installer.Core.Interfaces; using ClaudeDo.Installer.Localization; using ClaudeDo.Localization; using ClaudeDo.Releases; using ClaudeDo.Installer.Pages.InstallPage; using ClaudeDo.Installer.Pages.PathsPage; using ClaudeDo.Installer.Pages.ServicePage; +using ClaudeDo.Installer.Pages.SystemCheckPage; using ClaudeDo.Installer.Pages.UiSettingsPage; using ClaudeDo.Installer.Pages.WelcomePage; using ClaudeDo.Installer.Steps; @@ -121,8 +125,30 @@ public partial class App : Application sc.AddSingleton(sp => new ReleaseClient(sp.GetRequiredService())); sc.AddSingleton(); + // Environment checks — stateless, so their infrastructure is shared; ClaudeCliLookup is + // rebuilt per EnvironmentCheckService instance so a re-check doesn't reuse a stale result. + sc.AddSingleton(); + sc.AddSingleton(); + sc.AddTransient>(sp => () => + { + var processRunner = sp.GetRequiredService(); + var claudeLookup = new ClaudeCliLookup(processRunner); + return new EnvironmentCheckService(new IEnvironmentCheck[] + { + new GitCheck(processRunner), + new GitIdentityCheck(processRunner), + new PortCheck(sp.GetRequiredService()), + new WriteAccessCheck(), + new ClaudeCliCheck(claudeLookup), + new ClaudeVersionCheck(claudeLookup), + new ClaudeAuthCheck(claudeLookup), + new PermissionModeAutoCheck(claudeLookup), + }); + }); + // Pages sc.AddSingleton(); + sc.AddSingleton(); sc.AddSingleton(); sc.AddSingleton(); sc.AddSingleton(); diff --git a/src/ClaudeDo.Installer/Core/Interfaces/IInstallerPage.cs b/src/ClaudeDo.Installer/Core/Interfaces/IInstallerPage.cs index ef4db1c9..bd9b6799 100644 --- a/src/ClaudeDo.Installer/Core/Interfaces/IInstallerPage.cs +++ b/src/ClaudeDo.Installer/Core/Interfaces/IInstallerPage.cs @@ -13,4 +13,7 @@ public interface IInstallerPage Task LoadAsync(); Task ApplyAsync(); bool Validate(); + + /// True while this page wants "Next" disabled (e.g. a check run in progress or a blocking error). + bool BlocksNavigation => false; } diff --git a/src/ClaudeDo.Installer/Pages/PathsPage/PathsPageViewModel.cs b/src/ClaudeDo.Installer/Pages/PathsPage/PathsPageViewModel.cs index cd6d7d12..16b64e8e 100644 --- a/src/ClaudeDo.Installer/Pages/PathsPage/PathsPageViewModel.cs +++ b/src/ClaudeDo.Installer/Pages/PathsPage/PathsPageViewModel.cs @@ -12,7 +12,7 @@ public partial class PathsPageViewModel : ObservableObject, IInstallerPage public string Title => TrExtension.Localizer?["installer.paths.title"] ?? "Paths"; public string Icon => "\uE8B7"; - public int Order => 1; + public int Order => 2; public bool ShowInWizard => true; public bool ShowInSettings => true; public UserControl View => _view ??= new PathsPageView { DataContext = this }; diff --git a/src/ClaudeDo.Installer/Pages/ServicePage/ServicePageViewModel.cs b/src/ClaudeDo.Installer/Pages/ServicePage/ServicePageViewModel.cs index 437e93ac..76cc099e 100644 --- a/src/ClaudeDo.Installer/Pages/ServicePage/ServicePageViewModel.cs +++ b/src/ClaudeDo.Installer/Pages/ServicePage/ServicePageViewModel.cs @@ -14,7 +14,7 @@ public partial class ServicePageViewModel : ObservableObject, IInstallerPage public string Title => TrExtension.Localizer?["installer.service.title"] ?? "Service"; public string Icon => "\uE912"; - public int Order => 2; + public int Order => 3; public bool ShowInWizard => true; public bool ShowInSettings => true; public UserControl View => _view ??= new ServicePageView { DataContext = this }; diff --git a/src/ClaudeDo.Installer/Pages/SystemCheckPage/SystemCheckPageView.xaml b/src/ClaudeDo.Installer/Pages/SystemCheckPage/SystemCheckPageView.xaml new file mode 100644 index 00000000..459163af --- /dev/null +++ b/src/ClaudeDo.Installer/Pages/SystemCheckPage/SystemCheckPageView.xaml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +