Skip to content

feat(tour): added product tour#3703

Open
waleedlatif1 wants to merge 11 commits intostagingfrom
feat/product-tour
Open

feat(tour): added product tour#3703
waleedlatif1 wants to merge 11 commits intostagingfrom
feat/product-tour

Conversation

@waleedlatif1
Copy link
Collaborator

Summary

  • added product tour

Type of Change

  • New feature

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link

vercel bot commented Mar 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Mar 21, 2026 7:25pm

Request Review

@cursor
Copy link

cursor bot commented Mar 21, 2026

PR Summary

Medium Risk
Introduces a new third-party guided-tour dependency (react-joyride) and injects new DOM attributes/overlays across key workspace and workflow surfaces; regressions are most likely in UI layering/positioning and hydration/route-specific behavior.

Overview
Adds two guided tours powered by react-joyride: a navigation tour (mounted in WorkspaceLayout, auto-starts on first visit except on workflow pages) and a workflow tour (mounted in w/[workflowId]/layout, auto-starts on workflow pages and is resettable).

Introduces shared tour infrastructure (useTour, TourTooltipAdapter, step definitions) with localStorage persistence, custom trigger events (start-nav-tour / start-workflow-tour), and smooth step transitions.

Updates UI to support targeting and entry points: adds data-tour / data-item-id hooks on key elements (home, sidebar sections, workflow canvas/panel controls), adds a Help dropdown in the sidebar with “Take a tour”, and extends PopoverContent to optionally render an arrow. Also adds TourTooltip/TourCard to emcn and tailwind animations for tour tooltip entrance.

Written by Cursor Bugbot for commit 9078913. Configure here.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 21, 2026

Greptile Summary

This PR introduces a guided product tour feature for the Sim workspace, comprising two separate tours: a nav tour (highlighting sidebar sections on home/nav pages) and a workflow tour (highlighting canvas, panel tabs, deploy controls, and workflow controls). Both tours are built on top of react-joyride with a custom useTour hook that manages auto-start, localStorage persistence, manual re-triggering via CustomEvent, and coordinated fade transitions. A new TourTooltip EMCN component provides collision-aware positioning via Radix Popover primitives.

Key implementation notes:

  • The NavTour is mounted at the workspace layout level and guards itself with disabled: isWorkflowPage to suppress auto-start on workflow pages; WorkflowTour is mounted at the workflow layout level.
  • The sidebar's "Help" button has been converted to a dropdown with "Report an issue" and "Take a tour" entries; handleStartTour routes to the correct event based on isOnWorkflowPage (stale-closure issue from the prior review is resolved).
  • Shared logic (TourState, TourStateContext, mapPlacement, TourTooltipAdapter) has been extracted into tour-shared.tsx (prior duplication concern resolved).
  • One P1 bug remains: useTour does not call stopTour() when disabled flips from false to true at runtime. Because the tour overlay passes pointer events through (pointerEvents: none), users can click sidebar links and navigate to a workflow page while the nav tour is still running. The workflow tour then auto-starts 800 ms later, resulting in two overlapping tours with conflicting spotlights and tooltip cards.

Confidence Score: 3/5

  • One targeted fix is needed before merge: useTour must stop a running tour when disabled becomes true to prevent both tours running simultaneously.
  • The implementation is well-structured—duplication and the stale-closure concerns from prior rounds are resolved, and the manual trigger routing is correct. However, there is one P1 logic bug: because the tour overlay allows click-through (pointerEvents: none), a user can navigate to a workflow page mid-nav-tour, causing both tours to run at the same time. This directly breaks the primary product-tour user path and is a one-line fix (useEffect that calls stopTour() when disabled && run). Once addressed, the PR is ready to merge.
  • apps/sim/app/workspace/[workspaceId]/components/product-tour/use-tour.ts — the disabled flag does not stop an already-running tour.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/components/product-tour/use-tour.ts Core tour hook with a P1 bug: disabled becoming true at runtime does not stop a running tour, allowing both nav and workflow tours to run simultaneously. The manual-trigger listener also doesn't respect disabled.
