Skip to content

Commit 08e2d77

Browse files
committed
fix: Show Panel button now works — win capture-phase race with driver.js
driver.js registers a capture-phase click handler on document that calls stopImmediatePropagation() for all clicks inside the popover. Our handler was being re-registered (losing priority) every time openPanel changed identity. Use a ref so the listener stays first in the chain, and call stopImmediatePropagation ourselves to block driver.js from swallowing it. Made-with: Cursor
1 parent b82bc5f commit 08e2d77

1 file changed

Lines changed: 12 additions & 5 deletions

File tree

flexfoil-ui/src/onboarding/OnboardingProvider.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,12 @@ export function OnboardingProvider({ children }: OnboardingProviderProps) {
4949
const challengeIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
5050
const currentChallengeRef = useRef<Challenge | null>(null);
5151

52-
// Get layout context for panel focusing
52+
// Get layout context for panel focusing.
53+
// Ref avoids re-subscribing the capture-phase click listener (which must
54+
// stay registered before driver.js's own listener to win the race).
5355
const { openPanel } = useLayout();
56+
const openPanelRef = useRef(openPanel);
57+
openPanelRef.current = openPanel;
5458

5559
// Clean up challenge polling
5660
const clearChallengePolling = useCallback(() => {
@@ -310,25 +314,28 @@ export function OnboardingProvider({ children }: OnboardingProviderProps) {
310314
}, [clearChallengePolling, buildChallengeHTML, openPanel]);
311315

312316
// Delegated click handler for "Show Panel" buttons injected into tour popovers.
313-
// Active only while a tour is running so it doesn't interfere otherwise.
317+
// MUST register before driver.js's own capture-phase handler (which calls
318+
// stopImmediatePropagation on all popover clicks). We use a ref for openPanel
319+
// so this effect only re-runs on isActive change, keeping our listener first.
314320
useEffect(() => {
315321
if (!isActive) return;
316322

317323
const handleShowPanel = (e: MouseEvent) => {
318324
const btn = (e.target as HTMLElement).closest<HTMLButtonElement>('[data-open-panel]');
319325
if (!btn) return;
320326
e.stopPropagation();
327+
e.stopImmediatePropagation();
328+
e.preventDefault();
321329
const panelId = btn.dataset.openPanel;
322330
if (panelId) {
323-
openPanel(panelId);
324-
// After opening, give layout a moment to settle then refresh the highlight
331+
openPanelRef.current(panelId);
325332
setTimeout(() => driverRef.current?.refresh(), 150);
326333
}
327334
};
328335

329336
document.addEventListener('click', handleShowPanel, true);
330337
return () => document.removeEventListener('click', handleShowPanel, true);
331-
}, [isActive, openPanel]);
338+
}, [isActive]);
332339

333340
// Cleanup on unmount
334341
useEffect(() => {

0 commit comments

Comments
 (0)