// [nexus] Replacement onboarding wizard — single-step root directory flow // Exports `OnboardingWizard` to match the named import in App.tsx. // Wired via Vite alias: all imports of ./components/OnboardingWizard are // redirected here at build time; the original file is preserved for upstream rebase. import { useState, useEffect } from "react"; import { createPortal } from "react-dom"; // [nexus] use raw portal, not radix DialogPortal import { useLocation, useNavigate, useParams } from "@/lib/router"; import { VOCAB } from "@paperclipai/branding"; import { useQueryClient } from "@tanstack/react-query"; import { useCompany } from "../context/CompanyContext"; import { useDialog } from "../context/DialogContext"; import { companiesApi } from "../api/companies"; import { agentsApi } from "../api/agents"; import { queryKeys } from "../lib/queryKeys"; import { resolveRouteOnboardingOptions } from "../lib/onboarding-route"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { cn } from "../lib/utils"; // [nexus] Single-step onboarding wizard: root directory → workspace + PM + Engineer export function OnboardingWizard() { const { onboardingOpen, onboardingOptions, closeOnboarding } = useDialog(); const { companies, setSelectedCompanyId, loading: companiesLoading } = useCompany(); const queryClient = useQueryClient(); const navigate = useNavigate(); const location = useLocation(); const { companyPrefix } = useParams<{ companyPrefix?: string }>(); const [routeDismissed, setRouteDismissed] = useState(false); // Preserve wizard-show detection logic from the original OnboardingWizard const routeOnboardingOptions = companyPrefix && companiesLoading ? null : resolveRouteOnboardingOptions({ pathname: location.pathname, companyPrefix, companies, }); const effectiveOnboardingOpen = onboardingOpen || (routeOnboardingOptions !== null && !routeDismissed); useEffect(() => { setRouteDismissed(false); }, [location.pathname]); // Form state const [rootDir, setRootDir] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // Reset form when wizard closes useEffect(() => { if (!effectiveOnboardingOpen) { setRootDir(""); setError(null); setLoading(false); } }, [effectiveOnboardingOpen]); function handleClose() { setRouteDismissed(true); closeOnboarding(); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!rootDir.trim()) return; setLoading(true); setError(null); try { // Step 1: Create workspace (company) named after VOCAB.appName const company = await companiesApi.create({ name: VOCAB.appName }); setSelectedCompanyId(company.id); queryClient.invalidateQueries({ queryKey: queryKeys.companies.all }); const adapterConfig = { cwd: rootDir.trim() }; const runtimeConfig = { heartbeat: { enabled: true, intervalSec: 3600, wakeOnDemand: true, cooldownSec: 10, maxConcurrentRuns: 1, }, }; // Step 2: Create PM agent with role "ceo" for elevated permissions // (display label is "Project Manager" via AGENT_ROLE_LABELS; ceo/ bundle // has been rewritten with PM content in 04-01) await agentsApi.create(company.id, { name: "Project Manager", role: "ceo", adapterType: "claude_local", adapterConfig, runtimeConfig, }); // Step 3: Create Engineer agent await agentsApi.create(company.id, { name: "Engineer", role: "engineer", adapterType: "claude_local", adapterConfig, runtimeConfig, }); // Step 4: Create Generalist agent (non-code work: copy, research, docs) await agentsApi.create(company.id, { name: "Generalist", role: "general", adapterType: "claude_local", adapterConfig, runtimeConfig, metadata: { pendingSkillGroups: ["Creative"] }, }); queryClient.invalidateQueries({ queryKey: queryKeys.agents.list(company.id), }); // Navigate to dashboard — not an issue detail page closeOnboarding(); navigate(`/${company.issuePrefix}/dashboard`); } catch (err) { setError(err instanceof Error ? err.message : "Setup failed. Please try again."); setLoading(false); } } if (!effectiveOnboardingOpen) return null; return createPortal(
{/* Backdrop */}
{/* Card */}
{/* Header */}

Welcome to {VOCAB.appName}

Choose a project root directory. {VOCAB.appName} will set up a{" "} {VOCAB.ceo.toLowerCase()}, engineer, and generalist to start working.

{/* Form */}
setRootDir(e.target.value)} disabled={loading} autoFocus autoComplete="off" className="font-mono text-sm" />
{error && (

{error}

)}
, document.body // [nexus] portal to body, not radix DialogPortal ); }