apps/sim/app/workspace/[workspaceId]/components/product-tour/product-tour.tsx NavTour component, correctly passes disabled: isWorkflowPage to suppress auto-start; duplication issues from the previous review have been resolved via tour-shared.tsx.
apps/sim/app/workspace/[workspaceId]/components/product-tour/tour-shared.tsx Shared types, context, mapPlacement, and TourTooltipAdapter extracted correctly from the two tour components, eliminating prior duplication.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/layout.tsx WorkflowTour mounted inside an absolute h-0 w-0 overflow-hidden wrapper — functionally OK since Joyride portals to body, but the pattern is inconsistent with NavTour placement.
apps/sim/components/emcn/components/tour-tooltip/tour-tooltip.tsx New EMCN TourTooltip component using Radix Popover for collision-aware positioning; arrow SVG on the positioned variant is missing a border stroke, leaving a minor visual inconsistency.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx Help button expanded into a dropdown with "Take a tour" entry; handleStartTour correctly includes isOnWorkflowPage in its deps (previous stale-closure issue resolved).
apps/sim/components/emcn/components/popover/popover.tsx Added optional showArrow/arrowClassName props; overflow-auto switched to overflow-visible when showArrow is true to avoid clipping the arrow — change is backwards-compatible.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-controls/workflow-controls.tsx Returns a hidden placeholder div with data-tour='workflow-controls' when controls are not shown — ensures Joyride can always find the target DOM node; the two variants are mutually exclusive so no duplicate-attribute issue.

Sequence Diagram

sequenceDiagram
    participant User
    participant Sidebar
    participant NavTour
    participant WorkflowTour
    participant useTour
    participant Joyride
    participant localStorage

    User->>Sidebar: Visits home page (first time)
    NavTour->>useTour: mount (disabled=false)
    useTour->>useTour: setTimeout(1200ms)
    useTour->>localStorage: isTourCompleted?
    localStorage-->>useTour: false
    useTour->>Joyride: setRun(true)
    Joyride-->>User: Nav tour starts (sidebar highlights)

    User->>Sidebar: Clicks "Take a tour"
    Sidebar->>Sidebar: handleStartTour()
    alt On workflow page
        Sidebar->>WorkflowTour: dispatchEvent(START_WORKFLOW_TOUR_EVENT)
        WorkflowTour->>useTour: handleTrigger()
        useTour->>Joyride: setRun(true) + setTourKey(k+1)
        Joyride-->>User: Workflow tour starts
    else On home/nav page
        Sidebar->>NavTour: dispatchEvent(START_NAV_TOUR_EVENT)
        NavTour->>useTour: handleTrigger()
        useTour->>Joyride: setRun(true) + setTourKey(k+1)
        Joyride-->>User: Nav tour restarts
    end

    User->>Joyride: Completes tour (Done / X)
    Joyride->>useTour: callback(STATUS.FINISHED)
    useTour->>useTour: stopTour()
    useTour->>localStorage: markTourCompleted(storageKey)
Loading

Comments Outside Diff (2)

  1. apps/sim/app/workspace/[workspaceId]/components/product-tour/use-tour.ts, line 415-420 (link)

    P1 Running tour not stopped when disabled becomes true

    stopTour is never called when the disabled flag flips to true at runtime. Because the tour overlay has pointerEvents: none, the user can click through it—for example by clicking a workflow in the sidebar while the nav tour is active. When that navigation happens, disabled becomes true, but run stays true and Joyride keeps running. Seconds later, WorkflowTour auto-starts (800 ms delay), resulting in two overlapping tours with two simultaneous spotlights and tooltip cards.

    Add an effect that stops the tour whenever disabled becomes truthy:

    // In useTour, after the stopTour definition:
    useEffect(() => {
      if (disabled && run) {
        stopTour()
      }
    }, [disabled, run, stopTour])
    

    Note: stopTour calls markTourCompleted, which is fine here—the user explicitly navigated away, so persisting "completed" prevents an unexpected re-show on the next home-page visit. The tour remains retriggerable via "Take a tour".

  2. apps/sim/app/workspace/[workspaceId]/components/product-tour/use-tour.ts, line 481-521 (link)

    P2 disabled not respected by the manual-trigger listener

    The trigger-event listener is registered regardless of the disabled flag (it is not included in the effect guard or dependency array). If START_NAV_TOUR_EVENT were ever dispatched while on a workflow page, the nav tour would start even though NavTour was configured with disabled: true.

    Today the sidebar correctly routes to the right event, so this doesn't fire in practice. But as a defensive measure, add disabled to both the guard and the dependency array:

    And update the dependency array:

      }, [triggerEvent, resettable, disabled, storageKey, tourName])
    

