From d269dfd6d9274119a167c6c5be41582a63cedc85 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sat, 11 Apr 2026 17:56:41 -0700 Subject: [PATCH 1/3] Delete the tutorial --- .../src/renderer/components/TourHighlight.tsx | 68 --- .../onboarding/components/OnboardingFlow.tsx | 261 +++++----- .../components/TutorialHedgehog.tsx | 139 ------ .../onboarding/components/TutorialStep.tsx | 459 ------------------ .../onboarding/hooks/useTutorialTour.ts | 135 ------ .../src/renderer/features/onboarding/types.ts | 4 +- .../utils/generateInstrumentationPrompt.ts | 23 - 7 files changed, 127 insertions(+), 962 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 aac5d8558..0dde1750a 100644 --- a/apps/code/src/renderer/features/onboarding/components/OnboardingFlow.tsx +++ b/apps/code/src/renderer/features/onboarding/components/OnboardingFlow.tsx @@ -11,7 +11,6 @@ import { GitIntegrationStep } from "./GitIntegrationStep"; import { OrgStep } from "./OrgStep"; import { SignalsStep } from "./SignalsStep"; import { StepIndicator } from "./StepIndicator"; -import { TutorialStep } from "./TutorialStep"; import { WelcomeStep } from "./WelcomeStep"; export function OnboardingFlow() { @@ -25,8 +24,6 @@ export function OnboardingFlow() { completeOnboarding(); }; - const isTutorial = currentStep === "tutorial"; - return ( @@ -37,144 +34,138 @@ export function OnboardingFlow() { > - {isTutorial ? ( - - ) : ( - <> - {/* Background */} -
- - {/* Right panel — zen hedgehog */} - - - - - {/* Content */} - - - - {currentStep === "welcome" && ( - - - - )} + {/* Background */} +
- {currentStep === "org" && ( - - - - )} + {/* Right panel — zen hedgehog */} + + + - {currentStep === "git-integration" && ( - - - - )} + {/* Content */} + + + + {currentStep === "welcome" && ( + + + + )} - {currentStep === "signals" && ( - - - - )} - - + {currentStep === "org" && ( + + + + )} - - - - - - - - )} + + + )} + + + + + + + + + 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 166d0865a..000000000 --- a/apps/code/src/renderer/features/onboarding/components/TutorialStep.tsx +++ /dev/null @@ -1,459 +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 { PromptInput } from "@features/message-editor/components/PromptInput"; -import { useDraftStore } from "@features/message-editor/stores/draftStore"; -import type { EditorHandle } from "@features/message-editor/types"; -import { useOnboardingStore } from "@features/onboarding/stores/onboardingStore"; -import { ReasoningLevelSelector } from "@features/sessions/components/ReasoningLevelSelector"; -import { UnifiedModelSelector } from "@features/sessions/components/UnifiedModelSelector"; -import { getCurrentModeFromConfigOptions } from "@features/sessions/stores/sessionStore"; -import { useSettingsStore } from "@features/settings/stores/settingsStore"; -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 { 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.7 for the most capable option!", - "explain-mode": - "Open the mode menu in the prompt input to switch between Plan, Code, and other execution modes.", - "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], - ); - - const handleModeChange = useCallback( - (value: string) => { - if (modeOption) { - setConfigOption(modeOption.id, value); - } - if (subStep === "explain-mode") { - advance(); - } - }, - [modeOption, setConfigOption, subStep, advance], - ); - - const handleReasoningChange = useCallback( - (value: string) => { - if (thoughtOption) { - setConfigOption(thoughtOption.id, value); - } - }, - [thoughtOption, setConfigOption], - ); - - // 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: Prompt input — editor + toolbar + mode dropdown */} - -
- - {}} - onModelChange={handleModelChange} - disabled={isCreatingTask} - isConnecting={isPreviewLoading} - /> - - } - reasoningSelector={ - !isPreviewLoading && ( - - ) - } - tourHighlightSubmit={isHighlighted("submit-button")} - onEmptyChange={setEditorIsEmpty} - onSubmitClick={handleTutorialSubmit} - onSubmit={() => { - if (canSubmit) handleTutorialSubmit(); - }} - /> -
-
-
- )} -
- - {/* 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 4839ea78e..000000000 --- a/apps/code/src/renderer/features/onboarding/hooks/useTutorialTour.ts +++ /dev/null @@ -1,135 +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, - conversations: - configs?.some( - (c) => c.source_product === "conversations" && 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 759af6b79..a57243a20 100644 --- a/apps/code/src/renderer/features/onboarding/types.ts +++ b/apps/code/src/renderer/features/onboarding/types.ts @@ -2,13 +2,11 @@ export type OnboardingStep = | "welcome" | "org" | "git-integration" - | "signals" - | "tutorial"; + | "signals"; export const ONBOARDING_STEPS: OnboardingStep[] = [ "welcome", "org", "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"); -} From ae4bcceb47a46b77320f7891d921c1bf80f5aa35 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Fri, 17 Apr 2026 02:11:38 -0700 Subject: [PATCH 2/3] Update types.ts --- apps/code/src/renderer/features/onboarding/types.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/code/src/renderer/features/onboarding/types.ts b/apps/code/src/renderer/features/onboarding/types.ts index a57243a20..b66109311 100644 --- a/apps/code/src/renderer/features/onboarding/types.ts +++ b/apps/code/src/renderer/features/onboarding/types.ts @@ -1,8 +1,4 @@ -export type OnboardingStep = - | "welcome" - | "org" - | "git-integration" - | "signals"; +export type OnboardingStep = "welcome" | "org" | "git-integration" | "signals"; export const ONBOARDING_STEPS: OnboardingStep[] = [ "welcome", From 709474eab1e3dfcc67210cbf990678f7d554c537 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Mon, 20 Apr 2026 11:18:06 -0700 Subject: [PATCH 3/3] Remove TourHighlight from PromptInput and TaskInput --- .../message-editor/components/PromptInput.tsx | 13 +------------ .../task-detail/components/TaskInput.tsx | 19 ++++++++----------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/apps/code/src/renderer/features/message-editor/components/PromptInput.tsx b/apps/code/src/renderer/features/message-editor/components/PromptInput.tsx index bb3ae9f33..0e591b7d7 100644 --- a/apps/code/src/renderer/features/message-editor/components/PromptInput.tsx +++ b/apps/code/src/renderer/features/message-editor/components/PromptInput.tsx @@ -1,6 +1,5 @@ import "./message-editor.css"; import type { SessionConfigOption } from "@agentclientprotocol/sdk"; -import { TourHighlight } from "@components/TourHighlight"; import { ArrowUp, Stop } from "@phosphor-icons/react"; import { InputGroup, InputGroupAddon, InputGroupButton } from "@posthog/quill"; import { Flex, Text, Tooltip } from "@radix-ui/themes"; @@ -40,8 +39,6 @@ export interface PromptInputProps { // toolbar slots modelSelector?: React.ReactElement | null | false; reasoningSelector?: React.ReactElement | null | false; - // tour hook for the send button (new-task flow) - tourHighlightSubmit?: boolean; // prompt history provider getPromptHistory?: () => string[]; // callbacks @@ -79,7 +76,6 @@ export const PromptInput = forwardRef( enableCommands = true, modelSelector, reasoningSelector, - tourHighlightSubmit = false, getPromptHistory, onBeforeSubmit, onSubmit, @@ -218,7 +214,6 @@ export const PromptInput = forwardRef( submitTooltipOverride ?? (submitBlocked ? "Enter a message" : "Send message"); - // Render send/stop button (wrapped in TourHighlight when requested) const submitButton = isLoading && onCancel ? ( @@ -247,12 +242,6 @@ export const PromptInput = forwardRef( ); - const wrappedSubmit = tourHighlightSubmit ? ( - {submitButton} - ) : ( - submitButton - ); - return ( ( ! bash )} - {wrappedSubmit} + {submitButton} diff --git a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx index df89916d9..e60438cfd 100644 --- a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx +++ b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx @@ -1,4 +1,3 @@ -import { TourHighlight } from "@components/TourHighlight"; import { EnvironmentSelector } from "@features/environments/components/EnvironmentSelector"; import { FolderPicker } from "@features/folder-picker/components/FolderPicker"; import { GitHubRepoPicker } from "@features/folder-picker/components/GitHubRepoPicker"; @@ -551,16 +550,14 @@ export function TaskInput({ enableCommands enableBashMode={false} modelSelector={ - - - + } reasoningSelector={ !isPreviewLoading && (