diff --git a/.changeset/fn-7433-onboarding-git-setup.md b/.changeset/fn-7433-onboarding-git-setup.md new file mode 100644 index 0000000000..aaed5f19e2 --- /dev/null +++ b/.changeset/fn-7433-onboarding-git-setup.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Add explicit onboarding choices to use, initialize, or clone a git repository. +category: feature +dev: Setup wizard now sends gitSetupMode while preserving cloneUrl-only project registration clients. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 17cbe56c15..7c4469ccc1 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -778,6 +778,16 @@ Memory view provides a multi-file editor for project and daily memory files. Its  +## Setup Wizard Project Registration + +First-run setup and embedded project setup both expose a **Repository setup** section before path entry: + +- **Use Existing Directory** registers an existing git repository or workspace root. Workspace detection and the workspace-mode checkbox only appear in this mode. +- **Initialize New Repository** registers the selected local folder and relies on the server-side project registration path to run `git init` if the folder is not already a git repository. +- **Clone Git Repository** requires a non-blank remote URL and an absolute destination path. Fusion runs `git clone` with argument-vector execution, requires the destination to be absent or empty, cleans up a newly-created failed destination best-effort, then registers the cloned folder. + +Advanced setup remains limited to runtime node and isolation-mode choices, so repository mode selection is not hidden behind the advanced panel. + ## Agents View Agent list and detail surfaces now surface pending approvals per agent: diff --git a/docs/getting-started.md b/docs/getting-started.md index 14dbcb4ec2..64757167e4 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -90,11 +90,15 @@ Start the dashboard: fn dashboard ``` -On first launch, Fusion opens an onboarding wizard with three steps: +On first launch, Fusion opens an onboarding wizard with guided setup steps: 1. **AI Setup** — choose a provider and authenticate (you only need one to start). Anthropic/Claude and OpenAI Codex use a pasted authorization-code OAuth flow in onboarding and Settings (sign in, then paste the final redirect URL or code back into Fusion), and Fusion warns before login so you remember to copy the browser address bar URL before the redirect tab appears to fail. After the initial Claude OAuth login, Fusion normally refreshes the OAuth credential automatically with the stored refresh token when the access token expires, so repeated manual re-login is not usually required. **Anthropic — via Claude CLI** remains available as a separate optional path. Deprecated Google Gemini CLI / Antigravity entries are hidden; Google/Gemini API key, Google Generative AI, Vertex, and Cloud Code options remain available. -2. **GitHub (Optional)** — connect GitHub for issue import and PR workflows -3. **First Task** — create your first task or import one from GitHub +2. **GitHub (Optional)** — connect GitHub for issue import and PR workflows. +3. **Project Setup** — choose how Fusion should prepare a repository: + - **Use Existing Directory** registers a folder that is already a git repository or a workspace root with detected sub-repositories. + - **Initialize New Repository** registers an existing local folder and lets the server run `git init` during registration when the folder is not already a git repository. + - **Clone Git Repository** runs `git clone` from a remote URL into an empty or absent destination directory, then registers the cloned folder. Fusion rejects blank clone URLs and populated destinations. +4. **First Task** — create your first task or import one from GitHub. The wizard is dismissible and non-blocking. You can skip it and continue using Fusion, then reopen it later from **Settings → Authentication**. diff --git a/packages/dashboard/app/api/legacy.ts b/packages/dashboard/app/api/legacy.ts index e8e61006e9..e4e10fe98d 100644 --- a/packages/dashboard/app/api/legacy.ts +++ b/packages/dashboard/app/api/legacy.ts @@ -6981,6 +6981,7 @@ export interface ProjectCreateInput { path: string; isolationMode?: "in-process" | "child-process"; nodeId?: string; + gitSetupMode?: "existing" | "init" | "clone"; cloneUrl?: string; workspaceMode?: boolean; taskPrefix?: string; diff --git a/packages/dashboard/app/components/SetupWizardModal.css b/packages/dashboard/app/components/SetupWizardModal.css index 0dea1b3602..cb930cad92 100644 --- a/packages/dashboard/app/components/SetupWizardModal.css +++ b/packages/dashboard/app/components/SetupWizardModal.css @@ -137,17 +137,51 @@ margin: 0 0 var(--space-xl); } +.setup-wizard-repository-section { + display: flex; + flex-direction: column; + gap: var(--space-sm); + margin-bottom: var(--space-lg); + padding: var(--space-md); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + background: var(--card); +} + +.setup-wizard-section-heading { + display: flex; + flex-direction: column; + gap: calc(var(--space-xs) / 2); +} + +.setup-wizard-section-heading h3, +.setup-wizard-section-heading p { + margin: 0; +} + +.setup-wizard-section-heading h3 { + color: var(--text); + font-size: var(--font-size-md); + font-weight: 700; +} + +.setup-wizard-section-heading p { + color: var(--text-muted); + font-size: var(--font-size-sm); + line-height: 1.45; +} + .setup-wizard-mode-switch { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-sm); - margin: 0 0 var(--space-lg); + margin: 0; padding: 0; border: 0; } .setup-wizard-mode-switch legend { - font-size: 13px; + font-size: var(--font-size-sm); font-weight: 600; color: var(--text); margin-bottom: var(--space-xs); @@ -155,7 +189,7 @@ .setup-wizard-mode-option { display: flex; - align-items: center; + align-items: flex-start; gap: var(--space-sm); padding: var(--space-sm) var(--space-md); border: 1px solid var(--border); @@ -170,9 +204,30 @@ } .setup-wizard-mode-option input { + flex: 0 0 auto; + margin-top: calc(var(--space-xs) / 2); accent-color: var(--todo); } +.setup-wizard-mode-option-copy { + display: flex; + flex-direction: column; + gap: calc(var(--space-xs) / 2); + min-width: 0; +} + +.setup-wizard-mode-option-copy strong { + color: var(--text); + font-size: var(--font-size-sm); + line-height: 1.3; +} + +.setup-wizard-mode-option-copy span { + color: var(--text-muted); + font-size: var(--font-size-xs); + line-height: 1.35; +} + .setup-wizard-mode-option:hover { border-color: var(--text-dim); } @@ -281,6 +336,12 @@ margin-bottom: 0; } +.setup-wizard-inline-spinner { + display: inline-block; + margin-right: var(--space-xs); + vertical-align: middle; +} + .setup-wizard-auth-token { display: flex; flex-direction: column; @@ -650,6 +711,14 @@ gap: var(--space-xs); } + .setup-wizard-mode-switch { + grid-template-columns: 1fr; + } + + .setup-wizard-repository-section { + padding: var(--space-sm); + } + .setup-wizard-agent-layout { grid-template-columns: 1fr; } diff --git a/packages/dashboard/app/components/SetupWizardModal.tsx b/packages/dashboard/app/components/SetupWizardModal.tsx index 89461dad2b..21df88e3f6 100644 --- a/packages/dashboard/app/components/SetupWizardModal.tsx +++ b/packages/dashboard/app/components/SetupWizardModal.tsx @@ -46,7 +46,7 @@ export interface SetupWizardModalProps { } type WizardStep = "manual" | "agent" | "complete"; -type ManualSetupMode = "existing" | "clone"; +type ManualSetupMode = "existing" | "init" | "clone"; type AgentOutcome = "created" | "skipped" | null; interface WizardState { @@ -189,6 +189,19 @@ export function SetupWizardModal({ } }, [state.manualMode]); + const handleSetupModeChange = useCallback((mode: ManualSetupMode) => { + setState((prev) => ({ + ...prev, + manualMode: mode, + manualCloneUrl: mode === "clone" ? prev.manualCloneUrl : "", + detectedRepos: mode === "existing" ? prev.detectedRepos : [], + workspaceMode: mode === "existing" ? prev.workspaceMode : false, + isDetectingWorkspace: false, + error: null, + })); + detectWorkspaceRequestId.current += 1; + }, []); + const handleManualRegister = useCallback(async () => { const trimmedPath = state.manualPath.trim(); const trimmedName = state.manualName.trim(); @@ -205,8 +218,9 @@ export function SetupWizardModal({ path: trimmedPath, isolationMode: state.manualIsolationMode, nodeId: state.manualNodeId || undefined, + gitSetupMode: state.manualMode, cloneUrl: state.manualMode === "clone" ? trimmedCloneUrl : undefined, - workspaceMode: state.workspaceMode, + workspaceMode: state.manualMode === "existing" ? state.workspaceMode : false, taskPrefix: state.manualTaskPrefix.trim() || undefined, }; @@ -314,7 +328,13 @@ export function SetupWizardModal({ if (!isOpen) return null; const isExistingMode = state.manualMode === "existing"; + const isInitMode = state.manualMode === "init"; const isCloneMode = state.manualMode === "clone"; + const setupModeSubmitLabel = isCloneMode + ? t("setup.cloneAndRegisterProject", "Clone and Register Project") + : isInitMode + ? t("setup.initializeAndRegisterProject", "Initialize and Register Project") + : t("setup.registerProject", "Register Project"); const hasPath = state.manualPath.trim().length > 0; const hasName = state.manualName.trim().length > 0; const hasCloneUrl = state.manualCloneUrl.trim().length > 0; @@ -390,6 +410,63 @@ export function SetupWizardModal({ {/* Manual Step */} {state.step === "manual" && (
{t("setup.repositorySetupDescription", "Choose how Fusion should prepare the project directory before registration.")}
+{isCloneMode - ? t("setup.clonePathHint", "Select or type an absolute destination path. Fusion will clone into this directory.") - : t("setup.projectPathHint", "Select or type the absolute path to your project")} + ? t("setup.clonePathHint", "Select or type an absolute destination path. Fusion will clone into this directory. The destination must be empty or absent.") + : isInitMode + ? t("setup.initPathHint", "Select or type an absolute path to an empty or non-git folder. Fusion will run git init during registration.") + : t("setup.projectPathHint", "Select or type the absolute path to an existing git repository or workspace root.")}
+ {t("setup.cloneGitHint", "Fusion will run git clone into the destination directory, then register that cloned folder.")} +
+
-
- {t("setup.cloneGitHint", "Fusion will run git clone into the destination directory, then register that cloned folder.")} -
-