--- phase: 30-hardware-detection-mode-selection plan: 02 type: execute wave: 2 depends_on: ["30-01"] files_modified: - ui/src/api/hardware.ts - ui/src/hooks/useHardwareInfo.ts - ui/src/components/onboarding/ModeSelector.tsx - ui/src/components/onboarding/HardwareSummaryStep.tsx - ui/src/components/NexusOnboardingWizard.tsx autonomous: false requirements: - ONBD-01 - ONBD-02 - ONBD-07 must_haves: truths: - "User sees hardware detection results (GPU/RAM/unified memory) during onboarding within 5 seconds" - "User can select mode: Personal AI Assistant, Project Builder, or Both (default pre-selected)" - "Selected mode is persisted via PATCH /api/nexus/settings on wizard completion" - "Apple Silicon shows 'Unified memory' label, never 'VRAM'" - "When local AI is viable (gpu or apple_silicon tier), privacy frame copy is shown: 'Local AI (recommended for privacy) - Runs entirely on your machine. No accounts. No tracking. Works offline.'" - "CPU-only tier shows warning: 'Slower than GPU-accelerated models - cloud AI recommended'" - "Wizard has 3 steps: hardware detection, mode selection, root directory (existing)" artifacts: - path: "ui/src/api/hardware.ts" provides: "Typed fetch wrappers for hardware probe and nexus settings" exports: ["fetchHardwareInfo", "fetchNexusSettings", "updateNexusSettings"] - path: "ui/src/hooks/useHardwareInfo.ts" provides: "useQuery wrapper for hardware data" exports: ["useHardwareInfo"] - path: "ui/src/components/onboarding/ModeSelector.tsx" provides: "Three-card mode selector with selected state styling" exports: ["ModeSelector"] - path: "ui/src/components/onboarding/HardwareSummaryStep.tsx" provides: "Hardware info display with skeleton loading, tier-appropriate labels, privacy frame" exports: ["HardwareSummaryStep"] - path: "ui/src/components/NexusOnboardingWizard.tsx" provides: "Multi-step wizard: hardware -> mode -> root directory" key_links: - from: "ui/src/hooks/useHardwareInfo.ts" to: "/api/system/providers" via: "fetch in useQuery" pattern: "system/providers" - from: "ui/src/components/onboarding/HardwareSummaryStep.tsx" to: "ui/src/hooks/useHardwareInfo.ts" via: "useHardwareInfo hook" pattern: "useHardwareInfo" - from: "ui/src/components/NexusOnboardingWizard.tsx" to: "ui/src/api/hardware.ts" via: "updateNexusSettings call on wizard complete" pattern: "updateNexusSettings" - from: "ui/src/components/NexusOnboardingWizard.tsx" to: "ui/src/components/onboarding/ModeSelector.tsx" via: "React component composition" pattern: "ModeSelector" --- Build the onboarding UI components for hardware detection display, mode selection, and wire them into the NexusOnboardingWizard as a multi-step flow. Purpose: Delivers the user-facing experience for Phase 30 — users see their hardware, choose a mode, and the selection is persisted. This is the visual and interaction layer consuming the server endpoints from Plan 01. Output: Four new UI files (API client, hook, ModeSelector, HardwareSummaryStep) and one modified file (NexusOnboardingWizard.tsx refactored to 3-step flow). @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/30-hardware-detection-mode-selection/30-RESEARCH.md @.planning/phases/30-hardware-detection-mode-selection/30-UI-SPEC.md @.planning/phases/30-hardware-detection-mode-selection/30-01-SUMMARY.md @ui/src/components/NexusOnboardingWizard.tsx @ui/src/api/client.ts @ui/src/lib/queryKeys.ts @ui/src/lib/utils.ts @ui/src/components/ui/skeleton.tsx @ui/src/components/ui/button.tsx From server/src/services/hardware.ts: ```typescript export type HardwareTier = "gpu" | "apple_silicon" | "cpu_only"; export interface HardwareInfo { totalGb: number; freeGb: number; usableGb: number; platform: NodeJS.Platform; gpuName: string | null; gpuVramGb: number | null; unifiedMemory: boolean; hardwareTier: HardwareTier; cpuModel: string | null; } ``` From server/src/services/nexus-settings.ts: ```typescript export const NEXUS_MODES = ["personal_ai", "project_builder", "both"] as const; export type NexusMode = (typeof NEXUS_MODES)[number]; export type NexusSettings = { mode: NexusMode }; ``` Server endpoints: - GET /api/system/providers -> HardwareInfo (unauthenticated) - GET /api/nexus/settings -> NexusSettings (board auth) - PATCH /api/nexus/settings -> NexusSettings (board auth, body: Partial of NexusSettings) From ui/src/api/client.ts: ```typescript // Simple fetch wrapper — all API modules use this pattern: // import { api } from "./client"; // const data = await api.get("/endpoint"); // const data = await api.patch("/endpoint", body); ``` From ui/src/lib/queryKeys.ts: ```typescript export const queryKeys = { // ... existing keys // Add: hardware: { info: ["hardware", "info"] as const } }; ``` Task 1: API client, hook, ModeSelector, and HardwareSummaryStep ui/src/api/hardware.ts ui/src/hooks/useHardwareInfo.ts ui/src/components/onboarding/ModeSelector.tsx ui/src/components/onboarding/HardwareSummaryStep.tsx ui/src/lib/queryKeys.ts ui/src/api/client.ts ui/src/api/agents.ts ui/src/hooks/useHardwareInfo.ts ui/src/lib/queryKeys.ts ui/src/lib/utils.ts ui/src/components/ui/skeleton.tsx .planning/phases/30-hardware-detection-mode-selection/30-UI-SPEC.md .planning/phases/30-hardware-detection-mode-selection/30-RESEARCH.md **1. Create `ui/src/api/hardware.ts`:** Define types locally (not imported from server — UI and server are separate packages): ```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"; export interface NexusSettings { mode: NexusMode; } ``` Export three functions using the `api` client from `"./client"`: - `fetchHardwareInfo(): Promise` — `api.get("/system/providers")` - `fetchNexusSettings(): Promise` — `api.get("/nexus/settings")` - `updateNexusSettings(settings: Partial): Promise` — `api.patch("/nexus/settings", settings)` **2. Update `ui/src/lib/queryKeys.ts`:** Add to the queryKeys object: ```typescript hardware: { info: ["hardware", "info"] as const, }, ``` **3. Create `ui/src/hooks/useHardwareInfo.ts`:** ```typescript import { useQuery } from "@tanstack/react-query"; import { fetchHardwareInfo, type HardwareInfo } from "../api/hardware"; import { queryKeys } from "../lib/queryKeys"; export function useHardwareInfo(enabled = true) { return useQuery({ queryKey: queryKeys.hardware.info, queryFn: fetchHardwareInfo, enabled, staleTime: 5 * 60 * 1000, // 5 minutes — matches server cache TTL retry: 1, }); } ``` **4. Create `ui/src/components/onboarding/ModeSelector.tsx`:** Exact implementation from RESEARCH.md Pattern 5 and UI-SPEC.md: - Three buttons in a vertical grid (`grid gap-3`). - Each button: `flex flex-col gap-1 rounded-lg border p-4 text-left transition-colors`. - Selected state: `border-primary bg-primary/5`. - Unselected state: `border-border hover:border-muted-foreground/50`. - Label: `font-medium text-sm`. Description: `text-xs text-muted-foreground`. Mode definitions (verbatim from PRD/RESEARCH): ``` personal_ai: "Personal AI Assistant" / "Always available, persistent memory, private." project_builder: "Project Builder" / "Brainstorm -> PM -> Engineer -> shipped product." both: "Both (recommended)" / "A conversation becomes a project with one click." ``` Props: `{ value: NexusMode; onChange: (mode: NexusMode) => void }`. Import `cn` from `"@/lib/utils"` and `NexusMode` type from `"@/api/hardware"`. **5. Create `ui/src/components/onboarding/HardwareSummaryStep.tsx`:** Props: `{ hardwareInfo: HardwareInfo | undefined; isLoading: boolean; isError: boolean }`. **Loading state** (isLoading = true): Render three `Skeleton` rows (`h-4 w-full rounded`). Import Skeleton from `"@/components/ui/skeleton"`. **Error state** (isError = true): Render `

Could not detect hardware. You can still continue.

`. Note: NOT `text-destructive` — this is non-blocking per UI-SPEC. **Success state** (hardwareInfo exists): Render a vertical stack (`flex flex-col gap-4`): a) Hardware stats rows (`flex flex-col gap-2`): - If `hardwareInfo.hardwareTier === "apple_silicon"`: - Row: label "Unified memory" (never "VRAM"), value `{hardwareInfo.totalGb} GB` - Row: label "Available", value `{hardwareInfo.usableGb} GB` - Row: label "CPU", value `{hardwareInfo.cpuModel}` - If `hardwareInfo.hardwareTier === "gpu"`: - Row: label "GPU", value `{hardwareInfo.gpuName}` - Row: label "GPU VRAM", value `{hardwareInfo.gpuVramGb} GB` - Row: label "System RAM", value `{hardwareInfo.totalGb} GB` - If `hardwareInfo.hardwareTier === "cpu_only"`: - Row: label "System RAM", value `{hardwareInfo.totalGb} GB` - Row: label "CPU", value `{hardwareInfo.cpuModel}` - Warning: `

Slower than GPU-accelerated models -- cloud AI recommended

` Each stat row: `
{label}{value}
` b) Privacy frame (shown when `hardwareTier !== "cpu_only"`): ```tsx
Local AI (recommended for privacy) Runs entirely on your machine.{"\n"} No accounts. No tracking. Works offline.
``` Import `cn` from `"@/lib/utils"`, `Skeleton` from `"@/components/ui/skeleton"`, `HardwareInfo` from `"@/api/hardware"`.
cd /opt/nexus && pnpm --filter ui exec tsc --noEmit 2>&1 | head -30 - ui/src/api/hardware.ts exports `fetchHardwareInfo`, `fetchNexusSettings`, `updateNexusSettings`, `HardwareInfo`, `HardwareTier`, `NexusMode`, `NexusSettings` - ui/src/api/hardware.ts contains `/system/providers` - ui/src/api/hardware.ts contains `/nexus/settings` - ui/src/hooks/useHardwareInfo.ts exports `useHardwareInfo` - ui/src/hooks/useHardwareInfo.ts contains `queryKeys.hardware.info` - ui/src/lib/queryKeys.ts contains `hardware:` key with `info:` subkey - ui/src/components/onboarding/ModeSelector.tsx exports `ModeSelector` - ui/src/components/onboarding/ModeSelector.tsx contains `"Personal AI Assistant"` - ui/src/components/onboarding/ModeSelector.tsx contains `"Project Builder"` - ui/src/components/onboarding/ModeSelector.tsx contains `"Both (recommended)"` - ui/src/components/onboarding/ModeSelector.tsx contains `border-primary bg-primary/5` - ui/src/components/onboarding/HardwareSummaryStep.tsx exports `HardwareSummaryStep` - ui/src/components/onboarding/HardwareSummaryStep.tsx contains `"Unified memory"` (for Apple Silicon) - ui/src/components/onboarding/HardwareSummaryStep.tsx contains `"Local AI (recommended for privacy)"` - ui/src/components/onboarding/HardwareSummaryStep.tsx contains `"Runs entirely on your machine"` - ui/src/components/onboarding/HardwareSummaryStep.tsx contains `"Slower than GPU-accelerated models"` - ui/src/components/onboarding/HardwareSummaryStep.tsx contains `"Could not detect hardware. You can still continue."` - ui/src/components/onboarding/HardwareSummaryStep.tsx contains `Skeleton` import - TypeScript compilation exits 0 with no errors All four new UI files created with correct types, copy, and styling. ModeSelector shows three cards with correct labels and selection state. HardwareSummaryStep shows tier-appropriate hardware info, privacy frame for local AI tiers, and warning for CPU-only. TypeScript compiles cleanly.
Task 2: Wire multi-step wizard in NexusOnboardingWizard ui/src/components/NexusOnboardingWizard.tsx ui/src/components/NexusOnboardingWizard.tsx ui/src/components/onboarding/ModeSelector.tsx ui/src/components/onboarding/HardwareSummaryStep.tsx ui/src/hooks/useHardwareInfo.ts ui/src/api/hardware.ts .planning/phases/30-hardware-detection-mode-selection/30-UI-SPEC.md Refactor `NexusOnboardingWizard.tsx` from a single-step form into a 3-step wizard. **Step state:** Add `const [step, setStep] = useState(1);` — values 1, 2, 3. - Step 1: Hardware detection (auto-runs, no user input) — shows `HardwareSummaryStep` - Step 2: Mode selection — shows `ModeSelector` - Step 3: Root directory (existing form) — shows existing Input + submit button **Mode state:** Add `const [selectedMode, setSelectedMode] = useState("both");` — default "both" as per UI-SPEC ("Both (recommended)" pre-selected on mount). **Hardware hook:** Add `const { data: hardwareInfo, isLoading: hwLoading, isError: hwError } = useHardwareInfo(effectiveOnboardingOpen);` — only fetch when wizard is open. **Step indicator:** Above the step content, render: ```tsx

Step {step} of 3

``` **Step 1 — Hardware Detection:** - Heading: `hwLoading ? "Detecting your hardware..." : "Your hardware"` (text-2xl font-semibold, per UI-SPEC) - Body: `` - Button: "Continue" — always enabled (hardware probe is non-blocking). On click: `setStep(2)`. **Step 2 — Mode Selection:** - Heading: "Choose your mode" (text-2xl font-semibold) - Body: `` - Button: "Continue" — always enabled. On click: `setStep(3)`. **Step 3 — Root Directory:** - Heading: keep existing `Welcome to {VOCAB.appName}` heading and description text (adapter-dependent copy) - Body: keep existing Input field for rootDir - Button: keep existing "Get Started" button and submit logic **On submit (handleSubmit):** After the existing company + agent creation logic succeeds, add a call to persist the selected mode: ```typescript // Persist selected mode try { await updateNexusSettings({ mode: selectedMode }); } catch { // Non-blocking — mode defaults to "both" if save fails } ``` Place this AFTER the company creation succeeds but BEFORE the navigate call. Import `updateNexusSettings` from `"@/api/hardware"`. **Back navigation:** On steps 2 and 3, show a secondary "Back" button (variant="ghost") that decrements step. No back button on step 1. **Reset:** In the existing reset effect (when wizard closes), also reset `step` to 1 and `selectedMode` to "both". **Imports to add:** ```typescript import { ModeSelector } from "./onboarding/ModeSelector"; import { HardwareSummaryStep } from "./onboarding/HardwareSummaryStep"; import { useHardwareInfo } from "../hooks/useHardwareInfo"; import { updateNexusSettings, type NexusMode } from "../api/hardware"; ``` **Preserve:** All existing adapter probe logic (Hermes detection), the Dialog/DialogPortal structure, the form submission flow, and the handleClose function. The wizard card's outer styling (`p-8 flex flex-col gap-6`, max-w-md, shadow-2xl) must remain unchanged. **Key constraint:** The existing export must remain `export function OnboardingWizard()` — this is the named export consumed by App.tsx via the Vite alias.
cd /opt/nexus && pnpm --filter ui exec tsc --noEmit 2>&1 | head -30 - ui/src/components/NexusOnboardingWizard.tsx contains `import { ModeSelector }` from `"./onboarding/ModeSelector"` - ui/src/components/NexusOnboardingWizard.tsx contains `import { HardwareSummaryStep }` from `"./onboarding/HardwareSummaryStep"` - ui/src/components/NexusOnboardingWizard.tsx contains `import { useHardwareInfo }` from `"../hooks/useHardwareInfo"` - ui/src/components/NexusOnboardingWizard.tsx contains `import { updateNexusSettings` from `"../api/hardware"` - ui/src/components/NexusOnboardingWizard.tsx contains `useState(1)` for step state - ui/src/components/NexusOnboardingWizard.tsx contains `useState.*"both"` for mode state - ui/src/components/NexusOnboardingWizard.tsx contains `Step {step} of 3` or `Step ${step} of 3` - ui/src/components/NexusOnboardingWizard.tsx contains `"Detecting your hardware"` - ui/src/components/NexusOnboardingWizard.tsx contains `"Your hardware"` - ui/src/components/NexusOnboardingWizard.tsx contains `"Choose your mode"` - ui/src/components/NexusOnboardingWizard.tsx contains `updateNexusSettings({ mode: selectedMode })` - ui/src/components/NexusOnboardingWizard.tsx contains `export function OnboardingWizard()` - TypeScript compilation exits 0 with no errors NexusOnboardingWizard is a 3-step wizard (hardware detection, mode selection, root directory). Step indicator shows current step. Back button works on steps 2-3. Mode is persisted on completion. All existing functionality preserved.
Task 3: Visual verification of onboarding wizard flow ui/src/components/NexusOnboardingWizard.tsx Human verifies the complete 3-step onboarding wizard flow by running the dev server and walking through each step visually. cd /opt/nexus && pnpm --filter ui exec tsc --noEmit Three-step onboarding wizard: hardware detection display, mode selector cards, and root directory input. Hardware probe runs automatically and shows GPU/RAM/unified memory info. Mode selector has three cards (Personal AI Assistant, Project Builder, Both) with "Both" pre-selected. Privacy copy shown for local-AI-capable hardware. 1. Start the dev server: `cd /opt/nexus && pnpm dev` 2. Open browser to http://localhost:3100 3. The onboarding wizard should appear (or trigger it by navigating to /onboarding) 4. **Step 1 — Hardware Detection:** - Verify skeleton loading state briefly appears - Verify hardware info renders (RAM, GPU/unified memory as appropriate) - If on a machine with GPU or Apple Silicon: verify "Local AI (recommended for privacy)" copy appears - If CPU-only: verify "Slower than GPU-accelerated models" warning appears - Click "Continue" 5. **Step 2 — Mode Selection:** - Verify "Choose your mode" heading - Verify "Both (recommended)" is pre-selected with blue border - Click a different mode — verify selection moves - Verify step indicator shows "Step 2 of 3" - Click "Continue" 6. **Step 3 — Root Directory:** - Verify existing root directory input appears - Verify "Back" button takes you back to step 2 - Complete the form and submit 7. Verify the wizard closes and navigates to dashboard Type "approved" or describe issues Human confirms wizard renders correctly across all 3 steps with proper copy, styling, and navigation.
TypeScript compilation: ```bash cd /opt/nexus && pnpm --filter ui exec tsc --noEmit ``` Server tests: ```bash cd /opt/nexus && pnpm --filter server test --run -- 30-hardware-detection ``` Dev server runs without errors: ```bash cd /opt/nexus && pnpm dev ``` 1. TypeScript compiles cleanly for both server and ui packages 2. ModeSelector renders three cards with correct copy and selection styling 3. HardwareSummaryStep shows tier-appropriate labels and privacy frame 4. NexusOnboardingWizard flows through 3 steps with back navigation 5. Selected mode is persisted to data/nexus-settings.json on wizard completion 6. Human verification confirms visual correctness After completion, create `.planning/phases/30-hardware-detection-mode-selection/30-02-SUMMARY.md`