Last reviewed commit: "chore(tour): address..."

- Extract shared TourState, TourStateContext, mapPlacement, and TourTooltipAdapter
  into tour-shared.tsx, eliminating ~100 lines of duplication between product-tour.tsx
  and workflow-tour.tsx
- Fix stale closure in handleStartTour — add isOnWorkflowPage to useCallback deps
  so Take a tour dispatches the correct event after navigation
@waleedlatif1
Copy link
Collaborator Author

@greptile

@waleedlatif1
Copy link
Collaborator Author

@cursor review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

} from '@/app/workspace/[workspaceId]/components/product-tour/tour-shared'
import { useTour } from '@/app/workspace/[workspaceId]/components/product-tour/use-tour'

const logger = createLogger('NavTour')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused logger variable creates dead code

Low Severity

createLogger is imported and logger is created at module scope on line 15, but logger is never referenced anywhere in the file. The useTour hook already creates its own internal logger, making this one entirely dead code.

Fix in Cursor Fix in Web

'slide-in-right': 'slide-in-right 350ms ease-out forwards',
'slide-in-bottom': 'slide-in-bottom 400ms cubic-bezier(0.16, 1, 0.3, 1)',
'tour-tooltip-in': 'tour-tooltip-in 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94)',
'tour-tooltip-fade': 'tour-tooltip-fade 150ms cubic-bezier(0.25, 0.46, 0.45, 0.94)',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused tour-tooltip-fade animation defined but never referenced

Low Severity

The tour-tooltip-fade keyframe and its corresponding animate-tour-tooltip-fade animation are defined in the Tailwind config but the class animate-tour-tooltip-fade is never used anywhere in the codebase. Only animate-tour-tooltip-in is actually referenced (in tour-tooltip.tsx).

Fix in Cursor Fix in Web

Comment on lines +11 to 13
<WorkflowTour />
</div>
</main>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unnecessary overflow-hidden wrapper around WorkflowTour

Joyride portals its overlay and spotlight directly to document.body, so the h-0 w-0 overflow-hidden wrapper has no effect on what the user sees. However, Joyride does render a small internal container node as a child of its React parent. With overflow: hidden on a 0×0 div, any non-portaled DOM that Joyride injects (e.g. a __joyride container) could be silently clipped. Compare with how NavTour is placed in the workspace layout—directly as a sibling with no wrapper at all.

Consider matching that pattern for consistency and to avoid any future surprises with Joyride internal DOM:

Suggested change
<WorkflowTour />
</div>
</main>
<WorkflowTour />

Comment on lines +120 to +125
* totalSteps={5}
* placement="bottom"
* targetEl={document.querySelector('[data-tour="home"]')}
* onNext={handleNext}
* onClose={handleClose}
* />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Arrow SVG is missing the border stroke for the positioned variant

The centered-placement card explicitly adds border border-[var(--border)]. The popover-based (non-centered) variant relies on a drop-shadow filter to simulate a border, which works for the card itself. But the PopoverPrimitive.Arrow SVG only has className='-mt-px fill-[var(--bg)]' — no stroke — so the arrow edge that points toward the target element has no visible border line, creating a small visual inconsistency where the arrow appears to float without a matching outline.

You can add a stroke to the polyline so the arrow's outline matches the card:

Suggested change
* totalSteps={5}
* placement="bottom"
* targetEl={document.querySelector('[data-tour="home"]')}
* onNext={handleNext}
* onClose={handleClose}
* />
className='-mt-px fill-[var(--bg)] stroke-[var(--border)]'
>
<polygon points='0,0 14,0 7,7' className='stroke-none' />
<polyline points='0,0 7,7 14,0' fill='none' strokeWidth={1} />

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants