From 587f7e912f0e3b0088bfffe1d449b7175b3ade59 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sat, 11 Apr 2026 17:56:41 -0700 Subject: [PATCH] Delete the tutorial --- .../src/renderer/components/TourHighlight.tsx | 68 --- .../onboarding/components/OnboardingFlow.tsx | 285 ++++++----- .../components/TutorialHedgehog.tsx | 139 ------ .../onboarding/components/TutorialStep.tsx | 469 ------------------ .../onboarding/hooks/useTutorialTour.ts | 131 ----- .../src/renderer/features/onboarding/types.ts | 4 +- .../utils/generateInstrumentationPrompt.ts | 23 - .../components/TaskInputEditor.tsx | 69 ++- 8 files changed, 170 insertions(+), 1018 deletions(-) delete mode 100644 apps/code/src/renderer/components/TourHighlight.tsx delete mode 100644 apps/code/src/renderer/features/onboarding/components/TutorialHedgehog.tsx delete mode 100644 apps/code/src/renderer/features/onboarding/components/TutorialStep.tsx delete mode 100644 apps/code/src/renderer/features/onboarding/hooks/useTutorialTour.ts delete mode 100644 apps/code/src/renderer/features/onboarding/utils/generateInstrumentationPrompt.ts diff --git a/apps/code/src/renderer/components/TourHighlight.tsx b/apps/code/src/renderer/components/TourHighlight.tsx deleted file mode 100644 index 79311aae1..000000000 --- a/apps/code/src/renderer/components/TourHighlight.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { motion } from "framer-motion"; - -interface TourHighlightProps { - active: boolean; - children: React.ReactNode; - borderRadius?: string; - /** Set true for elements that should stretch to fill their container (e.g. editor) */ - fullWidth?: boolean; - /** When true and not active, dim to show it's not the focus of attention */ - dimWhenInactive?: boolean; - /** - * Keep opacity at 1 without triggering the glow — use when a child component - * is highlighted so this wrapper doesn't dim it via CSS opacity inheritance. - */ - opaque?: boolean; -} - -export function TourHighlight({ - active, - children, - borderRadius = "var(--radius-2)", - fullWidth, - dimWhenInactive, - opaque, -}: TourHighlightProps) { - const targetOpacity = active || opaque ? 1 : dimWhenInactive ? 0.35 : 1; - - return ( - - {children} - - ); -} diff --git a/apps/code/src/renderer/features/onboarding/components/OnboardingFlow.tsx b/apps/code/src/renderer/features/onboarding/components/OnboardingFlow.tsx index 2cb8a7d10..1d64fc693 100644 --- a/apps/code/src/renderer/features/onboarding/components/OnboardingFlow.tsx +++ b/apps/code/src/renderer/features/onboarding/components/OnboardingFlow.tsx @@ -12,7 +12,6 @@ import { GitIntegrationStep } from "./GitIntegrationStep"; import { OrgBillingStep } from "./OrgBillingStep"; import { SignalsStep } from "./SignalsStep"; import { StepIndicator } from "./StepIndicator"; -import { TutorialStep } from "./TutorialStep"; import { WelcomeStep } from "./WelcomeStep"; export function OnboardingFlow() { @@ -26,8 +25,6 @@ export function OnboardingFlow() { completeOnboarding(); }; - const isTutorial = currentStep === "tutorial"; - return ( @@ -38,157 +35,151 @@ export function OnboardingFlow() { > - {isTutorial ? ( - - ) : ( - <> - {/* Background */} -
- - {/* Right panel — zen hedgehog */} - - - - - {/* Content */} - - - - {currentStep === "welcome" && ( - - - - )} + {/* Background */} +
- {currentStep === "billing" && ( - - - - )} + {/* Right panel — zen hedgehog */} + + + - {currentStep === "org-billing" && ( - - - - )} + {/* Content */} + + + + {currentStep === "welcome" && ( + + + + )} - {currentStep === "git-integration" && ( - - - - )} + {currentStep === "billing" && ( + + + + )} - {currentStep === "signals" && ( - - - - )} - - + {currentStep === "org-billing" && ( + + + + )} - - - - - - - - )} + + + )} + + + + + + + + + diff --git a/apps/code/src/renderer/features/onboarding/components/TutorialHedgehog.tsx b/apps/code/src/renderer/features/onboarding/components/TutorialHedgehog.tsx deleted file mode 100644 index 3403f11d4..000000000 --- a/apps/code/src/renderer/features/onboarding/components/TutorialHedgehog.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { ArrowRight } from "@phosphor-icons/react"; -import { Button, Flex, Text } from "@radix-ui/themes"; -import zenHedgehog from "@renderer/assets/images/zen.png"; -import { AnimatePresence, motion } from "framer-motion"; - -interface TutorialHedgehogProps { - message: string; - onNext?: () => void; - stepNumber: number; - totalSteps: number; -} - -export function TutorialHedgehog({ - message, - onNext, - stepNumber, - totalSteps, -}: TutorialHedgehogProps) { - return ( -
- {/* Speech bubble — to the left of the hedgehog */} - - - - - - {message} - - - - {stepNumber}/{totalSteps} - - {onNext && ( - - )} - - - - {/* Tail pointing right toward hedgehog */} -
-
- - - - - {/* Hedgehog image — layoutId matches ZenHedgehog for seamless transition */} -
- -
-
- ); -} diff --git a/apps/code/src/renderer/features/onboarding/components/TutorialStep.tsx b/apps/code/src/renderer/features/onboarding/components/TutorialStep.tsx deleted file mode 100644 index 03c473876..000000000 --- a/apps/code/src/renderer/features/onboarding/components/TutorialStep.tsx +++ /dev/null @@ -1,469 +0,0 @@ -import { TourHighlight } from "@components/TourHighlight"; -import { FolderPicker } from "@features/folder-picker/components/FolderPicker"; -import { GitHubRepoPicker } from "@features/folder-picker/components/GitHubRepoPicker"; -import { BranchSelector } from "@features/git-interaction/components/BranchSelector"; -import type { MessageEditorHandle } from "@features/message-editor/components/MessageEditor"; -import { ModeIndicatorInput } from "@features/message-editor/components/ModeIndicatorInput"; -import { useDraftStore } from "@features/message-editor/stores/draftStore"; -import { useOnboardingStore } from "@features/onboarding/stores/onboardingStore"; -import { - cycleModeOption, - getCurrentModeFromConfigOptions, -} from "@features/sessions/stores/sessionStore"; -import { useSettingsStore } from "@features/settings/stores/settingsStore"; -import { TaskInputEditor } from "@features/task-detail/components/TaskInputEditor"; -import { WorkspaceModeSelect } from "@features/task-detail/components/WorkspaceModeSelect"; -import { usePreviewConfig } from "@features/task-detail/hooks/usePreviewConfig"; -import { useTaskCreation } from "@features/task-detail/hooks/useTaskCreation"; -import { - useGithubBranches, - useRepositoryIntegration, -} from "@hooks/useIntegrations"; -import { ArrowLeft } from "@phosphor-icons/react"; -import { Button, Flex } from "@radix-ui/themes"; -import { motion } from "framer-motion"; -import { - useCallback, - useEffect, - useLayoutEffect, - useRef, - useState, -} from "react"; -import { useHotkeys } from "react-hotkeys-hook"; -import { useTutorialTour } from "../hooks/useTutorialTour"; -import { TutorialHedgehog } from "./TutorialHedgehog"; - -const DOT_FILL = "var(--gray-6)"; - -const HEDGEHOG_MESSAGES: Record = { - "select-repo": - "Pick a repo to get started — I'll help you set up PostHog instrumentation for it!", - "select-worktree": - "Great choice! Now pick Worktree from the workspace mode dropdown — it creates a copy of your project to work in parallel.", - "select-model": - "Now pick your AI model — try selecting Claude Opus 4.6 for the most capable option!", - "explain-mode": - "Press Shift+Tab to cycle through execution modes like Plan, Code, and more.", - "auto-fill-prompt": - "I've written your first task prompt — it'll set up PostHog based on the signals you enabled. Press Next when you're ready!", - "submit-task": - "You're ready! Hit the arrow button to launch your first task.", - navigating: "Launching your task...", -}; - -const TOTAL_TOUR_STEPS = Object.keys(HEDGEHOG_MESSAGES).length - 1; // exclude "navigating" - -interface TutorialStepProps { - onComplete: () => void; - onBack: () => void; -} - -export function TutorialStep({ onComplete, onBack }: TutorialStepProps) { - const { allowBypassPermissions } = useSettingsStore(); - const completeOnboarding = useOnboardingStore( - (state) => state.completeOnboarding, - ); - - // Tour state machine - const { - subStep, - advance, - isEnabled, - isHighlighted, - generatedPrompt, - hasNextButton, - } = useTutorialTour(); - - const editorRef = useRef(null); - - // Clear any leftover draft and delay content until the hedgehog has animated in - const [contentVisible, setContentVisible] = useState(false); - useLayoutEffect(() => { - useDraftStore.getState().actions.setDraft("tutorial-input", null); - const timer = setTimeout(() => setContentVisible(true), 1000); - return () => clearTimeout(timer); - }, []); - - // GitHub repos - const { repositories, getIntegrationIdForRepo, isLoadingRepos } = - useRepositoryIntegration(); - const [selectedRepository, setSelectedRepository] = useState( - null, - ); - const [selectedDirectory, setSelectedDirectory] = useState(""); - const [selectedBranch, setSelectedBranch] = useState(null); - const [editorIsEmpty, setEditorIsEmpty] = useState(true); - const [workspaceMode, setWorkspaceMode] = useState< - "local" | "worktree" | "cloud" - >("local"); - const [selectedModel, setSelectedModel] = useState(null); - - const selectedIntegrationId = selectedRepository - ? getIntegrationIdForRepo(selectedRepository) - : undefined; - - const { - data: cloudBranchData, - isPending: cloudBranchesLoading, - isFetchingMore: cloudBranchesFetchingMore, - pauseLoadingMore: pauseCloudBranchesLoading, - resumeLoadingMore: resumeCloudBranchesLoading, - } = useGithubBranches(selectedIntegrationId, selectedRepository); - const cloudBranches = cloudBranchData?.branches; - const cloudDefaultBranch = cloudBranchData?.defaultBranch ?? null; - - // Preview config options — always claude - const { - modeOption, - modelOption, - thoughtOption, - isLoading: isPreviewLoading, - setConfigOption, - } = usePreviewConfig("claude"); - - const currentExecutionMode = - getCurrentModeFromConfigOptions(modeOption ? [modeOption] : undefined) ?? - "plan"; - const currentReasoningLevel = - thoughtOption?.type === "select" ? thoughtOption.currentValue : undefined; - - // Task creation — use whatever model the user picked - const { isCreatingTask, canSubmit, handleSubmit } = useTaskCreation({ - editorRef, - selectedDirectory, - selectedRepository, - githubIntegrationId: selectedIntegrationId, - workspaceMode, - branch: selectedBranch, - editorIsEmpty, - adapter: "claude", - executionMode: currentExecutionMode, - model: selectedModel ?? "claude-sonnet-4-6", - reasoningLevel: currentReasoningLevel, - }); - - // Editor wrapper is interactive when user needs to interact with model selector, editor text, or submit button - const editorInteractive = - subStep === "select-model" || - subStep === "submit-task" || - subStep === "navigating" || - isCreatingTask; - - const isTourActive = subStep !== "navigating"; - - // Advance tour when user selects a repo or folder - useEffect(() => { - if ( - subStep === "select-repo" && - (selectedRepository || selectedDirectory) - ) { - advance(); - } - }, [subStep, selectedRepository, selectedDirectory, advance]); - - // Auto-fill prompt with typing animation — waits for user to click Next first - const [autoFillTriggered, setAutoFillTriggered] = useState(false); - useEffect(() => { - if (subStep !== "auto-fill-prompt" || !editorRef.current) return; - if (!autoFillTriggered) return; - - let index = 0; - const interval = setInterval(() => { - index += 4; - editorRef.current?.setContent(generatedPrompt.slice(0, index)); - if (index >= generatedPrompt.length) { - clearInterval(interval); - advance(); - } - }, 15); - - return () => clearInterval(interval); - }, [subStep, generatedPrompt, advance, autoFillTriggered]); - - // Track mode selection; advance only when worktree is picked during select-worktree step - const handleWorkspaceModeChange = useCallback( - (mode: "local" | "worktree" | "cloud") => { - setWorkspaceMode(mode); - if (mode === "worktree" && subStep === "select-worktree") { - advance(); - } - }, - [subStep, advance], - ); - - // Track model selection; advance when any model is picked during select-model step - const handleModelChange = useCallback( - (model: string) => { - setSelectedModel(model); - if (subStep === "select-model") { - advance(); - } - }, - [subStep, advance], - ); - - // Shift+tab mode cycling (only active during explain-mode step) - const handleCycleMode = useCallback(() => { - const nextValue = cycleModeOption(modeOption, allowBypassPermissions); - if (nextValue && modeOption) { - setConfigOption(modeOption.id, nextValue); - } - }, [modeOption, allowBypassPermissions, setConfigOption]); - - useHotkeys( - "shift+tab", - (e) => { - e.preventDefault(); - handleCycleMode(); - }, - { - enableOnFormTags: true, - enableOnContentEditable: true, - enabled: !!modeOption && subStep === "explain-mode", - }, - [handleCycleMode, modeOption, subStep], - ); - - // Submit and complete onboarding - const handleTutorialSubmit = useCallback(async () => { - await handleSubmit(); - completeOnboarding(); - }, [handleSubmit, completeOnboarding]); - - // Handle Next button — for auto-fill step, trigger the typing animation - const handleNextClick = useCallback(() => { - if (subStep === "auto-fill-prompt" && !autoFillTriggered) { - setAutoFillTriggered(true); - } else { - advance(); - } - }, [subStep, autoFillTriggered, advance]); - - const stepNumber = Math.max( - 1, - Object.keys(HEDGEHOG_MESSAGES).indexOf(subStep) + 1, - ); - const hedgehogMessage = HEDGEHOG_MESSAGES[subStep] ?? ""; - - return ( - - {/* Main content area — mirrors TaskInput layout */} - - {/* Dot pattern background */} - - - {contentVisible && ( - - {/* Row 1: Repo picker + Workspace mode + Branch */} - - - {workspaceMode === "cloud" ? ( - - ) : ( - - )} - - - - - - - - - - - - {/* Row 2: Editor — opaque when a child (model/submit) is the active highlight */} - -
- { - setConfigOption(configId, value); - if (configId === modelOption?.id) { - handleModelChange(value); - } - }} - onAdapterChange={() => {}} - isLoading={isPreviewLoading} - autoFocus={false} - tourHighlight={ - isHighlighted("model-selector") - ? "model-selector" - : isHighlighted("submit-button") - ? "submit-button" - : null - } - /> -
-
- - {/* Row 3: Mode indicator */} - -
- -
-
-
- )} -
- - {/* Hedgehog guide */} - - - {/* Bottom controls */} - - - - -
- ); -} diff --git a/apps/code/src/renderer/features/onboarding/hooks/useTutorialTour.ts b/apps/code/src/renderer/features/onboarding/hooks/useTutorialTour.ts deleted file mode 100644 index 8bd42efbf..000000000 --- a/apps/code/src/renderer/features/onboarding/hooks/useTutorialTour.ts +++ /dev/null @@ -1,131 +0,0 @@ -import type { SignalSourceValues } from "@features/inbox/components/SignalSourceToggles"; -import { useSignalSourceConfigs } from "@features/inbox/hooks/useSignalSourceConfigs"; -import { useCallback, useMemo, useState } from "react"; -import { generateInstrumentationPrompt } from "../utils/generateInstrumentationPrompt"; - -export type TutorialSubStep = - | "select-repo" - | "select-worktree" - | "select-model" - | "explain-mode" - | "auto-fill-prompt" - | "submit-task" - | "navigating"; - -type TutorialComponent = - | "repo-picker" - | "workspace-mode" - | "branch-selector" - | "editor" - | "model-selector" - | "mode-indicator" - | "submit-button"; - -const SUB_STEP_ORDER: TutorialSubStep[] = [ - "select-repo", - "select-worktree", - "select-model", - "explain-mode", - "auto-fill-prompt", - "submit-task", - "navigating", -]; - -/** - * The step at which each component becomes unlocked. - * Once unlocked, it stays interactive for all subsequent steps. - */ -const UNLOCK_AT: Record = { - "repo-picker": "select-repo", - "workspace-mode": "select-worktree", - "branch-selector": "select-worktree", - editor: "submit-task", - "model-selector": "select-model", - "mode-indicator": "explain-mode", - "submit-button": "submit-task", -}; - -/** Which component is highlighted (has spotlight) at each sub-step */ -const HIGHLIGHTED_MAP: Record = { - "select-repo": "repo-picker", - "select-worktree": "workspace-mode", - "select-model": "model-selector", - "explain-mode": "mode-indicator", - "auto-fill-prompt": "editor", - "submit-task": "submit-button", - navigating: null, -}; - -export function useTutorialTour() { - const [subStep, setSubStep] = useState("select-repo"); - const { data: configs } = useSignalSourceConfigs(); - - const signals: SignalSourceValues = useMemo( - () => ({ - session_replay: - configs?.some( - (c) => c.source_product === "session_replay" && c.enabled, - ) ?? true, - github: - configs?.some((c) => c.source_product === "github" && c.enabled) ?? - false, - linear: - configs?.some((c) => c.source_product === "linear" && c.enabled) ?? - false, - zendesk: - configs?.some((c) => c.source_product === "zendesk" && c.enabled) ?? - false, - error_tracking: - configs?.some( - (c) => c.source_product === "error_tracking" && c.enabled, - ) ?? false, - }), - [configs], - ); - - const generatedPrompt = useMemo( - () => generateInstrumentationPrompt(signals), - [signals], - ); - - const currentIndex = SUB_STEP_ORDER.indexOf(subStep); - - const advance = useCallback(() => { - setSubStep((current) => { - const idx = SUB_STEP_ORDER.indexOf(current); - if (idx < SUB_STEP_ORDER.length - 1) { - return SUB_STEP_ORDER[idx + 1]; - } - return current; - }); - }, []); - - const isEnabled = useCallback( - (component: TutorialComponent): boolean => { - const unlockStep = UNLOCK_AT[component]; - const unlockIndex = SUB_STEP_ORDER.indexOf(unlockStep); - return currentIndex >= unlockIndex; - }, - [currentIndex], - ); - - const isHighlighted = useCallback( - (component: TutorialComponent): boolean => { - return HIGHLIGHTED_MAP[subStep] === component; - }, - [subStep], - ); - - /** Whether the tooltip for this step has a "Next" button (vs being advanced by user action) */ - const hasNextButton = - subStep === "explain-mode" || subStep === "auto-fill-prompt"; - - return { - subStep, - advance, - isEnabled, - isHighlighted, - generatedPrompt, - hasNextButton, - }; -} diff --git a/apps/code/src/renderer/features/onboarding/types.ts b/apps/code/src/renderer/features/onboarding/types.ts index 91160eefe..9b702fb63 100644 --- a/apps/code/src/renderer/features/onboarding/types.ts +++ b/apps/code/src/renderer/features/onboarding/types.ts @@ -3,8 +3,7 @@ export type OnboardingStep = | "billing" | "org-billing" | "git-integration" - | "signals" - | "tutorial"; + | "signals"; export const ONBOARDING_STEPS: OnboardingStep[] = [ "welcome", @@ -12,5 +11,4 @@ export const ONBOARDING_STEPS: OnboardingStep[] = [ "org-billing", "git-integration", "signals", - "tutorial", ]; diff --git a/apps/code/src/renderer/features/onboarding/utils/generateInstrumentationPrompt.ts b/apps/code/src/renderer/features/onboarding/utils/generateInstrumentationPrompt.ts deleted file mode 100644 index 406b5cab6..000000000 --- a/apps/code/src/renderer/features/onboarding/utils/generateInstrumentationPrompt.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { SignalSourceValues } from "@features/inbox/components/SignalSourceToggles"; - -export function generateInstrumentationPrompt( - signals: SignalSourceValues, -): string { - const parts: string[] = [ - "Set up PostHog instrumentation for this repository.", - ]; - - if (signals.session_replay) { - parts.push( - "Install the PostHog SDK if not already present and configure session recording. Initialize with `enable_recording_console_log: true` and ensure session replay is enabled.", - ); - } - - if (!signals.session_replay) { - parts.push( - "Check if the PostHog SDK is installed. If not, install it and initialize it with the project's API key. Set up basic event tracking.", - ); - } - - return parts.join("\n\n"); -} diff --git a/apps/code/src/renderer/features/task-detail/components/TaskInputEditor.tsx b/apps/code/src/renderer/features/task-detail/components/TaskInputEditor.tsx index 87bbac10e..f8c16718a 100644 --- a/apps/code/src/renderer/features/task-detail/components/TaskInputEditor.tsx +++ b/apps/code/src/renderer/features/task-detail/components/TaskInputEditor.tsx @@ -1,6 +1,5 @@ import "@features/message-editor/components/message-editor.css"; import type { SessionConfigOption } from "@agentclientprotocol/sdk"; -import { TourHighlight } from "@components/TourHighlight"; import { AttachmentsBar } from "@features/message-editor/components/AttachmentsBar"; import { EditorToolbar } from "@features/message-editor/components/EditorToolbar"; import type { MessageEditorHandle } from "@features/message-editor/components/MessageEditor"; @@ -34,7 +33,6 @@ interface TaskInputEditorProps { onAdapterChange?: (adapter: AgentAdapter) => void; isLoading?: boolean; autoFocus?: boolean; - tourHighlight?: "model-selector" | "submit-button" | null; } export const TaskInputEditor = forwardRef< @@ -58,7 +56,6 @@ export const TaskInputEditor = forwardRef< onAdapterChange, isLoading, autoFocus = true, - tourHighlight, }, ref, ) => { @@ -257,16 +254,14 @@ export const TaskInputEditor = forwardRef< iconSize={16} hideSelectors /> - - {})} - disabled={isCreatingTask} - isConnecting={isLoading} - onModelChange={handleModelChange} - /> - + {})} + disabled={isCreatingTask} + isConnecting={isLoading} + onModelChange={handleModelChange} + /> {!isLoading && ( - - { - e.stopPropagation(); - onSubmit(); - }} - disabled={!canSubmit || isSubmitDisabled} - loading={isCreatingTask} - style={{ - backgroundColor: - !canSubmit || isSubmitDisabled - ? "var(--accent-a4)" - : undefined, - color: - !canSubmit || isSubmitDisabled - ? "var(--accent-8)" - : undefined, - }} - > - - - + { + e.stopPropagation(); + onSubmit(); + }} + disabled={!canSubmit || isSubmitDisabled} + loading={isCreatingTask} + style={{ + backgroundColor: + !canSubmit || isSubmitDisabled + ? "var(--accent-a4)" + : undefined, + color: + !canSubmit || isSubmitDisabled + ? "var(--accent-8)" + : undefined, + }} + > + +