diff --git a/ui/src/components/OnboardingWizard.tsx b/ui/src/components/OnboardingWizard.tsx
index edb2911f..27421ba1 100644
--- a/ui/src/components/OnboardingWizard.tsx
+++ b/ui/src/components/OnboardingWizard.tsx
@@ -35,6 +35,7 @@ import { AsciiArtAnimation } from "./AsciiArtAnimation";
import { ChoosePathButton } from "./PathInstructionsModal";
import { HintIcon } from "./agent-config-primitives";
import { OpenCodeLogoIcon } from "./OpenCodeLogoIcon";
+import { FrontDoor } from "./FrontDoor";
import {
Building2,
Bot,
@@ -58,7 +59,7 @@ import {
MessageSquare
} from "lucide-react";
-type Step = 1 | 2 | 3 | 4 | 5 | 6;
+type Step = 0 | 1 | 2 | 3 | 4 | 5 | 6;
type AdapterType =
| "claude_local"
| "codex_local"
@@ -348,13 +349,19 @@ export function OnboardingWizard() {
const queryClient = useQueryClient();
const navigate = useNavigate();
- const initialStep = onboardingOptions.initialStep ?? 1;
+ const initialStep = onboardingOptions.initialStep ?? 0;
const existingCompanyId = onboardingOptions.companyId;
// Restore saved state from localStorage (read once on mount)
const saved = useMemo(loadSavedState, []);
const [step, setStep] = useState((saved?.step as Step) ?? initialStep);
+ const [onboardingPath, setOnboardingPath] = useState<"create" | "grow" | null>((saved?.onboardingPath as "create" | "grow" | null) ?? null);
+
+ // "Grow existing" questionnaire fields
+ const [growWorkflows, setGrowWorkflows] = useState((saved?.growWorkflows as string) ?? "");
+ const [growPainPoints, setGrowPainPoints] = useState((saved?.growPainPoints as string) ?? "");
+ const [growAutomate, setGrowAutomate] = useState((saved?.growAutomate as string) ?? "");
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [modelOpen, setModelOpen] = useState(false);
@@ -452,6 +459,7 @@ export function OnboardingWizard() {
q1, q2, q3, q4, agentName, adapterType, cwd, model, command, args, url,
createdCompanyId, createdCompanyPrefix, createdAgentId,
planningTaskId, planContent, hiringRoles,
+ onboardingPath, growWorkflows, growPainPoints, growAutomate,
};
localStorage.setItem(ONBOARDING_STORAGE_KEY, JSON.stringify(state));
}, [
@@ -459,6 +467,7 @@ export function OnboardingWizard() {
q1, q2, q3, q4, agentName, adapterType, cwd, model, command, args, url,
createdCompanyId, createdCompanyPrefix, createdAgentId,
planningTaskId, planContent, hiringRoles,
+ onboardingPath, growWorkflows, growPainPoints, growAutomate,
]);
// Resize textarea when step 3 is shown or description changes
@@ -550,7 +559,11 @@ export function OnboardingWizard() {
function reset() {
localStorage.removeItem(ONBOARDING_STORAGE_KEY);
- setStep(1);
+ setStep(0);
+ setOnboardingPath(null);
+ setGrowWorkflows("");
+ setGrowPainPoints("");
+ setGrowAutomate("");
setLoading(false);
setError(null);
setCompanyName("");
@@ -590,6 +603,14 @@ export function OnboardingWizard() {
closeOnboarding();
}
+ function handleLaunchToChat() {
+ const prefix = createdCompanyPrefix;
+ const taskId = planningTaskId;
+ reset();
+ closeOnboarding();
+ navigate(prefix ? `/${prefix}/chat${taskId ? `?taskId=${taskId}` : ""}` : "/dashboard");
+ }
+
function buildAdapterConfig(): Record {
const adapter = getUIAdapter(adapterType);
const config = adapter.buildAdapterConfig({
@@ -680,7 +701,7 @@ export function OnboardingWizard() {
queryKey: queryKeys.goals.list(company.id)
});
- setStep(2);
+ setStep(2); // → CEO config (was celebration, now swapped)
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to create company");
} finally {
@@ -753,13 +774,21 @@ export function OnboardingWizard() {
// Create the planning task unassigned — the CEO only gets assigned
// when the user sends their first message (user controls initiation)
+ const isGrowPath = onboardingPath === "grow";
+ const growContext = isGrowPath
+ ? `\n\nExisting workflows: ${growWorkflows}\nPain points: ${growPainPoints}\nFirst automation priority: ${growAutomate}`
+ : "";
const planningIssue = await issuesApi.create(createdCompanyId, {
- title: "Build hiring plan with CEO",
- description: `Company mission: ${companyGoal}
+ title: isGrowPath ? "Plan AI agents for existing company" : "Strategy & hiring plan with CEO",
+ description: `Company mission: ${companyGoal}${growContext}
-Collaborate with the board to create a hiring plan for the company.
+You are the CEO of this company. The board (the user) has just appointed you. Your first conversation should focus on STRATEGY — ask the board about their vision, priorities, and constraints. DO NOT immediately create a hiring plan. Instead:
-IMPORTANT: When writing the hiring plan document, use this exact format for EACH role. Use ## headings for each role (e.g. "## 1. Role Name") and ### sub-headings for each section within the role:
+1. FIRST: Greet the board briefly, then ask strategic questions to understand their priorities. Have a real conversation.
+2. ONLY WHEN the board says they're ready (e.g. "let's build the plan", "get started", "hire the team"), THEN create the hiring plan document.
+3. When you do create the plan, save it as a document using the plan key.${isGrowPath ? "\n4. Focus on agents that address the existing pain points and automate current workflows." : ""}
+
+When writing the hiring plan document, use this exact format for EACH role. Use ## headings for each role (e.g. "## 1. Role Name") and ### sub-headings for each section within the role:
## 1. Role Name
### Summary
@@ -782,7 +811,8 @@ Follow this structure for every role in the plan.`,
});
setPlanningTaskId(planningIssue.id);
- setStep(4);
+ // Go to launch celebration step (step 3)
+ setStep(3);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to create agent");
} finally {
@@ -895,9 +925,10 @@ Follow this structure for every role in the plan.`,
function handleKeyDown(e: React.KeyboardEvent) {
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
+ if (step === 0) return; // front door requires click
if (step === 1 && companyName.trim() && companyGoal.trim()) handleStep1Next();
- else if (step === 2) setStep(3);
- else if (step === 3 && agentName.trim()) handleStep2Next();
+ else if (step === 2 && agentName.trim()) handleStep2Next();
+ else if (step === 3) handleLaunchToChat();
else if (step === 4) setStep(5);
else if (step === 5) setStep(6);
else if (step === 6) handleLaunch();
@@ -928,7 +959,18 @@ Follow this structure for every role in the plan.`,
Close
- {/* Left half — form */}
+ {/* Step 0: Front Door — full-screen choice */}
+ {step === 0 && (
+