"use client"; import { PROTOTYPE_ONBOARDING_MODAL_MIN_HEIGHT_CLASS, PrototypeToolDialog, } from "@prototype/components/platform-ui/prototype-tool-dialog"; import { PrototypeBootstrapOnboardingAgentContextStep } from "@prototype/components/shell/prototype-bootstrap-onboarding-agent-context-step"; import { PrototypeBootstrapOnboardingNameStep } from "@prototype/components/shell/prototype-bootstrap-onboarding-name-step"; import { PrototypeBootstrapOnboardingSourceStep } from "@prototype/components/shell/prototype-bootstrap-onboarding-source-step"; import { PrototypeBootstrapOnboardingWaitingStep } from "@prototype/components/shell/prototype-bootstrap-onboarding-waiting-step"; import { PrototypeBootstrapOnboardingWelcomeStep } from "@prototype/components/shell/prototype-bootstrap-onboarding-welcome-step"; import { Button } from "@prototype/components/ui/button"; import { buildBootstrapSetupCopyText } from "@prototype/lib/prototypes/bootstrap-setup-prompt"; import { resolveOnboardingSourceForSetup } from "@prototype/lib/prototypes/resolve-onboarding-source-for-setup"; import type { SourceDirectoryPick } from "@prototype/lib/prototypes/resolve-source-directory-pick"; import { usePersistedLocalString } from "@prototype/lib/prototypes/use-persisted-local-state"; import { useTrackEvent } from "@prototype/lib/prototypes/telemetry/prototype-telemetry-context"; import { BOOTSTRAP_PREVIEW_STEPS, type BootstrapPreviewStep, useBootstrapPreview, } from "@prototype/lib/prototypes/use-bootstrap-preview"; import { useWorkspaceProfile } from "@prototype/lib/prototypes/use-workspace-profile"; import useCopyToClipboard from "@prototype/lib/use-copy-to-clipboard"; import { cn } from "@prototype/lib/utils"; import { ArrowLeft, ArrowRight } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; const PROFILE_STORAGE_KEYS = { sourceName: "prototype-profile:source-name", sourceDescription: "prototype-profile:source-description", agentContext: "prototype-profile:agent-context", agentContextPath: "prototype-profile:agent-context-path", } as const; const ONBOARDING_STEP_CONTENT: Record< Exclude, { title: string; description?: string } > = { welcome: { title: "Welcome to Proto!", description: "Let's set up your workspace", }, "source-directory": { title: "Connect your source directory", description: "Drop your product repo folder. Proto links it and pulls your design system straight from the source — no export step.", }, name: { title: "What should we call you?", description: "Your name appears on comments you make.", }, "agent-context": { title: "Add your agent context", description: "Paste markdown or select AGENTS.md from disk. Proto includes the file's absolute path in the setup prompt.", }, "setup-complete": { title: "Set up your workspace", description: "Copy the setup prompt into your agent and it will link the source directory and load its design system.", }, "component-library": { title: "Waiting for workspace setup", description: "Run the copied prompt in your agent to finish linking your workspace.", }, "library-loading": { title: "Design system syncing", description: "New prototypes need styles and base components from the linked source. Browse existing prototypes while setup finishes.", }, }; type PrototypeBootstrapOnboardingModalProps = { /** Full linked SOURCE_PATH from the host — never the sidebar display name. */ currentSourcePath?: string; syncConfigPath?: string; designSystemPagePath?: string; }; export function PrototypeBootstrapOnboardingModal({ currentSourcePath, syncConfigPath, designSystemPagePath, }: PrototypeBootstrapOnboardingModalProps) { const { bootstrapStep, advanceBootstrapStep, retreatBootstrapStep, isOnboardingModalVisible, } = useBootstrapPreview(); const [selectedDirectory, setSelectedDirectory] = useState(null); const workspaceProfile = useWorkspaceProfile(); const [userName, setUserName] = useState(workspaceProfile.userName); const [isContinuing, setIsContinuing] = useState(false); const [hasCopiedSetupPrompt, setHasCopiedSetupPrompt] = useState(false); const { copy, icon, isCopied } = useCopyToClipboard(); const track = useTrackEvent(); const { value: sourceName, updateValue: setSourceName } = usePersistedLocalString(PROFILE_STORAGE_KEYS.sourceName, ""); const { updateValue: setSourceDescription } = usePersistedLocalString(PROFILE_STORAGE_KEYS.sourceDescription, ""); const { value: agentContext, updateValue: setAgentContext } = usePersistedLocalString(PROFILE_STORAGE_KEYS.agentContext, ""); const { value: agentContextPath, updateValue: setAgentContextPath } = usePersistedLocalString(PROFILE_STORAGE_KEYS.agentContextPath, ""); useEffect(() => { setUserName(workspaceProfile.userName); }, [workspaceProfile.userName]); const advanceOnboardingStep = useCallback(() => { advanceBootstrapStep(); }, [advanceBootstrapStep]); const retreatOnboardingStep = useCallback(() => { retreatBootstrapStep(); }, [retreatBootstrapStep]); const onboardingStep = bootstrapStep === "normal" || bootstrapStep === "library-loading" ? null : bootstrapStep; useEffect(() => { if (onboardingStep) { track("modal.opened", { modal: "onboarding", step: onboardingStep }); } }, [onboardingStep, track]); const stepIndex = onboardingStep ? BOOTSTRAP_PREVIEW_STEPS.indexOf(onboardingStep) : -1; const canGoBack = stepIndex > 0 && onboardingStep !== "component-library"; const isWaitingForOnboarded = onboardingStep === "component-library"; const onboardingSource = useMemo( () => resolveOnboardingSourceForSetup({ selectedDirectory, }), [selectedDirectory], ); const handleContinue = useCallback(() => { if (!onboardingStep || isContinuing) return; setIsContinuing(true); try { if (onboardingStep === "source-directory") { if (!selectedDirectory?.name.trim()) { return; } const nextSourceName = selectedDirectory.name.trim(); setSourceName(nextSourceName); setSourceDescription( selectedDirectory.path?.trim() ? selectedDirectory.path.trim() : `Source folder "${nextSourceName}" selected from this machine.`, ); } else if (onboardingStep === "name") { if (!userName.trim()) return; } track("onboarding.step_advanced", { step: onboardingStep }); advanceOnboardingStep(); } finally { setIsContinuing(false); } }, [ advanceOnboardingStep, isContinuing, onboardingStep, selectedDirectory, setSourceDescription, setSourceName, track, userName, ]); const handleBack = useCallback(() => { retreatOnboardingStep(); }, [retreatOnboardingStep]); const hasSourceSelection = Boolean(selectedDirectory?.name.trim()); const continueDisabled = isContinuing || (onboardingStep === "name" && !userName.trim()) || (onboardingStep === "source-directory" && !hasSourceSelection); const continueLabel = onboardingStep === "setup-complete" ? isCopied ? "Copied prompt" : "Copy setup prompt" : onboardingStep === "agent-context" ? "Continue" : onboardingStep === "welcome" ? "Get Started" : "Continue"; const handlePrimaryAction = useCallback(async () => { if (onboardingStep === "component-library") { if (isContinuing) return; setIsContinuing(true); try { const origin = typeof window !== "undefined" ? window.location.origin : undefined; await copy( buildBootstrapSetupCopyText({ sourcePath: onboardingSource.absolutePath, sourceFolderName: onboardingSource.folderName, currentSourcePath, userName, agentContext, agentContextPath: agentContextPath.trim() || undefined, syncConfigPath, designSystemPagePath, origin, workspaceProfile, }), ); setHasCopiedSetupPrompt(true); track("modal.prompt_copied", { modal: "onboarding-setup-waiting" }); } catch { toast.error("Copy error", { description: "Failed to copy the setup prompt", }); } finally { setIsContinuing(false); } return; } if (onboardingStep === "setup-complete") { if (isContinuing) return; setIsContinuing(true); try { const origin = typeof window !== "undefined" ? window.location.origin : undefined; await copy( buildBootstrapSetupCopyText({ sourcePath: onboardingSource.absolutePath, sourceFolderName: onboardingSource.folderName, currentSourcePath, userName, agentContext, agentContextPath: agentContextPath.trim() || undefined, syncConfigPath, designSystemPagePath, origin, workspaceProfile, }), ); setHasCopiedSetupPrompt(true); track("modal.prompt_copied", { modal: "onboarding-setup-complete" }); advanceOnboardingStep(); } catch { toast.error("Copy error", { description: "Failed to copy the setup prompt", }); } finally { setIsContinuing(false); } return; } handleContinue(); }, [ advanceOnboardingStep, agentContext, agentContextPath, designSystemPagePath, copy, currentSourcePath, handleContinue, isContinuing, onboardingSource, onboardingStep, syncConfigPath, track, userName, workspaceProfile, ]); if (!isOnboardingModalVisible || !onboardingStep) { return null; } const stepContent = ONBOARDING_STEP_CONTENT[onboardingStep]; const title = stepContent.title; const description = stepContent.description; const isWelcomeStep = onboardingStep === "welcome"; const dialogDescription = isWelcomeStep || !description ? undefined : description; return ( {}} dismissible={false} overlayClassName="tool-dialog-overlay-opaque" className={PROTOTYPE_ONBOARDING_MODAL_MIN_HEIGHT_CLASS} title={title} description={dialogDescription} hideHeader={isWelcomeStep} bodyClassName={cn( "gap-4", isWelcomeStep && "flex flex-1 items-center justify-center px-0 py-0", isWaitingForOnboarded && "flex flex-1", )} footerClassName={ canGoBack ? "justify-between" : "justify-end" } footer={isWelcomeStep ? undefined : ( <> {canGoBack ? ( ) : null} )} > {onboardingStep === "welcome" ? ( { void handlePrimaryAction(); }} continueDisabled={continueDisabled} isContinuing={isContinuing} /> ) : null} {onboardingStep === "source-directory" ? ( ) : null} {onboardingStep === "name" ? ( ) : null} {onboardingStep === "agent-context" ? ( ) : null} {isWaitingForOnboarded ? ( ) : null} ); }