Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/spec/SPEC-007-guided-onboarding.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ existing-install re-entry paths.

### Step 5: Final Validation

- run config and health validation
- automatically run config and health validation when the step is reached
- show summary with remediation guidance on failure
- output next-step commands (`netclaw chat`, `netclaw config`)

Expand Down
35 changes: 33 additions & 2 deletions src/Netclaw.Cli.Tests/Tui/InitWizardPageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Extensions.DependencyInjection;
using Netclaw.Cli.Provider;
using Netclaw.Cli.Tui;
using Netclaw.Cli.Tui.Wizard;
using Netclaw.Cli.Tui.Wizard.Steps;
using Netclaw.Configuration;
using Netclaw.Providers;
Expand Down Expand Up @@ -197,6 +198,34 @@ public async Task TeamPosture_DefaultFeatures_AllEnabledInWrittenConfig()
}
}

[Fact]
public async Task EnteringHealthCheckStep_StartsValidationWithoutSecondEnter()
{
var vm = CreateViewModel();
try
{
AdvanceToStep(vm, WizardStepIds.SecurityPosture);
var postureStep = Assert.IsType<SecurityPostureStepViewModel>(vm.Orchestrator.CurrentStep);
postureStep.SelectedPosture = DeploymentPosture.Personal;

vm.GoNext();

Assert.Equal(WizardStepIds.HealthCheck, vm.Orchestrator.CurrentStep?.StepId);
var completion = vm.HealthCheckStep.HealthCheckCompletion;
Assert.NotNull(completion);

await completion!.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);

Assert.True(vm.HealthCheckStep.IsComplete.Value);
Assert.False(vm.HealthCheckStep.IsRunning.Value);
Assert.Contains(vm.HealthCheckStep.Results, r => r.Label == "Configuration written" && r.Passed == true);
}
finally
{
vm.Dispose();
}
}

// ── Helpers ──────────────────────────────────────────────────────────────

/// <summary>
Expand Down Expand Up @@ -230,8 +259,7 @@ private static void AdvanceToStep(InitWizardViewModel vm, string stepId)
_ => new InitWizardPage(),
_ =>
{
capturedVm = new InitWizardViewModel(
_paths, _registry, _fakeProbe, _fakeSlackProbe, _fakeDiscordProbe);
capturedVm = CreateViewModel();
return capturedVm;
});
});
Expand All @@ -243,4 +271,7 @@ private static void AdvanceToStep(InitWizardViewModel vm, string stepId)

return (terminal, app, capturedVm!);
}

private InitWizardViewModel CreateViewModel()
=> new(_paths, _registry, _fakeProbe, _fakeSlackProbe, _fakeDiscordProbe);
}
14 changes: 11 additions & 3 deletions src/Netclaw.Cli/Tui/InitWizardViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,17 +158,25 @@ public void GoNext()
{
if (_orchestrator.CurrentStep is HealthCheckStepViewModel healthStep)
{
if (!healthStep.IsRunning.Value && !healthStep.IsComplete.Value)
healthStep.StartWithOrchestrator(_orchestrator);
StartHealthCheckIfReady(healthStep);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just automatically run the health checks

return;
}

_orchestrator.GoNext();
var advanced = _orchestrator.GoNext();
_context.StatusMessage.Value = "";
if (advanced && _orchestrator.CurrentStep is HealthCheckStepViewModel enteredHealthStep)
StartHealthCheckIfReady(enteredHealthStep);

OnStepContentChanged?.Invoke();
RequestRedraw();
}

private void StartHealthCheckIfReady(HealthCheckStepViewModel healthStep)
{
if (!healthStep.IsRunning.Value && !healthStep.IsComplete.Value)
healthStep.StartWithOrchestrator(_orchestrator);
}

/// <summary>
/// Go back in the wizard. Returns false if at the beginning (caller should quit).
/// </summary>
Expand Down
14 changes: 11 additions & 3 deletions src/Netclaw.Cli/Tui/Wizard/Steps/HealthCheckStepView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// </copyright>
// -----------------------------------------------------------------------
using R3;
using Netclaw.Cli.Tui;
using Termina.Input;
using Termina.Layout;
using Termina.Reactive;
Expand All @@ -29,17 +30,24 @@ public ILayoutNode BuildContent(IWizardStepViewModel stepVm, StepViewCallbacks c

foreach (var item in items)
{
var (icon, color) = item.Passed switch
if (item.Passed is null)
{
lines.Add(SpinnerViews.Labeled(item.Label, Color.Yellow));

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add spinners to each of the health check stages

continue;
}

var (icon, color) = item.Passed.Value switch
{
true => ("\u2713", Color.Green),
false => ("\u2717", Color.Red),
null => ("\u25cf", Color.Yellow)
};
lines.Add(new TextNode($" {icon} {item.Label}").WithForeground(color));
}

if (lines.Count == 0)
lines.Add(new TextNode(" Press Enter to run health checks...").WithForeground(Color.BrightBlack));
lines.Add(vm.IsRunning.Value
? SpinnerViews.Labeled("Starting health checks...", Color.Yellow)
: new TextNode(" Health checks start automatically...").WithForeground(Color.BrightBlack));

// Post-flight summary: once the checks finish, nudge toward the bootstrap-vs-config
// split so the operator knows where ongoing settings live (simplify-netclaw-init §6).
Expand Down
6 changes: 2 additions & 4 deletions tests/smoke/tapes/init-wizard.tape
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,8 @@ Wait+Screen@10s /Who will interact with this Netclaw instance/
Enter

# ─── Step 4 of 4: Health Check ──────────────────────────────────────
Wait+Screen@10s /Press Enter to run health checks/
Enter

# Health checks contact ollama, write config, validate, and start the daemon.
# Health checks start automatically when the final step opens. No extra Enter.
# They contact ollama, write config, validate, and start the daemon.
# On a clean bootstrap, once validation passes the wizard launches chat
# automatically — there is no Enter gate / post-flight summary to confirm.
# Anchor on a health item that renders during the run (it stays on screen
Expand Down
Loading