diff --git a/backend/src/models/config.rs b/backend/src/models/config.rs index b64d4506..ad5309b5 100644 --- a/backend/src/models/config.rs +++ b/backend/src/models/config.rs @@ -9,6 +9,7 @@ pub struct Config { pub theme: ThemeMode, pub executor: ExecutorConfig, pub disclaimer_acknowledged: bool, + pub onboarding_acknowledged: bool, pub sound_alerts: bool, pub push_notifications: bool, pub editor: EditorConfig, @@ -48,6 +49,7 @@ impl Default for Config { theme: ThemeMode::System, executor: ExecutorConfig::Claude, disclaimer_acknowledged: false, + onboarding_acknowledged: false, sound_alerts: true, push_notifications: true, editor: EditorConfig::default(), diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 78e71b6c..cd4f7336 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,18 +6,23 @@ import { ProjectTasks } from "@/pages/project-tasks"; import { TaskAttemptComparePage } from "@/pages/task-attempt-compare"; import { Settings } from "@/pages/Settings"; import { DisclaimerDialog } from "@/components/DisclaimerDialog"; +import { OnboardingDialog } from "@/components/OnboardingDialog"; import { ConfigProvider, useConfig } from "@/components/config-provider"; import { ThemeProvider } from "@/components/theme-provider"; -import type { Config, ApiResponse } from "shared/types"; +import type { Config, ApiResponse, ExecutorConfig, EditorType } from "shared/types"; function AppContent() { const { config, updateConfig, loading } = useConfig(); const [showDisclaimer, setShowDisclaimer] = useState(false); + const [showOnboarding, setShowOnboarding] = useState(false); const showNavbar = true; useEffect(() => { if (config) { setShowDisclaimer(!config.disclaimer_acknowledged); + if (config.disclaimer_acknowledged) { + setShowOnboarding(!config.onboarding_acknowledged); + } } }, [config]); @@ -39,6 +44,38 @@ function AppContent() { if (data.success) { setShowDisclaimer(false); + setShowOnboarding(!config.onboarding_acknowledged); + } + } catch (err) { + console.error("Error saving config:", err); + } + }; + + const handleOnboardingComplete = async (onboardingConfig: { executor: ExecutorConfig; editor: { editor_type: EditorType; custom_command: string | null } }) => { + if (!config) return; + + const updatedConfig = { + ...config, + onboarding_acknowledged: true, + executor: onboardingConfig.executor, + editor: onboardingConfig.editor, + }; + + updateConfig(updatedConfig); + + try { + const response = await fetch("/api/config", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(updatedConfig), + }); + + const data: ApiResponse = await response.json(); + + if (data.success) { + setShowOnboarding(false); } } catch (err) { console.error("Error saving config:", err); @@ -63,6 +100,10 @@ function AppContent() { open={showDisclaimer} onAccept={handleDisclaimerAccept} /> + {showNavbar && }
diff --git a/frontend/src/components/OnboardingDialog.tsx b/frontend/src/components/OnboardingDialog.tsx new file mode 100644 index 00000000..8c5e7f8b --- /dev/null +++ b/frontend/src/components/OnboardingDialog.tsx @@ -0,0 +1,145 @@ +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Sparkles, Code } from "lucide-react"; +import type { EditorType, ExecutorConfig } from "shared/types"; +import { EXECUTOR_TYPES, EDITOR_TYPES, EXECUTOR_LABELS, EDITOR_LABELS } from "shared/types"; + +interface OnboardingDialogProps { + open: boolean; + onComplete: (config: { executor: ExecutorConfig; editor: { editor_type: EditorType; custom_command: string | null } }) => void; +} + +export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) { + const [executor, setExecutor] = useState({ type: "claude" }); + const [editorType, setEditorType] = useState("vscode"); + const [customCommand, setCustomCommand] = useState(""); + + const handleComplete = () => { + onComplete({ + executor, + editor: { + editor_type: editorType, + custom_command: editorType === "custom" ? customCommand || null : null, + }, + }); + }; + + const isValid = editorType !== "custom" || (editorType === "custom" && customCommand.trim() !== ""); + + return ( + {}}> + + +
+ + Welcome to Mission Control +
+ + Let's set up your coding preferences. You can always change these later in Settings. + +
+ +
+ + + + + Choose Your Coding Agent + + + +
+ + +

+ {executor.type === "claude" && "Claude will handle your coding tasks with advanced reasoning."} + {executor.type === "amp" && "Amp provides powerful code generation and debugging capabilities."} + {executor.type === "echo" && "Echo is a simple testing executor that repeats your commands."} +

+
+
+
+ + + + + + Choose Your Code Editor + + + +
+ + +

+ This editor will be used to open task attempts and project files. +

+
+ + {editorType === "custom" && ( +
+ + setCustomCommand(e.target.value)} + /> +

+ Enter the command to run your custom editor. Use spaces for arguments (e.g., "code --wait"). +

+
+ )} +
+
+
+ + + + +
+
+ ); +} diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index fe9f822e..bf92c66f 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -8,6 +8,7 @@ import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { Loader2 } from "lucide-react"; import type { ThemeMode, EditorType } from "shared/types"; +import { EXECUTOR_TYPES, EDITOR_TYPES, EXECUTOR_LABELS, EDITOR_LABELS } from "shared/types"; import { useTheme } from "@/components/theme-provider"; import { useConfig } from "@/components/config-provider"; @@ -51,6 +52,12 @@ export function Settings() { updateConfig({ disclaimer_acknowledged: false }); }; + const resetOnboarding = async () => { + if (!config) return; + + updateConfig({ onboarding_acknowledged: false }); + }; + if (loading) { return (
@@ -145,9 +152,11 @@ export function Settings() { - Claude - Amp - Echo + {EXECUTOR_TYPES.map((type) => ( + + {EXECUTOR_LABELS[type]} + + ))}

@@ -181,12 +190,11 @@ export function Settings() { - VS Code - Cursor - Windsurf - IntelliJ IDEA - Zed - Custom Command + {EDITOR_TYPES.map((type) => ( + + {EDITOR_LABELS[type]} + + ))}

@@ -260,7 +268,7 @@ export function Settings() { Manage safety warnings and acknowledgments. - +

@@ -284,6 +292,29 @@ export function Settings() { Resetting the disclaimer will require you to acknowledge the safety warning again on next app start.

+
+
+
+ +

+ {config.onboarding_acknowledged + ? "You have completed the onboarding process." + : "The onboarding process has not been completed."} +

+
+ +
+

+ Resetting the onboarding will show the setup screen again on next app start. +

+
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 929f07a1..708e52b7 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -7,6 +7,7 @@ export default defineConfig({ resolve: { alias: { "@": path.resolve(__dirname, "./src"), + "shared": path.resolve(__dirname, "../shared"), }, }, server: { diff --git a/shared/types.ts b/shared/types.ts index 336bc2fc..2e7896f2 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -4,7 +4,7 @@ export type ApiResponse = { success: boolean, data: T | null, message: string | null, }; -export type Config = { theme: ThemeMode, executor: ExecutorConfig, disclaimer_acknowledged: boolean, sound_alerts: boolean, push_notifications: boolean, editor: EditorConfig, }; +export type Config = { theme: ThemeMode, executor: ExecutorConfig, disclaimer_acknowledged: boolean, onboarding_acknowledged: boolean, sound_alerts: boolean, push_notifications: boolean, editor: EditorConfig, }; export type ThemeMode = "light" | "dark" | "system"; @@ -14,6 +14,25 @@ export type EditorType = "vscode" | "cursor" | "windsurf" | "intellij" | "zed" | export type ExecutorConfig = { "type": "echo" } | { "type": "claude" } | { "type": "amp" }; +// Constants for UI components +export const EXECUTOR_TYPES = ["echo", "claude", "amp"] as const; +export const EDITOR_TYPES = ["vscode", "cursor", "windsurf", "intellij", "zed", "custom"] as const; + +export const EXECUTOR_LABELS = { + echo: "Echo", + claude: "Claude", + amp: "Amp" +} as const; + +export const EDITOR_LABELS = { + vscode: "VS Code", + cursor: "Cursor", + windsurf: "Windsurf", + intellij: "IntelliJ IDEA", + zed: "Zed", + custom: "Custom Command" +} as const; + export type CreateProject = { name: string, git_repo_path: string, use_existing_repo: boolean, setup_script: string | null, }; export type Project = { id: string, name: string, git_repo_path: string, setup_script: string | null, created_at: Date, updated_at: Date, };