diff --git a/.planning/phases/32-multi-step-onboarding-wizard/32-RESEARCH.md b/.planning/phases/32-multi-step-onboarding-wizard/32-RESEARCH.md
new file mode 100644
index 00000000..c37fea19
--- /dev/null
+++ b/.planning/phases/32-multi-step-onboarding-wizard/32-RESEARCH.md
@@ -0,0 +1,368 @@
+# Phase 32: Multi-Step Onboarding Wizard - Research
+
+**Researched:** 2026-04-01
+**Domain:** React wizard UI — step navigation, summary screen, skip flow, chat handoff
+**Confidence:** HIGH
+
+## Summary
+
+Phase 32 completes the onboarding wizard that Phases 30 and 31 built. The wizard already has 4 steps (hardware detection, mode selection, provider selection, root directory). This phase adds three things: (1) a summary screen that replaces or follows the root directory step to show the user what they configured, (2) a guaranteed skip path through every step, and (3) a "Go to chat" button on the summary screen that opens the chat panel and closes the wizard without waiting.
+
+The skip mechanism already partially exists — `ProviderSelectionStep` has a "Skip for now" ghost button. What's missing is: (a) consistent skip affordance on steps 1 and 2, (b) a full summary step (step 5) that displays hardware tier, selected mode, and chosen provider, and (c) a one-click path from summary to chat using `useChatPanel().setChatOpen(true)`.
+
+No server-side changes are needed. Voice options (Whisper STT) are already wired server-side via `POST /api/transcribe` in `chat-files.ts` but there is no onboarding step for them. VOICE-03 is assigned to Phase 34, not Phase 32. Phase 32 should not introduce voice configuration UI — that is out of scope here.
+
+**Primary recommendation:** Add a `OnboardingSummaryStep` component in `ui/src/components/onboarding/`, insert it as step 5 in `NexusOnboardingWizard.tsx`, add skip buttons to steps 1 and 2, and wire the summary's "Start chatting" CTA to `setChatOpen(true)` from `useChatPanel`.
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+All implementation choices are at Claude's discretion — discuss phase was skipped per user setting. Use ROADMAP phase goal, success criteria, and codebase conventions to guide decisions.
+
+### Claude's Discretion
+All implementation choices.
+
+### Deferred Ideas (OUT OF SCOPE)
+None — discuss phase skipped.
+
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|------------------|
+| ONBD-04 | User can skip any onboarding step without blocking subsequent steps | Steps 1 and 2 have no skip button today; step 3 has "Skip for now" ghost button; pattern is established — replicate to all steps |
+| ONBD-05 | User sees summary screen showing configured providers and agent-model pairings | New `OnboardingSummaryStep` component needed as step 5; data available in wizard state (hardwareInfo, selectedMode, puterToken/googleOAuthStateId/apiKeyData) |
+| ONBD-06 | User can go from summary screen directly into chat with one click | `useChatPanel().setChatOpen(true)` + `closeOnboarding()` + `navigate(/${company.issuePrefix}/dashboard)` — but this requires company creation first |
+
+
+## Standard Stack
+
+### Core
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| React | 18.x (project baseline) | Wizard component state | Already in use throughout |
+| @tanstack/react-query | 5.x (project baseline) | Hardware info query | `useHardwareInfo` hook already uses it |
+| react-router-dom (via `@/lib/router`) | project alias | Navigation after wizard completion | Already used in wizard |
+
+### Supporting
+| Library | Version | Purpose | When to Use |
+|---------|---------|---------|-------------|
+| lucide-react | project baseline | Icons in summary screen | All existing onboarding components use it |
+| `@/components/ui/button` | project baseline | Consistent button styling | All CTA buttons in wizard use it |
+| `cn` from `@/lib/utils` | project baseline | Conditional class merging | Used in all onboarding sub-components |
+
+**Installation:** No new packages. All dependencies already installed.
+
+## Architecture Patterns
+
+### Recommended Project Structure
+```
+ui/src/components/
+├── NexusOnboardingWizard.tsx # main wizard — add step 5 (summary), skip to steps 1+2
+└── onboarding/
+ ├── HardwareSummaryStep.tsx # existing (step 1 display)
+ ├── ModeSelector.tsx # existing (step 2)
+ ├── ProviderSelectionStep.tsx # existing (step 3) — already has skip
+ ├── ApiKeyEntryForm.tsx # existing
+ ├── GoogleOAuthButton.tsx # existing
+ ├── PuterAuthButton.tsx # existing
+ └── OnboardingSummaryStep.tsx # NEW — step 5
+```
+
+### Pattern 1: Step State Machine
+**What:** Integer `step` state (1-4 today, will become 1-5). Each step renders conditionally on `{step === N}`. Navigation via `setStep(N)`.
+**When to use:** Already the established pattern — do not change to a different mechanism.
+**Example:**
+```typescript
+// From NexusOnboardingWizard.tsx (existing pattern)
+const [step, setStep] = useState(1);
+// ...
+{step === 5 && (
+ setStep(4)}
+ />
+)}
+```
+
+### Pattern 2: Skip Button (ghost variant)
+**What:** Every step except the last (summary) must have a skip CTA. Current pattern from `ProviderSelectionStep`: ``.
+**When to use:** Steps 1, 2, and 4 need skip added. Step 3 already has it.
+**Example:**
+```typescript
+// Step 1 — hardware detection, add after Continue button:
+
+// Step 2 — mode selection, add after Back/Continue buttons:
+
+// Step 4 — root directory, add after Back button:
+
+```
+
+### Pattern 3: Jump to Chat (ONBD-06)
+**What:** On the summary screen, "Start chatting" CTA calls `setChatOpen(true)` then calls `handleSubmit` logic (or equivalent). The key insight: company creation must happen before navigating to chat. The summary step is shown after step 4 (root directory) where the user may or may not have entered a directory. The "Start chatting" button must trigger company creation if it hasn't happened yet.
+**When to use:** Summary step's primary CTA.
+
+Two design options:
+
+**Option A (simpler):** Keep the existing `handleSubmit` on step 4, make "Get Started" button advance to step 5 (summary) instead of submitting, then summary's "Start chatting" actually submits. Company creation happens when the user clicks "Start chatting".
+
+**Option B:** Move company creation to wizard close, make step 5 a pure display step, "Start chatting" closes wizard and opens chat.
+
+**Recommendation: Option A.** The summary step is after all configuration is complete; when user clicks "Start chatting" on the summary, call the existing `handleSubmit` logic plus `setChatOpen(true)` afterward.
+
+```typescript
+// In NexusOnboardingWizard.tsx
+import { useChatPanel } from "../context/ChatPanelContext";
+// ...
+const { setChatOpen } = useChatPanel();
+
+async function handleStartChat(e?: React.FormEvent) {
+ // Same company creation logic as handleSubmit
+ // After navigate(/${company.issuePrefix}/dashboard):
+ setChatOpen(true);
+}
+```
+
+### Pattern 4: OnboardingSummaryStep Component
+**What:** A read-only summary card showing: hardware tier + memory, selected mode, provider name (or "None selected").
+**When to use:** Step 5 of the wizard.
+**Props:**
+```typescript
+interface OnboardingSummaryStepProps {
+ hardwareInfo: HardwareInfo | undefined;
+ selectedMode: NexusMode;
+ providerChosen: "puter" | "google" | "apikey" | null;
+ rootDir: string;
+ loading: boolean;
+ error: string | null;
+ onStartChat: () => void;
+ onBack: () => void;
+}
+```
+
+### Anti-Patterns to Avoid
+- **Changing the step integer type to a string enum:** The existing `step: 1 | 2 | 3 | 4` pattern is simple and consistent — keep it as `1 | 2 | 3 | 4 | 5`.
+- **Moving company creation earlier (before summary):** Company creation must stay in `handleSubmit` — called on "Start chatting" click. Creating the company before the user finalizes breaks the established pattern from Phase 31.
+- **Calling `setChatOpen(true)` before `navigate()`:** The chat panel is rendered inside `Layout`, which only renders after navigation to a company route. Call `setChatOpen(true)` after `navigate()`.
+- **Adding voice step in Phase 32:** VOICE-03 is Phase 34. Do not add a voice config step now.
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Provider display name | Custom string map | Inline label constants | Simple enough for a `const PROVIDER_LABELS` in the summary component |
+| Chat panel open state | Custom context/ref | `useChatPanel().setChatOpen` | Already wired to localStorage persistence and Layout rendering |
+| Hardware tier label | Custom detection | Read from `hardwareInfo.hardwareTier` | Already computed by `useHardwareInfo` hook |
+| Step validation | Custom state machine | Simple `setStep(N)` calls | 5 linear steps, no branching beyond skip |
+
+**Key insight:** This phase is pure UI composition. All data is already in wizard state; no new APIs, hooks, or server routes are needed.
+
+## Runtime State Inventory
+
+> Not applicable. This is a greenfield UI-only change — no rename, refactor, or migration involved.
+
+## Common Pitfalls
+
+### Pitfall 1: Step Count Mismatch in UI
+**What goes wrong:** The wizard shows "Step X of 4" in the header but there are now 5 steps.
+**Why it happens:** `
Step {step} of 4
` is hardcoded.
+**How to avoid:** Change `4` to `5` in the step indicator string when adding step 5. Also suppress "Step 5 of 5" on the summary — show "Summary" or "Almost done" instead since it's not a data entry step.
+**Warning signs:** QA screenshot shows "Step 5 of 4".
+
+### Pitfall 2: `setChatOpen(true)` Before Company Route Exists
+**What goes wrong:** Chat panel tries to render in a context without a `selectedCompanyId`, showing an empty state or an error.
+**Why it happens:** `ChatPanel` uses `useChatPanel()` and `useCompany()`. The company only exists after `companiesApi.create()` and `setSelectedCompanyId()`.
+**How to avoid:** Call `setChatOpen(true)` after `navigate(\`/${company.issuePrefix}/dashboard\`)` inside `handleStartChat`. React Router navigation is synchronous at the call site — Layout re-renders with the new company before the next paint.
+**Warning signs:** Chat panel opens but shows no conversation list.
+
+### Pitfall 3: Summary Step Not Receiving `rootDir` Data
+**What goes wrong:** Summary screen shows empty root directory field, even though user entered one.
+**Why it happens:** `rootDir` state is in the wizard parent but not passed to `OnboardingSummaryStep`.
+**How to avoid:** Pass `rootDir` as a prop. The component should display it in a `font-mono text-sm` span (matching the existing `Input` style).
+**Warning signs:** TypeScript error on prop type mismatch, or blank field in summary.
+
+### Pitfall 4: Skip on Step 4 Skips Company Creation
+**What goes wrong:** User skips the root directory step → summary appears → user clicks "Start chatting" → submit runs with empty `rootDir` → fails for `claude_local` adapter (which requires a cwd).
+**Why it happens:** `handleSubmit` has `if (defaultAdapter === "claude_local" && !rootDir.trim()) return;` guard.
+**How to avoid:** The submit logic already handles this correctly — it returns early without creating a company. The "Start chatting" button on the summary must be the same submit function. If `defaultAdapter === "claude_local"` and `rootDir` is empty, show the error on the summary screen. Alternatively, grey out "Start chatting" with a tooltip "Root directory required for Claude Code".
+**Warning signs:** User clicks "Start chatting" and nothing happens (silent return in `handleSubmit`).
+
+### Pitfall 5: Provider Label for No-Provider Case
+**What goes wrong:** Summary screen shows `null` or `undefined` when no provider was selected.
+**Why it happens:** `puterToken`, `googleOAuthStateId`, and `apiKeyData` are all null when user skipped step 3.
+**How to avoid:** Show "No provider selected — you can add one later in Settings" when all three are null.
+**Warning signs:** Summary shows blank provider row or crashes on null access.
+
+## Code Examples
+
+### Reading Provider State for Summary Display
+```typescript
+// Source: NexusOnboardingWizard.tsx (existing state)
+// Determine which provider was configured
+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";
+}
+```
+
+### Opening Chat After Wizard Completion
+```typescript
+// Source: ChatPanelContext.tsx — setChatOpen persists to localStorage
+const { setChatOpen } = useChatPanel();
+
+// Inside handleStartChat, after navigate():
+navigate(`/${company.issuePrefix}/dashboard`);
+setChatOpen(true);
+closeOnboarding();
+```
+
+### Summary Step Skeleton
+```typescript
+// ui/src/components/onboarding/OnboardingSummaryStep.tsx
+import { Button } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
+import type { HardwareInfo } from "@/api/hardware";
+import type { NexusMode } from "@/api/hardware";
+
+interface OnboardingSummaryStepProps {
+ hardwareInfo: HardwareInfo | undefined;
+ selectedMode: NexusMode;
+ providerLabel: string;
+ rootDir: string;
+ loading: boolean;
+ error: string | null;
+ onStartChat: () => void;
+ onBack: () => void;
+}
+
+export function OnboardingSummaryStep({
+ hardwareInfo, selectedMode, providerLabel, rootDir, loading, error, onStartChat, onBack,
+}: OnboardingSummaryStepProps) {
+ const modeLabels: Record = {
+ personal_ai: "Personal AI Assistant",
+ project_builder: "Project Builder",
+ both: "Both (recommended)",
+ };
+ return (
+
+
+
+
+
+ {rootDir && }
+
+ {error && (
+
{error}
+ )}
+
+
+
+ );
+}
+```
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| 4-step wizard (hw, mode, provider, rootDir) | 5-step wizard adding summary | Phase 32 | No breaking changes — just append step 5 |
+| No skip on steps 1 and 2 | Skip button on all steps | Phase 32 | Users who skip all still need one valid agent — see PROJECT.md constraint |
+
+**Key constraint from PROJECT.md (established in roadmap):**
+> Skip-all minimum valid state: one working agent with a valid provider must be created when user skips all steps.
+
+This means when the user skips everything and clicks "Start chatting" on the summary: the wizard must still call `handleSubmit` (which creates the company + agents). The default adapter (`claude_local` or `hermes_local`) and optional `rootDir` must still produce a valid state. For `hermes_local` this is fine (rootDir optional). For `claude_local` with no rootDir — the submit guard returns early. The UX must communicate this: disable "Start chatting" or show inline error on the summary if `claude_local` adapter is selected and rootDir is empty.
+
+## Environment Availability
+
+Step 2.6: SKIPPED — this phase is UI-only React changes. No new external dependencies.
+
+## Validation Architecture
+
+### Test Framework
+| Property | Value |
+|----------|-------|
+| Framework | Vitest + React Testing Library (project standard) |
+| Config file | `ui/vitest.config.ts` |
+| Quick run command | `pnpm vitest run ui/src/components/onboarding/` |
+| Full suite command | `pnpm test:run` |
+
+### Phase Requirements → Test Map
+
+| Req ID | Behavior | Test Type | Automated Command | File Exists? |
+|--------|----------|-----------|-------------------|-------------|
+| ONBD-04 | Skip button present on all steps | unit | `pnpm vitest run ui/src/components/NexusOnboardingWizard.test.tsx` | ❌ Wave 0 |
+| ONBD-05 | Summary screen renders hardware/mode/provider data | unit | `pnpm vitest run ui/src/components/onboarding/OnboardingSummaryStep.test.tsx` | ❌ Wave 0 |
+| ONBD-06 | "Start chatting" calls setChatOpen(true) and navigate | unit | `pnpm vitest run ui/src/components/NexusOnboardingWizard.test.tsx` | ❌ Wave 0 |
+
+### Sampling Rate
+- **Per task commit:** `pnpm vitest run ui/src/components/onboarding/`
+- **Per wave merge:** `pnpm test:run`
+- **Phase gate:** Full suite green before `/gsd:verify-work`
+
+### Wave 0 Gaps
+- [ ] `ui/src/components/onboarding/OnboardingSummaryStep.test.tsx` — covers ONBD-05
+- [ ] `ui/src/components/NexusOnboardingWizard.test.tsx` — covers ONBD-04, ONBD-06
+
+## Open Questions
+
+1. **Should step 4 (root directory) survive or be merged into summary?**
+ - What we know: Step 4 is the only form input step. Summary (step 5) is read-only except for the submit CTA.
+ - What's unclear: Whether the root directory input belongs on the summary screen or stays as its own step.
+ - Recommendation: Keep step 4 as a separate form step; summary is step 5 (pure read-only). This is less disruptive to existing code.
+
+2. **"Start chatting" on summary — does it also navigate or only open chat panel?**
+ - What we know: Current wizard navigates to `/${company.issuePrefix}/dashboard` on completion.
+ - What's unclear: The requirement says "go directly into chat" — does this mean open the chat panel on dashboard, or navigate to a chat-specific route?
+ - Recommendation: Navigate to dashboard + `setChatOpen(true)`. The chat panel slides in from the right on all company pages. No separate chat route exists.
+
+3. **Step indicator label for summary step**
+ - What we know: Current indicator is "Step N of 4" — hardcoded.
+ - Recommendation: Show "Summary" instead of "Step 5 of 5" on step 5 to signal it's a review step, not a data entry step.
+
+## Sources
+
+### Primary (HIGH confidence)
+- Direct codebase inspection: `ui/src/components/NexusOnboardingWizard.tsx` — full wizard implementation
+- Direct codebase inspection: `ui/src/components/onboarding/*.tsx` — all 6 sub-components
+- Direct codebase inspection: `ui/src/context/ChatPanelContext.tsx` — setChatOpen API
+- Direct codebase inspection: `server/src/routes/chat-files.ts` — Whisper transcription (Phase 34 only)
+- Direct codebase inspection: `.planning/STATE.md` — Project constraints (skip-all minimum valid state)
+- Direct codebase inspection: `.planning/REQUIREMENTS.md` — ONBD-04/05/06 exact wording
+
+### Secondary (MEDIUM confidence)
+- None needed — all required information in codebase.
+
+### Tertiary (LOW confidence)
+- None.
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH — all existing code confirmed
+- Architecture: HIGH — wizard pattern is established, summary step is additive
+- Pitfalls: HIGH — derived from direct reading of existing component code and constraint documentation
+
+**Research date:** 2026-04-01
+**Valid until:** 2026-05-01 (stable React codebase, no moving dependencies)