diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 7ebd57ac..a3f9075c 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -141,7 +141,10 @@ Plans: 1. A user can click "Skip" on every onboarding step (hardware, provider, voice) and reach the summary screen; the resulting workspace has at least one working agent with a valid provider 2. The summary screen shows the configured providers and agent-model pairings for the selected mode; no corporate language ("company", "CEO", "mission") appears anywhere in the flow 3. From the summary screen, one click navigates directly to the Personal Assistant chat or the project dashboard (depending on chosen mode) with no additional prompts -**Plans**: TBD +**Plans**: 1 plan + +Plans: +- [ ] 32-01-PLAN.md — Summary step, skip buttons, chat handoff **UI hint**: yes ### Phase 33: Persistent Memory + Personal Assistant Mode diff --git a/.planning/phases/32-multi-step-onboarding-wizard/32-01-PLAN.md b/.planning/phases/32-multi-step-onboarding-wizard/32-01-PLAN.md new file mode 100644 index 00000000..c8d73601 --- /dev/null +++ b/.planning/phases/32-multi-step-onboarding-wizard/32-01-PLAN.md @@ -0,0 +1,350 @@ +--- +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` +