From 5bc345b1bd224c583ef6802b6f51f7defa0fe43a Mon Sep 17 00:00:00 2001 From: sysadmin Date: Wed, 10 Jun 2026 09:19:11 +0100 Subject: [PATCH] feat(toolbox): first-run auto-applies the collected preconfig (no manual walkthrough) --- .../Components/Routes.razor | 9 ++++++- .../Components/Steps/ApplyStep.razor | 14 ++++++++++ .../SilverOS.Welcome.Tests/ApplyStepTests.cs | 27 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/windows/welcome/src/SilverOS.Welcome.UI/Components/Routes.razor b/windows/welcome/src/SilverOS.Welcome.UI/Components/Routes.razor index a5ebfe6..25efb8d 100644 --- a/windows/welcome/src/SilverOS.Welcome.UI/Components/Routes.razor +++ b/windows/welcome/src/SilverOS.Welcome.UI/Components/Routes.razor @@ -60,7 +60,7 @@ else break; case 4: - + break; case 5: @@ -104,6 +104,7 @@ else private bool _loading = true; private bool _applyRunning = false; private bool _toolboxHome = false; + private bool _autoApply = false; private string? _error; private IReadOnlyList _flavours = Array.Empty(); @@ -167,6 +168,12 @@ else if (pre.Bitlocker.Enable && !string.IsNullOrEmpty(pre.Bitlocker.Pin)) State.BitLockerPin = pre.Bitlocker.Pin; + + // First run with a preconfig: skip the manual walkthrough. Jump straight to the + // Apply step and signal it to auto-start (spec §4d: auto-runs once, shows progress + // + recovery key, then Done). + _currentStep = 4; + _autoApply = true; } private void ReRunSetup() diff --git a/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/ApplyStep.razor b/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/ApplyStep.razor index 323ec3b..aae2437 100644 --- a/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/ApplyStep.razor +++ b/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/ApplyStep.razor @@ -50,8 +50,10 @@ @code { [Parameter] public EventCallback OnComplete { get; set; } [Parameter] public EventCallback OnRunningChanged { get; set; } + [Parameter] public bool AutoStart { get; set; } private bool _running; + private bool _autoStarted; private bool _complete; private int _percent; private string _stageLabel = "Preparing…"; @@ -71,6 +73,18 @@ : single[..ErrorDisplayMaxLength] + "…"; } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + // First-run auto-apply: when the host jumps straight to this step with AutoStart, + // kick off the same apply the Start button would, exactly once. The manual path + // (AutoStart=false) is untouched. + if (firstRender && AutoStart && !_autoStarted) + { + _autoStarted = true; + await StartAsync(); + } + } + public async Task StartAsync() { // Re-entrancy guard: prevent a second overlapping apply if already running diff --git a/windows/welcome/tests/SilverOS.Welcome.Tests/ApplyStepTests.cs b/windows/welcome/tests/SilverOS.Welcome.Tests/ApplyStepTests.cs index 755f1ea..1b95fdd 100644 --- a/windows/welcome/tests/SilverOS.Welcome.Tests/ApplyStepTests.cs +++ b/windows/welcome/tests/SilverOS.Welcome.Tests/ApplyStepTests.cs @@ -73,6 +73,33 @@ public class ApplyStepTests : TestContext Assert.True(completed); } + [Fact] + public void AutoStart_triggers_apply_once_without_a_button_click() + { + var apply = new Mock(); + apply.Setup(a => a.RunAsync(It.IsAny(), It.IsAny>(), It.IsAny())) + .Returns(Task.CompletedTask); + var state = new WizardState + { + Flavour = new FlavourManifest { Id = "daily-driver", Hardening = new() { Modules = new[] { "00" } } }, + BitLockerPin = "123456" + }; + Services.AddSingleton(state); + Services.AddSingleton(apply.Object); + AddCatalog(Services); + AddPreconfig(Services); + + // AutoStart=true should fire StartAsync from OnAfterRenderAsync on first render, + // with no Start button click. + var cut = RenderComponent(p => p.Add(s => s.AutoStart, true)); + + cut.WaitForAssertion(() => + apply.Verify(a => a.RunAsync( + It.Is(r => r.Flavour.Id == "daily-driver"), + It.IsAny>(), + It.IsAny()), Times.Once)); + } + [Fact] public async Task Shows_error_and_retry_button_when_apply_fails() {