nexus/.planning/phases/32-multi-step-onboarding-wizard/32-01-PLAN.md
2026-04-04 03:55:49 +00:00

350 lines
15 KiB
Markdown

---
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\\)"
---
<objective>
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.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/32-multi-step-onboarding-wizard/32-RESEARCH.md
<interfaces>
<!-- Key types and contracts the executor needs -->
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<NexusMode>("both");
const [puterToken, setPuterToken] = useState<string | null>(null);
const [googleOAuthStateId, setGoogleOAuthStateId] = useState<string | null>(null);
const [apiKeyData, setApiKeyData] = useState<{ provider: string; apiKey: string } | null>(null);
const [rootDir, setRootDir] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const { data: hardwareInfo } = useHardwareInfo(effectiveOnboardingOpen);
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Create OnboardingSummaryStep component and tests</name>
<files>ui/src/components/onboarding/OnboardingSummaryStep.tsx, ui/src/components/onboarding/OnboardingSummaryStep.test.tsx</files>
<read_first>
- 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)
</read_first>
<action>
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"`.
</action>
<verify>
<automated>cd /opt/nexus && pnpm vitest run ui/src/components/onboarding/OnboardingSummaryStep.test.tsx</automated>
</verify>
<acceptance_criteria>
- 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
</acceptance_criteria>
<done>OnboardingSummaryStep renders hardware, mode, provider, root dir in a read-only card with Start chatting CTA. All 6 tests pass.</done>
</task>
<task type="auto">
<name>Task 2: Wire summary step into wizard, add skip buttons, connect chat handoff</name>
<files>ui/src/components/NexusOnboardingWizard.tsx</files>
<read_first>
- 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)
</read_first>
<action>
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 `<p className="text-xs text-muted-foreground text-center">Step {step} of 4</p>` to:
```tsx
<p className="text-xs text-muted-foreground text-center">
{step === 5 ? "Summary" : `Step ${step} of 4`}
</p>
```
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
<Button type="button" variant="ghost" onClick={() => setStep(2)} className="w-full">
Skip
</Button>
```
**6. Add skip button to Step 2** (mode selection) — after the Back button in the button group, add:
```tsx
<Button type="button" variant="ghost" onClick={() => setStep(3)} className="w-full">
Skip
</Button>
```
**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
<Button
type="button"
onClick={() => setStep(5)}
disabled={loading || probing}
className="w-full"
>
Review &amp; finish
</Button>
```
Also remove the `<form onSubmit={handleSubmit}>` wrapper around step 4 — replace with a plain `<div className="flex flex-col gap-4">`. The submit now happens on step 5.
Also add skip button to Step 4 after the Back button:
```tsx
<Button type="button" variant="ghost" onClick={() => setStep(5)} className="w-full" disabled={loading}>
Skip to summary
</Button>
```
**8. Add Step 5 — Summary** after the step 4 block:
```tsx
{step === 5 && (
<OnboardingSummaryStep
hardwareInfo={hardwareInfo}
selectedMode={selectedMode}
providerLabel={deriveProviderLabel(puterToken, googleOAuthStateId, apiKeyData)}
rootDir={rootDir}
loading={loading}
error={error}
onStartChat={handleStartChat}
onBack={() => 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".
</action>
<verify>
<automated>cd /opt/nexus && pnpm vitest run ui/src/components/onboarding/</automated>
</verify>
<acceptance_criteria>
- 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
</acceptance_criteria>
<done>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.</done>
</task>
</tasks>
<verification>
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)
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/32-multi-step-onboarding-wizard/32-01-SUMMARY.md`
</output>