feat(toolbox): first-run auto-applies the collected preconfig (no manual walkthrough)

This commit is contained in:
sysadmin
2026-06-10 09:19:11 +01:00
parent e88e476cd6
commit 5bc345b1bd
3 changed files with 49 additions and 1 deletions

View File

@@ -60,7 +60,7 @@ else
<PrefsStep />
break;
case 4:
<ApplyStep OnComplete="AdvanceToDone" OnRunningChanged="@(v => { _applyRunning = v; StateHasChanged(); })" />
<ApplyStep AutoStart="_autoApply" OnComplete="AdvanceToDone" OnRunningChanged="@(v => { _applyRunning = v; StateHasChanged(); })" />
break;
case 5:
<DoneStep />
@@ -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<FlavourManifest> _flavours = Array.Empty<FlavourManifest>();
@@ -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()

View File

@@ -50,8 +50,10 @@
@code {
[Parameter] public EventCallback OnComplete { get; set; }
[Parameter] public EventCallback<bool> 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

View File

@@ -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<IApplyService>();
apply.Setup(a => a.RunAsync(It.IsAny<ApplyRequest>(), It.IsAny<IProgress<ApplyProgress>>(), It.IsAny<CancellationToken>()))
.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<ApplyStep>(p => p.Add(s => s.AutoStart, true));
cut.WaitForAssertion(() =>
apply.Verify(a => a.RunAsync(
It.Is<ApplyRequest>(r => r.Flavour.Id == "daily-driver"),
It.IsAny<IProgress<ApplyProgress>>(),
It.IsAny<CancellationToken>()), Times.Once));
}
[Fact]
public async Task Shows_error_and_retry_button_when_apply_fails()
{