From a35fac7281054228d67d4dfc8c88952e83e3e19f Mon Sep 17 00:00:00 2001 From: scotttong Date: Tue, 17 Mar 2026 16:22:15 -0700 Subject: [PATCH] experiment: redesign onboarding wizard with 6-step mission-driven flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the old 4-step wizard (Company → Agent → Task → Launch) with a new 6-step flow that puts the user in control of key moments: 1. Define your mission (required, with questionnaire or direct input) 2. Launch your company! (celebration moment) 3. Bring your CEO to life (agent creation, reframed) 4. Chat with your CEO (placeholder for hiring plan chat) 5. Review hiring plan (placeholder for editable role cards) 6. Make your first hires (summary + task creation) Key changes: - Mission/goal is now mandatory (was optional) - Two paths to define mission: "I know my mission" or "Help me figure it out" - Prompt chips for inspiration - Questionnaire generates a draft mission from 4 questions - Company launch is a celebrated moment before CEO creation - Step type expanded from 1-4 to 1-6 in DialogContext - Agent creation step reframed as "giving the CEO a heartbeat" - Steps 4-5 are placeholders for the chat and plan review components Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/src/components/OnboardingWizard.tsx | 452 +++++++++++++++++++------ ui/src/context/DialogContext.tsx | 2 +- 2 files changed, 349 insertions(+), 105 deletions(-) diff --git a/ui/src/components/OnboardingWizard.tsx b/ui/src/components/OnboardingWizard.tsx index 88e16d09..8d746be7 100644 --- a/ui/src/components/OnboardingWizard.tsx +++ b/ui/src/components/OnboardingWizard.tsx @@ -53,7 +53,7 @@ import { X } from "lucide-react"; -type Step = 1 | 2 | 3 | 4; +type Step = 1 | 2 | 3 | 4 | 5 | 6; type AdapterType = | "claude_local" | "codex_local" @@ -65,7 +65,22 @@ type AdapterType = | "http" | "openclaw_gateway"; -const DEFAULT_TASK_DESCRIPTION = `Setup yourself as the CEO. Use the ceo persona found here: +const MISSION_PROMPT_CHIPS = [ + "Build a SaaS product", + "Scale a content business", + "Launch a marketplace" +]; + +function buildMissionFromQuestionnaire(q1: string, q2: string, q3: string, q4: string): string { + const parts: string[] = []; + if (q1.trim()) parts.push(q1.trim()); + if (q2.trim()) parts.push(`We serve ${q2.trim().toLowerCase()}.`); + if (q3.trim()) parts.push(`Our biggest challenge is ${q3.trim().toLowerCase()}.`); + if (q4.trim()) parts.push(`Success looks like ${q4.trim().toLowerCase()}.`); + return parts.join(" "); +} + +const DEFAULT_TASK_DESCRIPTION = `Setup yourself as the CEO. Use the ceo persona found here: https://github.com/paperclipai/companies/blob/main/default/ceo/AGENTS.md @@ -91,6 +106,13 @@ export function OnboardingWizard() { // Step 1 const [companyName, setCompanyName] = useState(""); const [companyGoal, setCompanyGoal] = useState(""); + const [missionPath, setMissionPath] = useState<"direct" | "questionnaire" | null>(null); + const [missionConfirmed, setMissionConfirmed] = useState(false); + // Questionnaire answers + const [q1, setQ1] = useState(""); // What do you do? + const [q2, setQ2] = useState(""); // Who do you serve? + const [q3, setQ3] = useState(""); // Biggest bottleneck? + const [q4, setQ4] = useState(""); // What would success look like? // Step 2 const [agentName, setAgentName] = useState("CEO"); @@ -158,8 +180,8 @@ export function OnboardingWizard() { // Resize textarea when step 3 is shown or description changes useEffect(() => { - if (step === 3) autoResizeTextarea(); - }, [step, taskDescription, autoResizeTextarea]); + // Auto-resize removed — task description textarea no longer used in onboarding + }, [step, autoResizeTextarea]); const { data: adapterModels, @@ -171,7 +193,7 @@ export function OnboardingWizard() { ? queryKeys.agents.adapterModels(createdCompanyId, adapterType) : ["agents", "none", "adapter-models", adapterType], queryFn: () => agentsApi.adapterModels(createdCompanyId!, adapterType), - enabled: Boolean(createdCompanyId) && onboardingOpen && step === 2 + enabled: Boolean(createdCompanyId) && onboardingOpen && step === 3 }); const isLocalAdapter = adapterType === "claude_local" || @@ -192,7 +214,7 @@ export function OnboardingWizard() { : "claude"); useEffect(() => { - if (step !== 2) return; + if (step !== 3) return; setAdapterEnvResult(null); setAdapterEnvError(null); }, [step, adapterType, cwd, model, command, args, url]); @@ -249,6 +271,12 @@ export function OnboardingWizard() { setError(null); setCompanyName(""); setCompanyGoal(""); + setMissionPath(null); + setMissionConfirmed(false); + setQ1(""); + setQ2(""); + setQ3(""); + setQ4(""); setAgentName("CEO"); setAdapterType("claude_local"); setCwd(""); @@ -351,20 +379,18 @@ export function OnboardingWizard() { setSelectedCompanyId(company.id); queryClient.invalidateQueries({ queryKey: queryKeys.companies.all }); - if (companyGoal.trim()) { - const parsedGoal = parseOnboardingGoalInput(companyGoal); - await goalsApi.create(company.id, { - title: parsedGoal.title, - ...(parsedGoal.description - ? { description: parsedGoal.description } - : {}), - level: "company", - status: "active" - }); - queryClient.invalidateQueries({ - queryKey: queryKeys.goals.list(company.id) - }); - } + const parsedGoal = parseOnboardingGoalInput(companyGoal); + await goalsApi.create(company.id, { + title: parsedGoal.title, + ...(parsedGoal.description + ? { description: parsedGoal.description } + : {}), + level: "company", + status: "active" + }); + queryClient.invalidateQueries({ + queryKey: queryKeys.goals.list(company.id) + }); setStep(2); } catch (err) { @@ -436,7 +462,7 @@ export function OnboardingWizard() { queryClient.invalidateQueries({ queryKey: queryKeys.agents.list(createdCompanyId) }); - setStep(3); + setStep(4); } catch (err) { setError(err instanceof Error ? err.message : "Failed to create agent"); } finally { @@ -539,10 +565,12 @@ export function OnboardingWizard() { function handleKeyDown(e: React.KeyboardEvent) { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); - if (step === 1 && companyName.trim()) handleStep1Next(); - else if (step === 2 && agentName.trim()) handleStep2Next(); - else if (step === 3 && taskTitle.trim()) handleStep3Next(); - else if (step === 4) handleLaunch(); + if (step === 1 && companyName.trim() && companyGoal.trim()) handleStep1Next(); + else if (step === 2) setStep(3); + else if (step === 3 && agentName.trim()) handleStep2Next(); + else if (step === 4) setStep(5); + else if (step === 5) setStep(6); + else if (step === 6) handleLaunch(); } } @@ -582,10 +610,12 @@ export function OnboardingWizard() {
{( [ - { step: 1 as Step, label: "Company", icon: Building2 }, - { step: 2 as Step, label: "Agent", icon: Bot }, - { step: 3 as Step, label: "Task", icon: ListTodo }, - { step: 4 as Step, label: "Launch", icon: Rocket } + { step: 1 as Step, label: "Mission", icon: Building2 }, + { step: 2 as Step, label: "Launch", icon: Rocket }, + { step: 3 as Step, label: "CEO", icon: Bot }, + { step: 4 as Step, label: "Chat", icon: Sparkles }, + { step: 5 as Step, label: "Plan", icon: ListTodo }, + { step: 6 as Step, label: "Hire", icon: Bot } ] as const ).map(({ step: s, label, icon: Icon }) => (
-

Name your company

+

Define your mission

- This is the organization your agents will work for. + Your mission drives everything — your CEO, your hires, + and the work your company will do.

@@ -638,37 +669,219 @@ export function OnboardingWizard() { autoFocus /> -
-
+ )} + + {/* Direct mission input */} + {missionPath === "direct" && ( +
+
+ +