From 7a9cd68ea88f075099abeb0f1d5966392d315330 Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Wed, 29 Apr 2026 16:16:53 -0700 Subject: [PATCH] =?UTF-8?q?feat(project-creation):=203-path=20wizard=20?= =?UTF-8?q?=E2=80=94=20Build=20/=20OSS=20/=20Import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User feedback: the previous flow was a single-screen "name + audience" dialog that gave AI no context about what the user actually wanted to make. That worked for the demo but produced messy projects in practice because everything was decided after the fact in chat. The new flow asks the user one human question first ("How would you like to begin?") and then captures the minimum context needed to seed the AI's first conversation in the project. Three paths, each is a 2-step setup screen with internal step dots: - Build your own idea — Step 1: name + audience. Step 2: free-text "what do you want to build". Becomes the project's vision and the AI's first-message context. - Run an open source tool — Step 1: name + audience. Step 2: segmented tabs to either (a) paste a GitHub link or (b) describe the kind of tool you want and have Vibn find one. Vision is set to either "Install and host this open-source project: " or "Find and install an open-source tool that fits this need: " so the AI knows which mode to operate in on first chat. - Import existing code — Step 1: name + audience + repo URL. Step 2: optional "what do you want to do with it" textarea. Public repos only for v1; private-repo OAuth lands later. Backend: - /api/projects/create now accepts and persists `creationMode` and `sourceData` on the project record under a `kickoff` blob: { mode, sourceData, vision, createdAt } The chat endpoint will read this on first turn to seed the AI with the user's stated intent rather than asking them to re-type it in chat. Cleanup: - Removed FreshIdeaSetup, CodeImportSetup, ChatImportSetup, MigrateSetup — replaced by BuildSetup, OssSetup, ImportSetup. - Removed the unused initialWorkspacePath prop from project-association-prompt (the new flow doesn't take it). - TypeSelector defaults are restored — the modal opens on the type-picker step now, not directly on a setup form. UI building blocks added to setup-shared: - TextArea (multi-line input) - StepDots (page indicator) - SegmentedTabs (generic-typed tab selector, used in OSS Step 2) - SecondaryButton (used as ← Back inside Step 2) Made-with: Cursor --- app/api/projects/create/route.ts | 12 ++ components/project-association-prompt.tsx | 1 - components/project-creation/BuildSetup.tsx | 132 ++++++++++++++ .../project-creation/ChatImportSetup.tsx | 85 --------- .../project-creation/CodeImportSetup.tsx | 105 ----------- .../project-creation/CreateProjectFlow.tsx | 35 ++-- .../project-creation/FreshIdeaSetup.tsx | 92 ---------- components/project-creation/ImportSetup.tsx | 145 +++++++++++++++ components/project-creation/MigrateSetup.tsx | 166 ----------------- components/project-creation/OssSetup.tsx | 172 ++++++++++++++++++ components/project-creation/TypeSelector.tsx | 82 +++------ components/project-creation/setup-shared.tsx | 115 ++++++++++++ 12 files changed, 623 insertions(+), 519 deletions(-) create mode 100644 components/project-creation/BuildSetup.tsx delete mode 100644 components/project-creation/ChatImportSetup.tsx delete mode 100644 components/project-creation/CodeImportSetup.tsx delete mode 100644 components/project-creation/FreshIdeaSetup.tsx create mode 100644 components/project-creation/ImportSetup.tsx delete mode 100644 components/project-creation/MigrateSetup.tsx create mode 100644 components/project-creation/OssSetup.tsx diff --git a/app/api/projects/create/route.ts b/app/api/projects/create/route.ts index 97c2e907..bc0e288e 100644 --- a/app/api/projects/create/route.ts +++ b/app/api/projects/create/route.ts @@ -78,6 +78,8 @@ export async function POST(request: Request) { githubRepoUrl, githubDefaultBranch, githubToken, + creationMode, + sourceData, } = body; // Check slug uniqueness @@ -239,6 +241,16 @@ export async function POST(request: Request) { // Coolify project — one per VIBN project, scopes all app services + DBs. // Apps are deployed on-demand via apps_create (no auto-scaffold). coolifyProjectUuid, + // How this project was created — drives the AI's first-chat seed. + // Shape: { mode: "build"|"oss"|"import", sourceData: {...} } where + // sourceData is the path-specific payload from the wizard. + creationMode: creationMode ?? null, + kickoff: creationMode ? { + mode: creationMode, + sourceData: sourceData ?? null, + vision: vision || null, + createdAt: now, + } : null, // Import metadata isImport: !!githubRepoUrl, importAnalysisStatus: githubRepoUrl ? 'pending' : null, diff --git a/components/project-association-prompt.tsx b/components/project-association-prompt.tsx index eff1a577..b5e581c7 100644 --- a/components/project-association-prompt.tsx +++ b/components/project-association-prompt.tsx @@ -298,7 +298,6 @@ export function ProjectAssociationPrompt({ workspace }: { workspace: string }) { setUnassociatedWorkspace(null); } }} - initialWorkspacePath={unassociatedWorkspace?.workspacePath} workspace={workspace} /> diff --git a/components/project-creation/BuildSetup.tsx b/components/project-creation/BuildSetup.tsx new file mode 100644 index 00000000..b2a38ea3 --- /dev/null +++ b/components/project-creation/BuildSetup.tsx @@ -0,0 +1,132 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { toast } from "sonner"; +import { JM } from "./modal-theme"; +import { + SetupHeader, FieldLabel, TextInput, TextArea, ForWhomSelector, + PrimaryButton, SecondaryButton, StepDots, type SetupProps, +} from "./setup-shared"; + +/** + * "Build your own idea" — two-step setup. + * Step 1: project name + audience. + * Step 2: describe the idea (free text). Becomes the seed message + * for the first AI conversation in the project. + */ +export function BuildSetup({ workspace, onClose, onBack }: SetupProps) { + const router = useRouter(); + const [step, setStep] = useState<0 | 1>(0); + const [name, setName] = useState(""); + const [forWhom, setForWhom] = useState<"personal" | "client">("personal"); + const [idea, setIdea] = useState(""); + const [loading, setLoading] = useState(false); + + const canContinue = name.trim().length > 0; + const canCreate = idea.trim().length > 4; + + const handleCreate = async () => { + if (!canCreate) return; + setLoading(true); + try { + const res = await fetch("/api/projects/create", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + projectName: name.trim(), + projectType: "web-app", + slug: name.toLowerCase().replace(/[^a-z0-9]+/g, "-"), + vision: idea.trim(), + product: { name: name.trim(), isForClient: forWhom === "client" }, + creationMode: "build", + sourceData: { idea: idea.trim() }, + }), + }); + if (!res.ok) { + const err = await res.json(); + toast.error(err.error || "Failed to create project"); + return; + } + const data = await res.json(); + onClose(); + router.push(`/${workspace}/project/${data.projectId}/overview`); + } catch { + toast.error("Something went wrong"); + } finally { + setLoading(false); + } + }; + + return ( +
+ setStep(0)} onClose={onClose} + /> + + {step === 0 && ( + <> + Project name + { if (e.key === "Enter" && canContinue) setStep(1); }} + autoFocus + /> + + + + setStep(1)} disabled={!canContinue}> + Next → + + } /> + + )} + + {step === 1 && ( + <> + What do you want to build? +