Skip to content

Add interactive tour#510

Open
dnsi0 wants to merge 11 commits into
mainfrom
add-interactive-tour
Open

Add interactive tour#510
dnsi0 wants to merge 11 commits into
mainfrom
add-interactive-tour

Conversation

@dnsi0

@dnsi0 dnsi0 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #506 .

Adds an interactive tutorial/onboarding system to guide users through key dashboard workflows. Four feature tours are included: Run Job flow, Run Node flow, Owner Profile, and Consumer Profile. The implementation uses a lightweight custom React context with localStorage persistence (no external library dependency), an overlay component that highlights UI elements step-by-step, and a tutorial button component placed on relevant pages to allow users to start or replay tours.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@vercel

vercel Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
nodes-dashboard Ready Ready Preview, Comment Jun 9, 2026 7:47am

Request Review

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@ndrpp

ndrpp commented Jun 8, 2026

Copy link
Copy Markdown
Member

/run-security-scan

@alexcos20 alexcos20 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI automated code review (Gemini 3).

Overall risk: low

Summary:
The PR introduces a well-designed, interactive feature tour utilizing React context and DOM positioning overlays. The architecture successfully leverages data-attributes for low-coupling element tracking. However, two primary areas require optimization: (1) React state semantics—avoiding side effects within state updaters and fixing a hydration mismatch due to synchronous localStorage reads during rendering, and (2) Performance—resolving severe DOM thrashing caused by querying elements continuously on every animation frame.

Comments:
• [ERROR][bug] React state updater functions must be pure. Currently, side effects like writeActive and markCompleted are executed inside the setActive((prev) => ...) functional updater. Additionally, isCompleted reads localStorage synchronously during render, causing React hydration mismatches on the client. Fix both by introducing a completed local state and rewriting advance and goToStep to use the current active state from their closure.

--- a/src/components/tutorial/tutorial-context.tsx
+++ b/src/components/tutorial/tutorial-context.tsx
@@ -51,19 +51,26 @@
   }
 };
 
-const markCompleted = (id: TutorialId) => {
-  if (typeof window === 'undefined') return;
-  const list = readCompleted();
-  if (!list.includes(id)) {
-    list.push(id);
-    localStorage.setItem(COMPLETED_KEY, JSON.stringify(list));
-  }
-};
-
 export const TutorialProvider = ({ children }: { children: React.ReactNode }) => {
   const [active, setActive] = useState<ActiveTutorial | null>(null);
+  const [completed, setCompleted] = useState<string[]>([]);
 
   useEffect(() => {
     setActive(readActive());
+    setCompleted(readCompleted());
   }, []);
 
+  const markCompleted = useCallback((id: TutorialId) => {
+    setCompleted((prev) => {
+      if (!prev.includes(id)) {
+        const next = [...prev, id];
+        if (typeof window !== 'undefined') {
+          localStorage.setItem(COMPLETED_KEY, JSON.stringify(next));
+        }
+        return next;
+      }
+      return prev;
+    });
+  }, []);
+
   const start = useCallback((id: TutorialId, startIndex = 0) => {
     const next = { id, stepIndex: startIndex };
@@ -80,29 +87,27 @@
   }, []);
 
   const advance = useCallback(() => {
-    setActive((prev) => {
-      if (!prev) return prev;
-      const config = getTutorialConfig(prev.id);
-      const nextIndex = prev.stepIndex + 1;
-      if (nextIndex >= config.steps.length) {
-        markCompleted(prev.id);
-        writeActive(null);
-        return null;
-      }
-      const next = { id: prev.id, stepIndex: nextIndex };
-      writeActive(next);
-      return next;
-    });
-  }, []);
+    if (!active) return;
+    const config = getTutorialConfig(active.id);
+    const nextIndex = active.stepIndex + 1;
+    if (nextIndex >= config.steps.length) {
+      markCompleted(active.id);
+      writeActive(null);
+      setActive(null);
+    } else {
+      const next = { id: active.id, stepIndex: nextIndex };
+      writeActive(next);
+      setActive(next);
+    }
+  }, [active, markCompleted]);
 
   const goToStep = useCallback((index: number) => {
-    setActive((prev) => {
-      if (!prev) return prev;
-      const config = getTutorialConfig(prev.id);
-      if (index < 0 || index >= config.steps.length) return prev;
-      const next = { id: prev.id, stepIndex: index };
-      writeActive(next);
-      return next;
-    });
-  }, []);
+    if (!active) return;
+    const config = getTutorialConfig(active.id);
+    if (index < 0 || index >= config.steps.length) return;
+    const next = { id: active.id, stepIndex: index };
+    writeActive(next);
+    setActive(next);
+  }, [active]);
 
   const isCompleted = useCallback((id: TutorialId) => {
-    return readCompleted().includes(id);
-  }, []);
+    return completed.includes(id);
+  }, [completed]);

• [WARNING][performance] The measure function queries the DOM with document.querySelector(selector) continuously on every animation frame. This produces severe layout thrashing and rapidly consumes system resources. You should cache the element reference and only fallback to re-querying if it disconnects from the DOM. Additionally, redundant explicit scroll and resize event listeners should be removed since requestAnimationFrame loops continuously anyway.

--- a/src/components/tutorial/tutorial-overlay.tsx
+++ b/src/components/tutorial/tutorial-overlay.tsx
@@ -98,12 +98,17 @@
     let frame = 0;
     let cancelled = false;
+    let cachedEl: Element | null = null;
 
     const measure = () => {
-      const el = document.querySelector(selector);
+      if (!cachedEl || !cachedEl.isConnected) {
+        cachedEl = document.querySelector(selector);
+      }
+      const el = cachedEl;
       if (!el) {
         setElement(null);
         setRect(null);
         return;
       }
@@ -131,16 +136,10 @@
 
     tick();
 
-    const handleResize = () => measure();
-    window.addEventListener('resize', handleResize);
-    window.addEventListener('scroll', handleResize, true);
-
     return () => {
       cancelled = true;
       window.cancelAnimationFrame(frame);
-      window.removeEventListener('resize', handleResize);
-      window.removeEventListener('scroll', handleResize, true);
     };
   }, [selector]);

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@ndrpp ndrpp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

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.

Guided tour dashboard

4 participants