feat(ui): add interactive product tour with react joyride by adithyaakrishna · Pull Request #3655 · simstudioai/sim
Greptile Summary
This PR adds an interactive product tour using React Joyride for first-time users. The tour spans 13 steps across the home page and workflow editor, auto-starts on first login, and can be re-triggered via Help > "Take a tour" in the sidebar. The implementation uses a custom TourTooltip backed by Radix UI's Popover with a virtual anchor for precise positioning, and the tour state is persisted in localStorage.
Key observations:
- Cross-page step skipping: All 13 steps are in a flat array spanning two routes. Since there is no navigation between pages, users who trigger the tour from the workflow editor silently skip all 6 home-page steps (and vice versa), making the tour effectively page-scoped rather than the cross-page experience described in the PR.
- Orphaned
data-tourattributes:data-tour="new-workflow",data-tour="panel-menu", anddata-tour="run-button"are added to DOM elements but do not correspond to any step intour-steps.ts. - Fragile sidebar selectors: Steps 3–5 use bare CSS class selectors (
.sidebar-container,.workflows-section) instead of thedata-tourattribute pattern used by every other step, making them vulnerable to CSS refactoring. refCallbackref-forwarding bug: ThehasSetRefguard inTourTooltiphas inverted timing with the effect reset, causing joyride's internal ref to benullafter each step transition. This is currently harmless because joyride's floater is hidden, but is a latent correctness issue.- Accessibility: The invisible
refDivforwardsaria-modalfrom joyride's tooltip props, which would declare an empty zero-size element as a modal dialog to screen readers.
Confidence Score: 3/5
- The PR introduces a useful first-time onboarding flow with no critical runtime errors, but has a UX design gap (cross-page step skipping), orphaned data attributes, fragile CSS selectors, and a latent ref-forwarding bug in the tooltip.
- No breaking changes or security issues, and the new ProductTour component is isolated from core workflow logic. The main concern is the fragmented tour experience (users on the editor never see home steps) which contradicts the PR's stated intent. The ref bug in TourTooltip is currently dormant. Orphaned data-tour attributes are dead code. These collectively keep the score at 3 — mergeable with fixes.
- Pay close attention to
product-tour.tsx(cross-page skipping logic),tour-tooltip.tsx(hasSetRef timing and aria-modal forwarding),tour-steps.ts(CSS class selectors for steps 3–5), andpanel.tsx/sidebar.tsx(orphaned data-tour attributes).
Important Files Changed
| Filename | Overview |
|---|---|
| apps/sim/app/workspace/[workspaceId]/components/product-tour/product-tour.tsx | Core tour orchestrator: manages run/stepIndex state, auto-start on first login, and manual re-trigger via custom event. The retrigger timer is now correctly tracked via a ref. However, the callback handler silently skips all cross-page steps with no user navigation, meaning users on the workflow editor never see home-page steps. |
| apps/sim/app/workspace/[workspaceId]/components/product-tour/tour-tooltip.tsx | Custom tooltip renderer using Radix Popover with a virtual anchor for positioning. The hasSetRef guard has inverted timing logic that leaves joyride's internal ref as null after each step change. Also forwards aria-modal to an invisible zero-size div, which creates an accessibility concern. |
| apps/sim/app/workspace/[workspaceId]/components/product-tour/tour-steps.ts | Defines all 13 tour steps spanning both the home page and workflow editor. Steps 3–5 use bare CSS class selectors (.sidebar-container, .workflows-section) inconsistent with the data-tour="..." pattern used elsewhere, making them fragile to refactoring. |
| apps/sim/app/workspace/[workspaceId]/layout.tsx | Mounts ProductTour inside WorkspacePermissionsProvider in the workspace layout, giving it lifetime across all workspace routes. Clean integration. |
| apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx | Adds the "Take a tour" button in the Help section and dispatches START_TOUR_EVENT. Also adds a data-tour="new-workflow" attribute to the + button that has no matching tour step in tour-steps.ts. |
| apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx | Adds data-tab-button attributes (toolbar/copilot/editor) for tour targeting and data-tour="deploy-run". Also adds data-tour="panel-menu" and data-tour="run-button" which have no matching tour steps — orphaned attributes. |
| apps/sim/components/emcn/components/popover/popover.tsx | Adds arrowClassName prop to PopoverContent and showArrow support with a custom SVG arrow. Well-structured addition to the existing CVA-based component, consistent with existing prop patterns. |
Sequence Diagram
sequenceDiagram
participant Layout as WorkspaceLayout
participant PT as ProductTour
participant LS as localStorage
participant Joyride as Joyride (dynamic)
participant TT as TourTooltip
participant Sidebar as Sidebar
participant Win as window
Layout->>PT: mount (on workspace load)
PT->>LS: isTourCompleted()?
alt first-time user
LS-->>PT: false
PT->>PT: setTimeout(1200ms) → setRun(true), stepIndex=0
PT->>Joyride: run=true, stepIndex=0
Joyride->>TT: render step tooltip
TT->>TT: querySelector(step.target)
alt target found
TT->>TT: createPortal(Popover, document.body)
else target not found (cross-page step)
Joyride-->>PT: callback(TARGET_NOT_FOUND)
PT->>PT: setStepIndex(index + 1) — silent skip
end
Note over PT,Joyride: User clicks Next/Back/Skip/Done
Joyride-->>PT: callback(STEP_AFTER / FINISHED / SKIPPED)
PT->>LS: markTourCompleted()
PT->>PT: setRun(false)
else returning user
LS-->>PT: true → no auto-start
end
Sidebar->>Win: dispatchEvent(START_TOUR_EVENT)
Win-->>PT: handleStartTour()
PT->>LS: resetTourCompletion()
PT->>PT: setRun(false) → setTourKey(k+1) → setTimeout(50ms) → setRun(true)
Last reviewed commit: "chore: updated modal..."