Skip to content

feat: guided tour component#842

Merged
rohanchkrabrty merged 3 commits into
mainfrom
guided-tour
Jul 10, 2026
Merged

feat: guided tour component#842
rohanchkrabrty merged 3 commits into
mainfrom
guided-tour

Conversation

@rohanchkrabrty

Copy link
Copy Markdown
Contributor

Summary

  • Add the guided Tour component (RFC 003) for building step-by-step onboarding flows, built on Base UI Popover with a spotlight overlay
  • Split the implementation into composable parts (tour-root, tour-content, tour-overlay, tour-parts, tour-context) with supporting hooks (use-tour-target, use-prefers-reduced-motion) and shared types
  • Export the component from the package root and add full test coverage for stepping, targeting, and overlay edge cases
  • Wire up docs and playground: MDX guide, props reference, live demo, and interactive tour examples on the www site

@rohanchkrabrty rohanchkrabrty self-assigned this Jul 2, 2026
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Jul 9, 2026 10:41am

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b9f19526-b0e0-4830-a9de-a74e595ab398

📥 Commits

Reviewing files that changed from the base of the PR and between 85aa0e0 and 3f67556.

📒 Files selected for processing (9)
  • apps/www/src/components/playground/tour-examples.tsx
  • packages/raystack/components/tour/tour-content.tsx
  • packages/raystack/components/tour/tour-context.tsx
  • packages/raystack/components/tour/tour-overlay.tsx
  • packages/raystack/components/tour/tour-parts.tsx
  • packages/raystack/components/tour/tour-root.tsx
  • packages/raystack/components/tour/use-prefers-reduced-motion.ts
  • packages/raystack/components/tour/use-tour-target.ts
  • packages/raystack/components/tour/utils.ts
💤 Files with no reviewable changes (3)
  • packages/raystack/components/tour/use-prefers-reduced-motion.ts
  • packages/raystack/components/tour/tour-context.tsx
  • packages/raystack/components/tour/tour-parts.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/www/src/components/playground/tour-examples.tsx
  • packages/raystack/components/tour/tour-content.tsx
  • packages/raystack/components/tour/use-tour-target.ts
  • packages/raystack/components/tour/tour-root.tsx

📝 Walkthrough

Walkthrough

This PR adds a new Tour component package under packages/raystack/components/tour, including types, context, target resolution, root orchestration, overlay/content/parts subcomponents, styling, and utility helpers. The public exports are moved from tour-beta to tour, and the new package is wired into docs, tests, playground/demo components, and the apps/www tour example page. The docs now reference Tour.Content in place of Tour.Popover.

Suggested reviewers: rsbh, rohilsurana, paanSinghCoder

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main addition: a guided tour component.
Description check ✅ Passed The description is clearly related and matches the PR's new Tour component, docs, tests, and examples.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (2)
packages/raystack/components/tour/use-tour-target.ts (1)

118-125: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Observing the entire document.body subtree on every active step.

