Add interactive tour#510
Conversation
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
/run-security-scan |
alexcos20
left a comment
There was a problem hiding this comment.
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]);There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
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.