diff --git a/ui/src/components/onboarding/OnboardingSummaryStep.test.tsx b/ui/src/components/onboarding/OnboardingSummaryStep.test.tsx
new file mode 100644
index 00000000..487bf1cb
--- /dev/null
+++ b/ui/src/components/onboarding/OnboardingSummaryStep.test.tsx
@@ -0,0 +1,99 @@
+// @vitest-environment jsdom
+// [nexus] Unit tests for OnboardingSummaryStep component
+import { describe, it, expect, vi, afterEach } from "vitest";
+import { render, screen, fireEvent, cleanup } from "@testing-library/react";
+import { renderToStaticMarkup } from "react-dom/server";
+import { OnboardingSummaryStep } from "./OnboardingSummaryStep";
+import type { HardwareInfo } from "@/api/hardware";
+
+afterEach(() => {
+ cleanup();
+});
+
+const mockHardwareInfo: HardwareInfo = {
+ totalGb: 16,
+ freeGb: 8,
+ usableGb: 12,
+ platform: "darwin",
+ gpuName: null,
+ gpuVramGb: null,
+ unifiedMemory: true,
+ hardwareTier: "apple_silicon",
+ cpuModel: "Apple M2",
+};
+
+const defaultProps = {
+ hardwareInfo: mockHardwareInfo,
+ selectedMode: "both" as const,
+ providerLabel: "Puter (free, zero-config)",
+ rootDir: "~/projects/test",
+ loading: false,
+ error: null,
+ onStartChat: vi.fn(),
+ onBack: vi.fn(),
+};
+
+describe("OnboardingSummaryStep", () => {
+ it("renders all summary rows with provided data", () => {
+ const html = renderToStaticMarkup(
+
+ );
+
+ expect(html).toContain("Hardware");
+ expect(html).toContain("Apple Silicon");
+ expect(html).toContain("Mode");
+ expect(html).toContain("Both (recommended)");
+ expect(html).toContain("Provider");
+ expect(html).toContain("Puter (free, zero-config)");
+ expect(html).toContain("Root directory");
+ expect(html).toContain("~/projects/test");
+ });
+
+ it("hides root directory row when rootDir is empty string", () => {
+ const html = renderToStaticMarkup(
+
+ );
+
+ expect(html).not.toContain("Root directory");
+ });
+
+ it("shows error message when error prop is non-null", () => {
+ const html = renderToStaticMarkup(
+
+ );
+
+ expect(html).toContain("Setup failed. Please try again.");
+ });
+
+ it("calls onStartChat when 'Start chatting' button is clicked", () => {
+ const onStartChat = vi.fn();
+ render();
+
+ const buttons = screen.getAllByRole("button");
+ const startBtn = buttons.find((b) => b.textContent?.includes("Start chatting"));
+ expect(startBtn).toBeTruthy();
+ fireEvent.click(startBtn!);
+
+ expect(onStartChat).toHaveBeenCalledOnce();
+ });
+
+ it("disables 'Start chatting' button when loading is true", () => {
+ render();
+
+ const buttons = screen.getAllByRole("button");
+ const startBtn = buttons.find((b) => b.textContent?.includes("Setting up"));
+ expect(startBtn).toBeTruthy();
+ expect(startBtn).toHaveProperty("disabled", true);
+ });
+
+ it("shows 'Setting up...' text when loading is true", () => {
+ const html = renderToStaticMarkup(
+
+ );
+
+ expect(html).toContain("Setting up...");
+ });
+});
diff --git a/ui/src/components/onboarding/OnboardingSummaryStep.tsx b/ui/src/components/onboarding/OnboardingSummaryStep.tsx
new file mode 100644
index 00000000..44ce4b25
--- /dev/null
+++ b/ui/src/components/onboarding/OnboardingSummaryStep.tsx
@@ -0,0 +1,129 @@
+// [nexus] Summary screen for onboarding wizard step 5 — read-only review before starting
+import type { HardwareInfo, HardwareTier, NexusMode } from "@/api/hardware";
+import { Button } from "@/components/ui/button";
+
+interface OnboardingSummaryStepProps {
+ hardwareInfo: HardwareInfo | undefined;
+ selectedMode: NexusMode;
+ providerLabel: string;
+ rootDir: string;
+ loading: boolean;
+ error: string | null;
+ onStartChat: () => void;
+ onBack: () => void;
+}
+
+interface SummaryRowProps {
+ label: string;
+ value: string;
+ mono?: boolean;
+}
+
+function SummaryRow({ label, value, mono }: SummaryRowProps) {
+ return (
+
+ {label}
+ {value}
+
+ );
+}
+
+const HARDWARE_TIER_LABELS: Record = {
+ gpu: "GPU",
+ apple_silicon: "Apple Silicon",
+ cpu_only: "CPU Only",
+};
+
+const MODE_LABELS: Record = {
+ personal_ai: "Personal AI Assistant",
+ project_builder: "Project Builder",
+ both: "Both (recommended)",
+};
+
+export function OnboardingSummaryStep({
+ hardwareInfo,
+ selectedMode,
+ providerLabel,
+ rootDir,
+ loading,
+ error,
+ onStartChat,
+ onBack,
+}: OnboardingSummaryStepProps) {
+ const hardwareLabel = hardwareInfo
+ ? (HARDWARE_TIER_LABELS[hardwareInfo.hardwareTier] ?? "Unknown")
+ : "Unknown";
+
+ const modeLabel = MODE_LABELS[selectedMode];
+
+ return (
+
+
+
Ready to go
+
Review your setup before starting.
+
+
+ {/* Summary card */}
+
+
+
+
+ {rootDir && }
+
+
+ {/* Error message */}
+ {error && (
+
+ {error}
+
+ )}
+
+ {/* Actions */}
+
+
+
+
+
+ );
+}