From e1bdb9a800f02dc7b78f16a2a9fdd6088ec43365 Mon Sep 17 00:00:00 2001 From: Nexus Dev Date: Thu, 2 Apr 2026 23:28:48 +0000 Subject: [PATCH] feat(30-02): wire multi-step wizard in NexusOnboardingWizard - Refactor to 3-step flow: hardware detection, mode selection, root directory - Add step indicator 'Step N of 3' - Add HardwareSummaryStep on step 1 with dynamic heading - Add ModeSelector on step 2 with 'both' pre-selected - Add Back buttons on steps 2 and 3 - Persist selected mode via updateNexusSettings on wizard completion - Reset step and mode on wizard close --- ui/src/components/NexusOnboardingWizard.tsx | 238 ++++++++++++++------ 1 file changed, 167 insertions(+), 71 deletions(-) diff --git a/ui/src/components/NexusOnboardingWizard.tsx b/ui/src/components/NexusOnboardingWizard.tsx index 8a45ed76..ce6cabdd 100644 --- a/ui/src/components/NexusOnboardingWizard.tsx +++ b/ui/src/components/NexusOnboardingWizard.tsx @@ -1,4 +1,4 @@ -// [nexus] Replacement onboarding wizard — single-step root directory flow +// [nexus] Replacement onboarding wizard — 3-step flow: hardware detection, mode selection, root directory // Exports `OnboardingWizard` to match the named import in App.tsx. // Wired via Vite alias: all imports of ./components/OnboardingWizard are // redirected here at build time; the original file is preserved for upstream rebase. @@ -17,8 +17,12 @@ import { Dialog, DialogPortal } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { cn } from "../lib/utils"; +import { ModeSelector } from "./onboarding/ModeSelector"; +import { HardwareSummaryStep } from "./onboarding/HardwareSummaryStep"; +import { useHardwareInfo } from "../hooks/useHardwareInfo"; +import { updateNexusSettings, type NexusMode } from "../api/hardware"; -// [nexus] Single-step onboarding wizard: root directory → workspace + PM + Engineer +// [nexus] 3-step onboarding wizard: hardware detection → mode selection → root directory export function OnboardingWizard() { const { onboardingOpen, onboardingOptions, closeOnboarding } = useDialog(); const { companies, setSelectedCompanyId, loading: companiesLoading } = useCompany(); @@ -45,11 +49,20 @@ export function OnboardingWizard() { setRouteDismissed(false); }, [location.pathname]); + // Step state: 1 = hardware detection, 2 = mode selection, 3 = root directory + const [step, setStep] = useState(1); + + // Mode state: "both" pre-selected per UI-SPEC + const [selectedMode, setSelectedMode] = useState("both"); + // Form state const [rootDir, setRootDir] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + // [nexus] Hardware detection — only fetch when wizard is open + const { data: hardwareInfo, isLoading: hwLoading, isError: hwError } = useHardwareInfo(effectiveOnboardingOpen); + // [nexus] Adapter detection state — probe for Hermes when wizard opens const [defaultAdapter, setDefaultAdapter] = useState<"claude_local" | "hermes_local">("claude_local"); const [probing, setProbing] = useState(false); @@ -60,6 +73,8 @@ export function OnboardingWizard() { setRootDir(""); setError(null); setLoading(false); + setStep(1); + setSelectedMode("both"); } }, [effectiveOnboardingOpen]); @@ -161,6 +176,13 @@ export function OnboardingWizard() { queryKey: queryKeys.agents.list(company.id), }); + // Persist selected mode — non-blocking + try { + await updateNexusSettings({ mode: selectedMode }); + } catch { + // Non-blocking — mode defaults to "both" if save fails + } + // Navigate to dashboard — not an issue detail page closeOnboarding(); navigate(`/${company.issuePrefix}/dashboard`); @@ -188,80 +210,154 @@ export function OnboardingWizard() { "p-8 flex flex-col gap-6" )} > - {/* Header */} -
-

- Welcome to {VOCAB.appName} -

-

- {defaultAdapter === "hermes_local" - ? `${VOCAB.appName} will set up a local AI workspace with a ${VOCAB.ceo.toLowerCase()}, engineer, and generalist — no API key needed.` - : `Choose a project root directory. ${VOCAB.appName} will set up a ${VOCAB.ceo.toLowerCase()} and engineer to start working.`} -

-
+ {/* Step indicator */} +

Step {step} of 3

- {/* Form */} -
-
- - setRootDir(e.target.value)} - disabled={loading} - autoFocus - autoComplete="off" - className="font-mono text-sm" + {/* Step 1 — Hardware Detection */} + {step === 1 && ( + <> +
+

+ {hwLoading ? "Detecting your hardware..." : "Your hardware"} +

+
+ + -
- {error && ( -

- {error} -

- )} + + + )} - + + + + )} + + {/* Step 3 — Root Directory */} + {step === 3 && ( + <> + {/* Header */} +
+

+ Welcome to {VOCAB.appName} +

+

+ {defaultAdapter === "hermes_local" + ? `${VOCAB.appName} will set up a local AI workspace with a ${VOCAB.ceo.toLowerCase()}, engineer, and generalist — no API key needed.` + : `Choose a project root directory. ${VOCAB.appName} will set up a ${VOCAB.ceo.toLowerCase()} and engineer to start working.`} +

+
+ + {/* Form */} + +
+ + setRootDir(e.target.value)} + disabled={loading} + autoFocus + autoComplete="off" + className="font-mono text-sm" + /> +
+ + {error && ( +

+ {error} +

+ )} + + + + + + + )}