From ff7cf3bb5bce9ef9d7ab64afcea6bcf1c67f9950 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 27 Mar 2026 19:55:14 -0700 Subject: [PATCH] improvement(tour): remove auto-start, only trigger on explicit user action --- .../components/product-tour/product-tour.tsx | 4 - .../components/product-tour/use-tour.ts | 84 ++----------------- .../components/product-tour/workflow-tour.tsx | 6 +- 3 files changed, 8 insertions(+), 86 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/components/product-tour/product-tour.tsx b/apps/sim/app/workspace/[workspaceId]/components/product-tour/product-tour.tsx index 1c49837afa5..ba659237800 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/product-tour/product-tour.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/product-tour/product-tour.tsx @@ -16,7 +16,6 @@ const Joyride = dynamic(() => import('react-joyride'), { ssr: false, }) -const NAV_TOUR_STORAGE_KEY = 'sim-nav-tour-completed-v1' export const START_NAV_TOUR_EVENT = 'start-nav-tour' export function NavTour() { @@ -25,9 +24,6 @@ export function NavTour() { const { run, stepIndex, tourKey, isTooltipVisible, isEntrance, handleCallback } = useTour({ steps: navTourSteps, - storageKey: NAV_TOUR_STORAGE_KEY, - autoStartDelay: 1200, - resettable: true, triggerEvent: START_NAV_TOUR_EVENT, tourName: 'Navigation tour', disabled: isWorkflowPage, diff --git a/apps/sim/app/workspace/[workspaceId]/components/product-tour/use-tour.ts b/apps/sim/app/workspace/[workspaceId]/components/product-tour/use-tour.ts index 1c3ed52e179..10b09caf9bb 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/product-tour/use-tour.ts +++ b/apps/sim/app/workspace/[workspaceId]/components/product-tour/use-tour.ts @@ -12,17 +12,11 @@ const FADE_OUT_MS = 80 interface UseTourOptions { /** Tour step definitions */ steps: Step[] - /** localStorage key for completion persistence */ - storageKey: string - /** Delay before auto-starting the tour (ms) */ - autoStartDelay?: number - /** Whether this tour can be reset/retriggered */ - resettable?: boolean /** Custom event name to listen for manual triggers */ triggerEvent?: string /** Identifier for logging */ tourName?: string - /** When true, suppresses auto-start (e.g. to avoid overlapping with another active tour) */ + /** When true, stops a running tour (e.g. navigating away from the relevant page) */ disabled?: boolean } @@ -41,49 +35,14 @@ interface UseTourReturn { handleCallback: (data: CallBackProps) => void } -function isTourCompleted(storageKey: string): boolean { - try { - return localStorage.getItem(storageKey) === 'true' - } catch { - return false - } -} - -function markTourCompleted(storageKey: string): void { - try { - localStorage.setItem(storageKey, 'true') - } catch { - logger.warn('Failed to persist tour completion', { storageKey }) - } -} - -function clearTourCompletion(storageKey: string): void { - try { - localStorage.removeItem(storageKey) - } catch { - logger.warn('Failed to clear tour completion', { storageKey }) - } -} - -/** - * Tracks which tours have already attempted auto-start in this page session. - * Module-level so it survives component remounts (e.g. navigating between - * workflows remounts WorkflowTour), while still resetting on full page reload. - */ -const autoStartAttempted = new Set() - /** * Shared hook for managing product tour state with smooth transitions. * - * Handles auto-start on first visit, localStorage persistence, - * manual triggering via custom events, and coordinated fade + * Handles manual triggering via custom events and coordinated fade * transitions between steps to prevent layout shift. */ export function useTour({ steps, - storageKey, - autoStartDelay = 1200, - resettable = false, triggerEvent, tourName = 'tour', disabled = false, @@ -94,15 +53,10 @@ export function useTour({ const [isTooltipVisible, setIsTooltipVisible] = useState(true) const [isEntrance, setIsEntrance] = useState(true) - const disabledRef = useRef(disabled) const retriggerTimerRef = useRef | null>(null) const transitionTimerRef = useRef | null>(null) const rafRef = useRef(null) - useEffect(() => { - disabledRef.current = disabled - }, [disabled]) - /** * Schedules a two-frame rAF to reveal the tooltip after the browser * finishes repositioning. Stores the outer frame ID in `rafRef` so @@ -137,8 +91,7 @@ export function useTour({ setRun(false) setIsTooltipVisible(true) setIsEntrance(true) - markTourCompleted(storageKey) - }, [storageKey, cancelPendingTransitions]) + }, [cancelPendingTransitions]) /** Transition to a new step with a coordinated fade-out/fade-in */ const transitionToStep = useCallback( @@ -164,40 +117,17 @@ export function useTour({ /** Stop the tour when disabled becomes true (e.g. navigating away from the relevant page) */ useEffect(() => { if (disabled && run) { - cancelPendingTransitions() - setRun(false) - setIsTooltipVisible(true) - setIsEntrance(true) + stopTour() logger.info(`${tourName} paused — disabled became true`) } - }, [disabled, run, tourName, cancelPendingTransitions]) - - /** Auto-start on first visit (once per page session per tour) */ - useEffect(() => { - if (disabled || autoStartAttempted.has(storageKey) || isTourCompleted(storageKey)) return - - const timer = setTimeout(() => { - if (disabledRef.current) return - - autoStartAttempted.add(storageKey) - setStepIndex(0) - setIsEntrance(true) - setIsTooltipVisible(false) - setRun(true) - logger.info(`Auto-starting ${tourName}`) - scheduleReveal() - }, autoStartDelay) - - return () => clearTimeout(timer) - }, [disabled, storageKey, autoStartDelay, tourName, scheduleReveal]) + }, [disabled, run, tourName, stopTour]) /** Listen for manual trigger events */ useEffect(() => { - if (!triggerEvent || !resettable) return + if (!triggerEvent) return const handleTrigger = () => { setRun(false) - clearTourCompletion(storageKey) setTourKey((k) => k + 1) if (retriggerTimerRef.current) { @@ -222,7 +152,7 @@ export function useTour({ clearTimeout(retriggerTimerRef.current) } } - }, [triggerEvent, resettable, storageKey, tourName, scheduleReveal]) + }, [triggerEvent, tourName, scheduleReveal]) /** Clean up all pending async work on unmount */ useEffect(() => { diff --git a/apps/sim/app/workspace/[workspaceId]/components/product-tour/workflow-tour.tsx b/apps/sim/app/workspace/[workspaceId]/components/product-tour/workflow-tour.tsx index 13bcf7468c5..383a3311c0b 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/product-tour/workflow-tour.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/product-tour/workflow-tour.tsx @@ -15,19 +15,15 @@ const Joyride = dynamic(() => import('react-joyride'), { ssr: false, }) -const WORKFLOW_TOUR_STORAGE_KEY = 'sim-workflow-tour-completed-v1' export const START_WORKFLOW_TOUR_EVENT = 'start-workflow-tour' /** * Workflow tour that covers the canvas, blocks, copilot, and deployment. - * Runs on first workflow visit and can be retriggered via "Take a tour". + * Triggered via "Take a tour" in the sidebar menu. */ export function WorkflowTour() { const { run, stepIndex, tourKey, isTooltipVisible, isEntrance, handleCallback } = useTour({ steps: workflowTourSteps, - storageKey: WORKFLOW_TOUR_STORAGE_KEY, - autoStartDelay: 800, - resettable: true, triggerEvent: START_WORKFLOW_TOUR_EVENT, tourName: 'Workflow tour', })