childList: true, subtree: true on document.body means every DOM mutation anywhere on the page re-runs reconcile() for every active tour instance. On pages with frequent unrelated DOM churn this adds continuous overhead while a tour step is active. Given the target is usually scoped to a small part of the page, consider scoping the observer to a narrower ancestor when derivable, or debouncing reconcile().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/tour/use-tour-target.ts` around lines 118 - 125,
The MutationObserver setup in useTourTarget is watching the entire document.body
subtree, which causes reconcile() to run on unrelated DOM churn during active
steps. Update the observer in useTourTarget to scope the observation to a
narrower ancestor when the target can be derived, or add debouncing/throttling
around reconcile() so frequent mutations don’t trigger continuous rechecks.
apps/www/src/content/docs/components/tour/props.ts (1)

144-153: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Doc type mirrors lose fidelity for the render-function signature and the event union.

TourContentProps.children's render-function parameter is typed unknown here, but the real signature is TourRenderProps (step/index/totalSteps/isFirstStep/isLastStep/status/actions) per tour-content.tsx. Similarly, TourEvent is flattened into one interface with all fields optional, hiding that step/status are only present for specific type values (per types.ts's discriminated union). Readers relying solely on the auto-generated table won't see either shape.

Consider exporting a TourRenderProps interface here (referenced from an <auto-type-table> block) and adding a short prose note next to the TourEvent table clarifying which fields apply to which type.

Also applies to: 186-188

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/www/src/content/docs/components/tour/props.ts` around lines 144 - 153,
The generated docs types are losing the real shapes for
`TourContentProps.children` and `TourEvent`. Update the docs in this section to
reference the actual `TourRenderProps` shape used by `tour-content.tsx` instead
of `unknown`, and make `TourEvent` reflect the discriminated union from
`types.ts` so `step` and `status` are only shown where they apply. Use the
existing `TourContentProps`, `TourRenderProps`, and `TourEvent` symbols to place
the correction, and add a brief note near the event table clarifying which
fields belong to each `type`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/www/src/components/playground/tour-examples.tsx`:
- Around line 29-37: The `settings` tour example has a content/position
mismatch: the description says the popover is anchored on the right, but the
`settings` object in `tour-examples.tsx` uses `side: 'bottom'`. Update the
`content` text or the `side` value so they agree, and keep the example in sync
with the intended behavior of `settings` and its
`spotlightRadius`/`spotlightPadding` configuration.

In `@packages/raystack/components/tour/tour-overlay.tsx`:
- Around line 192-199: The TourOverlay div is letting consumer props override
implementation-critical attributes because `{...rest}` is spread after
`aria-hidden`, `data-status`, `data-entered`, and `data-hole-open`. Update
`TourOverlay` so these chrome attributes are applied last, or otherwise
explicitly filter/override conflicting props from `rest`, ensuring
`aria-hidden`, `data-entered`, `data-hole-open`, and `data-status` always come
from the component state.

In `@packages/raystack/components/tour/tour-root.tsx`:
- Around line 416-431: The scroll-into-view effect in tour-root.tsx is
re-running on unrelated re-renders because it depends on the unstable step
object, which can fight user scrolling during an active tour. Update the effect
in TourRoot to key off the stable index primitive instead of step, while keeping
the existing guards for popoverOpen, anchor, and reducedMotion so it only
scrolls when the active step truly changes.

In `@packages/raystack/components/tour/use-tour-target.ts`:
- Around line 64-131: Function-type TourTarget values are re-triggering
useTourTarget’s effect on every parent render because the dependency list keys
off target identity, which keeps recreating the MutationObserver and restarting
the timeout. Update useTourTarget to avoid relying on referential equality for
function targets in the effect lifecycle—either resolve/comparison-cache
function targets per render or otherwise stabilize them before the effect
logic—so onNotFound can still fire reliably. Keep the fix within useTourTarget
and the resolveTourTarget/reconcile flow, and ensure inline function targets no
longer reset the timeout on unrelated renders.
- Around line 86-131: The timeout handling in useTourTarget allows
onNotFoundRef.current to be scheduled again after it has already fired, which
breaks the “fires once” behavior. Add a one-time latch in the
reconcile/startTimer/timeout path so that once the not-found callback runs,
subsequent reconcile calls for the same unresolved target do not schedule
another timeout. Keep the fix localized to useTourTarget and preserve the
existing clearTimer behavior for resolve/reconnect cases.
- Around line 6-36: resolveTourTarget currently returns Element and ref targets
without enforcing the documented connectedness guarantee, so direct callers can
receive detached nodes. Update resolveTourTarget in use-tour-target.ts so the
Element and RefObject branches also verify .isConnected and return null for
orphaned nodes, matching the behavior already enforced by useTourTarget’s
reconcile() and preserving the docstring contract.

---

Nitpick comments:
In `@apps/www/src/content/docs/components/tour/props.ts`:
- Around line 144-153: The generated docs types are losing the real shapes for
`TourContentProps.children` and `TourEvent`. Update the docs in this section to
reference the actual `TourRenderProps` shape used by `tour-content.tsx` instead
of `unknown`, and make `TourEvent` reflect the discriminated union from
`types.ts` so `step` and `status` are only shown where they apply. Use the
existing `TourContentProps`, `TourRenderProps`, and `TourEvent` symbols to place
the correction, and add a brief note near the event table clarifying which
fields belong to each `type`.

In `@packages/raystack/components/tour/use-tour-target.ts`:
- Around line 118-125: The MutationObserver setup in useTourTarget is watching
the entire document.body subtree, which causes reconcile() to run on unrelated
DOM churn during active steps. Update the observer in useTourTarget to scope the
observation to a narrower ancestor when the target can be derived, or add
debouncing/throttling around reconcile() so frequent mutations don’t trigger
continuous rechecks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 481fc9fa-773c-41e2-b85f-368b5e06a592

📥 Commits

Reviewing files that changed from the base of the PR and between 75c5cc1 and 85aa0e0.

📒 Files selected for processing (22)
  • apps/www/src/app/examples/tour/page.tsx
  • apps/www/src/components/demo/demo.tsx
  • apps/www/src/components/playground/index.ts
  • apps/www/src/components/playground/tour-examples.tsx
  • apps/www/src/components/tour-demo.tsx
  • apps/www/src/content/docs/components/tour/demo.ts
  • apps/www/src/content/docs/components/tour/index.mdx
  • apps/www/src/content/docs/components/tour/props.ts
  • docs/rfcs/003-guided-tour-component.md
  • packages/raystack/components/tour/__tests__/tour.test.tsx
  • packages/raystack/components/tour/index.tsx
  • packages/raystack/components/tour/tour-content.tsx
  • packages/raystack/components/tour/tour-context.tsx
  • packages/raystack/components/tour/tour-overlay.tsx
  • packages/raystack/components/tour/tour-parts.tsx
  • packages/raystack/components/tour/tour-root.tsx
  • packages/raystack/components/tour/tour.module.css
  • packages/raystack/components/tour/tour.tsx
  • packages/raystack/components/tour/types.ts
  • packages/raystack/components/tour/use-prefers-reduced-motion.ts
  • packages/raystack/components/tour/use-tour-target.ts
  • packages/raystack/index.tsx

Comment thread apps/www/src/components/playground/tour-examples.tsx
Comment thread packages/raystack/components/tour/tour-overlay.tsx Outdated
Comment thread packages/raystack/components/tour/tour-root.tsx Outdated
Comment thread packages/raystack/components/tour/use-tour-target.ts Outdated
Comment thread packages/raystack/components/tour/use-tour-target.ts
Comment thread packages/raystack/components/tour/use-tour-target.ts
* Whether two rects match within half a pixel on every side. Shared with the
* root's reveal loop, which feeds it `DOMRect`s (`x`/`y` mirror `left`/`top`).
*/
export function rectsEqual(a: SpotlightRect, b: SpotlightRect) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Move rectsEqual (and SpotlightRect) out of the overlay into a shared utils.ts.
Right now rectsEqual lives in the overlay component but tour-root.tsx imports it — so the root is reaching into a leaf component to grab geometry helpers.

That's backwards: the parent shouldn't depend on internals of a child.

Comment thread packages/raystack/components/tour/tour-root.tsx Outdated
Comment thread packages/raystack/components/tour/use-tour-target.ts
Comment thread packages/raystack/components/tour/use-tour-target.ts Outdated
Comment thread packages/raystack/components/tour/use-tour-target.ts
@rohanchkrabrty rohanchkrabrty merged commit 75a8a6b into main Jul 10, 2026
5 checks passed
@rohanchkrabrty rohanchkrabrty deleted the guided-tour branch July 10, 2026 04:29
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