Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Binary file modified tests/smoke/screenshots/config-search-brave-entry.approved.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/smoke/screenshots/config-search-saved.approved.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/smoke/screenshots/config-search-selection.approved.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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