From 96ae5126b24625c444bd29f2cf29f15c2656260b Mon Sep 17 00:00:00 2001 From: Sawyer Date: Fri, 18 Sep 2026 02:41:31 -0700 Subject: [PATCH] fix(web): dismissing the first-run tour unmounts it (CL-8523) Closing, skipping or finishing the tour only persisted the seen flag; the Joyride instance kept running for the rest of the session, holding two containers appended to document.body and an overlay above the app. Dismissal now also stops the tour so it unmounts. --- apps/web/src/shell/first-run-tour.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/web/src/shell/first-run-tour.tsx b/apps/web/src/shell/first-run-tour.tsx index 175148b5b..7b97be484 100644 --- a/apps/web/src/shell/first-run-tour.tsx +++ b/apps/web/src/shell/first-run-tour.tsx @@ -61,10 +61,12 @@ const STEPS: readonly Step[] = [ * been seen for this user, so it costs nothing on every later visit. */ export function FirstRunTour({ userId }: { readonly userId: string }) { - const [run] = useState(() => !hasSeenTour(userId)); - - if (!run) return null; + const [run, setRun] = useState(() => !hasSeenTour(userId)); + // Dismissing has to unmount Joyride, not just remember the dismissal: + // a running Joyride keeps two portals appended to `document.body` and + // an overlay over the app, and those portal containers are managed + // outside React's tree. function handleCallback(data: CallBackProps) { // The tooltip's close (X) button fires action "close" without ever // moving status to FINISHED or SKIPPED, so it has to be treated as a @@ -76,9 +78,12 @@ export function FirstRunTour({ userId }: { readonly userId: string }) { data.action === ACTIONS.CLOSE ) { markTourSeen(userId); + setRun(false); } } + if (!run) return null; + return (