--- phase: 32-multi-step-onboarding-wizard plan: 01 type: execute wave: 1 depends_on: [] files_modified: - ui/src/components/onboarding/OnboardingSummaryStep.tsx - ui/src/components/NexusOnboardingWizard.tsx - ui/src/components/onboarding/OnboardingSummaryStep.test.tsx autonomous: true requirements: [ONBD-04, ONBD-05, ONBD-06] must_haves: truths: - "User can click Skip on step 1 (hardware) and advance to step 2" - "User can click Skip on step 2 (mode) and advance to step 3" - "User can click Skip to summary on step 4 (root dir) and advance to step 5" - "Step 3 (provider) already has skip — no change needed" - "User sees summary screen showing hardware tier, mode, provider, and root directory" - "Summary screen shows 'None selected' when provider was skipped" - "User clicks 'Start chatting' on summary and lands on dashboard with chat panel open" - "Step indicator shows 'Summary' on step 5 instead of 'Step 5 of 5'" artifacts: - path: "ui/src/components/onboarding/OnboardingSummaryStep.tsx" provides: "Read-only summary display with Start chatting CTA" exports: ["OnboardingSummaryStep"] - path: "ui/src/components/NexusOnboardingWizard.tsx" provides: "5-step wizard with skip buttons on steps 1, 2, 4 and summary as step 5" contains: "step === 5" - path: "ui/src/components/onboarding/OnboardingSummaryStep.test.tsx" provides: "Unit tests for summary step rendering and CTA behavior" key_links: - from: "ui/src/components/NexusOnboardingWizard.tsx" to: "ui/src/components/onboarding/OnboardingSummaryStep.tsx" via: "import and render at step === 5" pattern: "OnboardingSummaryStep" - from: "ui/src/components/NexusOnboardingWizard.tsx" to: "useChatPanel" via: "setChatOpen(true) after navigate in handleStartChat" pattern: "setChatOpen\\(true\\)" --- Complete the onboarding wizard by adding skip buttons to steps 1, 2, and 4, creating a summary screen (step 5) that displays all configured settings, and wiring a "Start chatting" CTA that creates the workspace and opens the chat panel. Purpose: Users can skip through onboarding quickly or review their choices before starting — fulfilling ONBD-04 (skip any step), ONBD-05 (summary screen), and ONBD-06 (one-click to chat). Output: OnboardingSummaryStep component, updated NexusOnboardingWizard with 5 steps and skip buttons, unit tests. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/32-multi-step-onboarding-wizard/32-RESEARCH.md From ui/src/api/hardware.ts: ```typescript export type HardwareTier = "gpu" | "apple_silicon" | "cpu_only"; export interface HardwareInfo { totalGb: number; freeGb: number; usableGb: number; platform: string; gpuName: string | null; gpuVramGb: number | null; unifiedMemory: boolean; hardwareTier: HardwareTier; cpuModel: string | null; } export type NexusMode = "personal_ai" | "project_builder" | "both"; ``` From ui/src/context/ChatPanelContext.tsx: ```typescript export function useChatPanel(): { chatOpen: boolean; setChatOpen: (open: boolean) => void; }; ``` From ui/src/components/NexusOnboardingWizard.tsx (existing state available to summary): ```typescript // These are the wizard state variables available to pass as props: const [selectedMode, setSelectedMode] = useState("both"); const [puterToken, setPuterToken] = useState(null); const [googleOAuthStateId, setGoogleOAuthStateId] = useState(null); const [apiKeyData, setApiKeyData] = useState<{ provider: string; apiKey: string } | null>(null); const [rootDir, setRootDir] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const { data: hardwareInfo } = useHardwareInfo(effectiveOnboardingOpen); ``` Task 1: Create OnboardingSummaryStep component and tests ui/src/components/onboarding/OnboardingSummaryStep.tsx, ui/src/components/onboarding/OnboardingSummaryStep.test.tsx - ui/src/components/onboarding/HardwareSummaryStep.tsx (existing step pattern, styling conventions) - ui/src/components/onboarding/ModeSelector.tsx (NexusMode usage, label patterns) - ui/src/api/hardware.ts (HardwareInfo and NexusMode types) Create `ui/src/components/onboarding/OnboardingSummaryStep.tsx`: 1. Define props interface: ```typescript interface OnboardingSummaryStepProps { hardwareInfo: HardwareInfo | undefined; selectedMode: NexusMode; providerLabel: string; rootDir: string; loading: boolean; error: string | null; onStartChat: () => void; onBack: () => void; } ``` 2. Implement a read-only summary with 4 rows in a bordered card (`rounded-lg border border-border p-4`): - **Hardware**: Show `hardwareInfo.hardwareTier` mapped to display labels ("GPU", "Apple Silicon", "CPU Only"), or "Unknown" if undefined - **Mode**: Map `NexusMode` to labels: `personal_ai` -> "Personal AI Assistant", `project_builder` -> "Project Builder", `both` -> "Both (recommended)" - **Provider**: Render the `providerLabel` string directly (caller computes it) - **Root directory**: Only show row if `rootDir` is non-empty, render value in `font-mono text-sm` 3. Create a helper `SummaryRow` component (internal, not exported) that renders a label-value pair: - Label in `text-muted-foreground` on the left - Value on the right, optionally mono (`font-mono text-sm` when `mono` prop is true) - Use flexbox `justify-between` 4. Show error message if `error` is non-null (`text-sm text-destructive bg-destructive/10 rounded-md px-3 py-2`) 5. "Start chatting" primary button — `onClick={onStartChat}`, `disabled={loading}`, shows "Setting up..." when loading (with the same spinner SVG used in the root dir step) 6. "Back" ghost button — `onClick={onBack}`, `disabled={loading}` 7. Do NOT include any corporate language ("company", "CEO", "mission") per ROADMAP success criteria. Create `ui/src/components/onboarding/OnboardingSummaryStep.test.tsx`: Using Vitest + React Testing Library (project standard): - Test 1: Renders all summary rows with provided data (hardware tier, mode label, provider label, root dir) - Test 2: Hides root directory row when rootDir is empty string - Test 3: Shows error message when error prop is non-null - Test 4: Calls onStartChat when "Start chatting" button clicked - Test 5: Disables "Start chatting" button when loading is true - Test 6: Shows "Setting up..." text when loading is true Mock data: `hardwareInfo` with `hardwareTier: "apple_silicon"`, `selectedMode: "both"`, `providerLabel: "Puter (free, zero-config)"`, `rootDir: "~/projects/test"`. cd /opt/nexus && pnpm vitest run ui/src/components/onboarding/OnboardingSummaryStep.test.tsx - grep -q "export function OnboardingSummaryStep" ui/src/components/onboarding/OnboardingSummaryStep.tsx - grep -q "Start chatting" ui/src/components/onboarding/OnboardingSummaryStep.tsx - grep -q "onStartChat" ui/src/components/onboarding/OnboardingSummaryStep.tsx - grep -q "SummaryRow" ui/src/components/onboarding/OnboardingSummaryStep.tsx - grep -q "describe" ui/src/components/onboarding/OnboardingSummaryStep.test.tsx OnboardingSummaryStep renders hardware, mode, provider, root dir in a read-only card with Start chatting CTA. All 6 tests pass. Task 2: Wire summary step into wizard, add skip buttons, connect chat handoff ui/src/components/NexusOnboardingWizard.tsx - ui/src/components/NexusOnboardingWizard.tsx (full file — the primary modification target) - ui/src/context/ChatPanelContext.tsx (useChatPanel hook export and setChatOpen signature) - ui/src/components/onboarding/OnboardingSummaryStep.tsx (the component just created in Task 1) Modify `ui/src/components/NexusOnboardingWizard.tsx` with these changes: **1. Import additions (top of file):** - `import { OnboardingSummaryStep } from "./onboarding/OnboardingSummaryStep";` - `import { useChatPanel } from "../context/ChatPanelContext";` **2. Add useChatPanel hook** inside the `OnboardingWizard` component body, near the other hooks: ```typescript const { setChatOpen } = useChatPanel(); ``` **3. Add `deriveProviderLabel` helper** inside or before the component: ```typescript function deriveProviderLabel( puterToken: string | null, googleOAuthStateId: string | null, apiKeyData: { provider: string; apiKey: string } | null, ): string { if (puterToken) return "Puter (free, zero-config)"; if (googleOAuthStateId) return "Google Gemini (free tier)"; if (apiKeyData) return `API key — ${apiKeyData.provider}`; return "None selected"; } ``` **4. Update step indicator** — change `

Step {step} of 4

` to: ```tsx

{step === 5 ? "Summary" : `Step ${step} of 4`}

``` Note: Keep "of 4" — the summary is not a form step, so steps 1-4 are the data entry steps. Step 5 shows "Summary" label instead. **5. Add skip button to Step 1** (hardware detection) — after the existing Continue button, add: ```tsx ``` **6. Add skip button to Step 2** (mode selection) — after the Back button in the button group, add: ```tsx ``` **7. Modify Step 4 "Get Started" button** — change from `type="submit"` to `type="button"` and change its onClick to advance to step 5 instead of submitting: ```tsx ``` Also remove the `
` wrapper around step 4 — replace with a plain `
`. The submit now happens on step 5. Also add skip button to Step 4 after the Back button: ```tsx ``` **8. Add Step 5 — Summary** after the step 4 block: ```tsx {step === 5 && ( setStep(4)} /> )} ``` **9. Create `handleStartChat` function** — adapts existing `handleSubmit` logic but also opens chat: ```typescript async function handleStartChat() { // Guard: claude_local requires rootDir if (defaultAdapter === "claude_local" && !rootDir.trim()) { setError("Root directory is required for Claude Code. Go back to step 4 to set it."); return; } setLoading(true); setError(null); try { // === Same company creation logic as handleSubmit === const company = await companiesApi.create({ name: VOCAB.appName }); setSelectedCompanyId(company.id); queryClient.invalidateQueries({ queryKey: queryKeys.companies.all }); // ... (copy all the agent creation, mode save, and credential storage code // from handleSubmit — the baseAdapterConfig, hermesPromptTemplate, // adapterConfig, runtimeConfig, PM agent create, Engineer agent create, // queryClient invalidation, updateNexusSettings, puterToken/google/apiKey storage) // Navigate to dashboard then open chat panel closeOnboarding(); navigate(`/${company.issuePrefix}/dashboard`); setChatOpen(true); // <-- ONBD-06: opens chat panel after navigation } catch (err) { setError(err instanceof Error ? err.message : "Setup failed. Please try again."); setLoading(false); } } ``` IMPORTANT: The `handleStartChat` function contains the SAME company/agent creation logic as the existing `handleSubmit`. The only differences are: (a) no `e: React.FormEvent` parameter, (b) error message mentions going back to step 4 for rootDir, (c) `setChatOpen(true)` is called after `navigate()`. You can either refactor the shared logic into an internal helper or duplicate it — refactoring into a helper like `createWorkspace()` is cleaner. **10. Update the reset effect** — in the `useEffect` that resets form state when wizard closes, change `setStep(1)` to still reset to 1 (no change needed — step 5 naturally resets). **11. Update the top comment** — change "4-step flow" to "5-step flow" and update the step list to include "summary". cd /opt/nexus && pnpm vitest run ui/src/components/onboarding/ - grep -q "OnboardingSummaryStep" ui/src/components/NexusOnboardingWizard.tsx - grep -q "useChatPanel" ui/src/components/NexusOnboardingWizard.tsx - grep -q "setChatOpen(true)" ui/src/components/NexusOnboardingWizard.tsx - grep -q "step === 5" ui/src/components/NexusOnboardingWizard.tsx - grep -q "deriveProviderLabel" ui/src/components/NexusOnboardingWizard.tsx - grep -c "Skip" ui/src/components/NexusOnboardingWizard.tsx shows at least 3 skip buttons - grep -q "Summary" ui/src/components/NexusOnboardingWizard.tsx (step indicator label) - grep -q "handleStartChat" ui/src/components/NexusOnboardingWizard.tsx Wizard has 5 steps with skip on 1/2/4, summary on step 5, and "Start chatting" creates workspace then opens chat panel. TypeScript compiles and tests pass. 1. `pnpm vitest run ui/src/components/onboarding/` — all unit tests pass 2. `cd /opt/nexus && pnpm tsc --noEmit` — no TypeScript errors 3. Grep checks: `grep -q "setChatOpen(true)" ui/src/components/NexusOnboardingWizard.tsx` confirms ONBD-06 wiring 4. Grep checks: `grep -c "Skip" ui/src/components/NexusOnboardingWizard.tsx` confirms at least 3 skip buttons (ONBD-04) 5. Grep checks: `grep -q "OnboardingSummaryStep" ui/src/components/NexusOnboardingWizard.tsx` confirms summary wired (ONBD-05) - OnboardingSummaryStep component exists and renders hardware, mode, provider, root dir - Skip buttons on steps 1, 2, and 4 (step 3 already has one) - Step 5 shows "Summary" in the step indicator - "Start chatting" on summary creates workspace, navigates to dashboard, and opens chat panel - No corporate language ("company", "CEO", "mission") in the summary step - All unit tests pass - TypeScript compiles cleanly After completion, create `.planning/phases/32-multi-step-onboarding-wizard/32-01-SUMMARY.md`