From 3400baee05ebdbb025700124a2066603b219f38f Mon Sep 17 00:00:00 2001 From: John Moorman <45500240+mojoro@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:07:27 +0200 Subject: [PATCH 1/5] refactor: make openingDrillTourSteps a function that resolves steps based on viewport width Converts the static openingDrillTourSteps array to a function that returns mobile or desktop step configurations based on window width. This allows different targetIds and placements per breakpoint without changing the tourConfigs schema or any call sites. --- src/constants/tours.ts | 74 ++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/src/constants/tours.ts b/src/constants/tours.ts index 8bf34aba..b3a130e0 100644 --- a/src/constants/tours.ts +++ b/src/constants/tours.ts @@ -135,40 +135,44 @@ export const handBrainTourSteps: TourStep[] = [ }, ] -export const openingDrillTourSteps: TourStep[] = [ - { - id: 'opening-drill-overview', - title: 'Welcome to Opening Drills', - description: - 'Practice your openings by drilling specific openings against Maia. First select the openings you want to practice, configure drill settings, and practice them repeatedly until they become second nature. You can track your progress and analyze your performance after each drill.', - targetId: 'opening-drill-modal', - placement: 'bottom', - }, - { - id: 'opening-drill-selection', - title: 'Select Openings', - description: - 'Browse and select the openings you want to practice. You can search for specific variations and add them using the + button.', - targetId: 'opening-drill-browse', - placement: 'right', - }, - { - id: 'opening-drill-start', - title: 'Start Drilling', - description: - 'Customize your drills by choosing the Maia opponent strength, how many moves in each drill, and how many drills to do. Click "Start Drilling" to start!', - targetId: 'opening-drill-selected', - placement: 'left', - }, - { - id: 'opening-drill-gameplay', - title: 'Drill Experience', - description: - "During drilling, you'll play through your selected openings against Maia until reaching the target move count. After each drill, you can analyze the moves you played, see how the Maia and Stockfish evaluations changed throughout the game, and learn from your insights.", - targetId: 'opening-drill-modal', - placement: 'bottom', - }, -] +const openingDrillTourSteps = (): TourStep[] => { + const isMobile = typeof window !== 'undefined' && window.innerWidth <= 1024 + + return [ + { + id: 'opening-drill-overview', + title: 'Welcome to Opening Drills', + description: + 'Practice your openings by drilling specific openings against Maia. First select the openings you want to practice, configure drill settings, and practice them repeatedly until they become second nature. You can track your progress and analyze your performance after each drill.', + targetId: 'opening-drill-modal', + placement: 'bottom', + }, + { + id: 'opening-drill-selection', + title: 'Select Openings', + description: + 'Browse and select the openings you want to practice. You can search for specific variations and add them using the + button.', + targetId: 'opening-drill-browse', + placement: isMobile ? 'bottom' : 'right', + }, + { + id: 'opening-drill-start', + title: 'Start Drilling', + description: + 'Customize your drills by choosing the Maia opponent strength, how many moves in each drill, and how many drills to do. Click "Start Drilling" to start!', + targetId: isMobile ? 'opening-drill-selected' : 'opening-drill-preview', + placement: isMobile ? 'bottom' : 'left', + }, + { + id: 'opening-drill-gameplay', + title: 'Drill Experience', + description: + "During drilling, you'll play through your selected openings against Maia until reaching the target move count. After each drill, you can analyze the moves you played, see how the Maia and Stockfish evaluations changed throughout the game, and learn from your insights.", + targetId: 'opening-drill-modal', + placement: 'bottom', + }, + ] +} export const tourConfigs = { analysis: { @@ -199,6 +203,6 @@ export const tourConfigs = { openingDrill: { id: 'openingDrill', name: 'Opening Drill Tour', - steps: openingDrillTourSteps, + steps: openingDrillTourSteps(), }, } From 749957ffa4872b538c3673b9146f5617cdffc2b4 Mon Sep 17 00:00:00 2001 From: John Moorman <45500240+mojoro@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:14:38 +0200 Subject: [PATCH 2/5] feat: add beforeAction hook to tour steps for mobile tab switching Adds optional beforeAction callback to TourStep interface, executed before each step transition in handleNext and handlePrevious. Used by the opening drill tour to dispatch synthetic click events on mobile tab buttons so the correct panel is visible before Joyride targets it. --- src/constants/tours.ts | 14 ++++++++++++++ src/contexts/TourContext.tsx | 13 +++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/constants/tours.ts b/src/constants/tours.ts index b3a130e0..3caf492c 100644 --- a/src/constants/tours.ts +++ b/src/constants/tours.ts @@ -154,6 +154,13 @@ const openingDrillTourSteps = (): TourStep[] => { 'Browse and select the openings you want to practice. You can search for specific variations and add them using the + button.', targetId: 'opening-drill-browse', placement: isMobile ? 'bottom' : 'right', + ...(isMobile && { + beforeAction: () => { + document + .getElementById('opening-drill-browse') + ?.dispatchEvent(new MouseEvent('click', { bubbles: true })) + }, + }), }, { id: 'opening-drill-start', @@ -162,6 +169,13 @@ const openingDrillTourSteps = (): TourStep[] => { 'Customize your drills by choosing the Maia opponent strength, how many moves in each drill, and how many drills to do. Click "Start Drilling" to start!', targetId: isMobile ? 'opening-drill-selected' : 'opening-drill-preview', placement: isMobile ? 'bottom' : 'left', + ...(isMobile && { + beforeAction: () => { + document + .getElementById('opening-drill-selected') + ?.dispatchEvent(new MouseEvent('click', { bubbles: true })) + }, + }), }, { id: 'opening-drill-gameplay', diff --git a/src/contexts/TourContext.tsx b/src/contexts/TourContext.tsx index a5a32680..a5edb150 100644 --- a/src/contexts/TourContext.tsx +++ b/src/contexts/TourContext.tsx @@ -16,6 +16,7 @@ export interface TourStep { targetId: string placement?: 'top' | 'bottom' | 'left' | 'right' offset?: { x: number; y: number } + beforeAction?: () => void | Promise } export interface TourState { @@ -497,6 +498,10 @@ export const TourProvider: React.FC = ({ children }) => { const handleNext = useCallback(async () => { if (tourState.currentStep < tourState.steps.length - 1) { const nextStepIndex = tourState.currentStep + 1 + const nextStepData = tourState.steps[nextStepIndex] + if (nextStepData?.beforeAction) { + await nextStepData.beforeAction() + } await preScrollToTarget(nextStepIndex) nextStep() } else { @@ -504,7 +509,7 @@ export const TourProvider: React.FC = ({ children }) => { } }, [ tourState.currentStep, - tourState.steps.length, + tourState.steps, nextStep, endTour, preScrollToTarget, @@ -513,10 +518,14 @@ export const TourProvider: React.FC = ({ children }) => { const handlePrevious = useCallback(async () => { if (tourState.currentStep > 0) { const prevStepIndex = tourState.currentStep - 1 + const prevStepData = tourState.steps[prevStepIndex] + if (prevStepData?.beforeAction) { + await prevStepData.beforeAction() + } await preScrollToTarget(prevStepIndex) prevStep() } - }, [tourState.currentStep, prevStep, preScrollToTarget]) + }, [tourState.currentStep, tourState.steps, prevStep, preScrollToTarget]) // Update Joyride steps when tour state changes useEffect(() => { From 5ae588a4fb9dc0a8eee2274a74f92f9041305128 Mon Sep 17 00:00:00 2001 From: John Moorman <45500240+mojoro@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:38:29 +0200 Subject: [PATCH 3/5] fix: fix drill tour step targets for mobile and desktop - Add beforeAction to TourStep interface for pre-step DOM interactions - Execute beforeAction in handleNext/handlePrevious before transitions - Mobile step 2: dispatch click on Selected tab before targeting it - Mobile step 3: dispatch click on Selected tab, target panel content - Desktop step 3: target opening-drill-queue section - Rename SelectedPanel id to opening-drill-selected-panel to avoid duplicate ids with the mobile tab button - Add id to Queue section in DrillStudioPanel for desktop targeting --- src/components/Openings/OpeningSelectionModal.tsx | 4 ++-- src/constants/tours.ts | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/Openings/OpeningSelectionModal.tsx b/src/components/Openings/OpeningSelectionModal.tsx index 70065d5f..10721d1c 100644 --- a/src/components/Openings/OpeningSelectionModal.tsx +++ b/src/components/Openings/OpeningSelectionModal.tsx @@ -1196,7 +1196,7 @@ const DrillStudioPanel: React.FC<{ {/* Queue */} -
+

Queue @@ -1308,7 +1308,7 @@ const SelectedPanel: React.FC<{ showTargetSlider, }) => (

diff --git a/src/constants/tours.ts b/src/constants/tours.ts index 3caf492c..aa2a314f 100644 --- a/src/constants/tours.ts +++ b/src/constants/tours.ts @@ -182,8 +182,17 @@ const openingDrillTourSteps = (): TourStep[] => { title: 'Drill Experience', description: "During drilling, you'll play through your selected openings against Maia until reaching the target move count. After each drill, you can analyze the moves you played, see how the Maia and Stockfish evaluations changed throughout the game, and learn from your insights.", - targetId: 'opening-drill-modal', - placement: 'bottom', + targetId: isMobile + ? 'opening-drill-selected-panel' + : 'opening-drill-queue', + placement: isMobile ? 'bottom' : 'left', + ...(isMobile && { + beforeAction: () => { + document + .getElementById('opening-drill-selected') + ?.dispatchEvent(new MouseEvent('click', { bubbles: true })) + }, + }), }, ] } From 7a383ae2b7adc38ad77acde19ac95363bd5a32bd Mon Sep 17 00:00:00 2001 From: John Moorman <45500240+mojoro@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:42:11 +0200 Subject: [PATCH 4/5] fix: bump drill modal layout breakpoint from md to lg The two-column desktop layout (browse panel + preview) breaks at widths below ~851px, but the mobile tab layout didn't activate until md (768px). Bump layout-critical breakpoints to lg (1024px) to match the existing TABLET_BREAKPOINT_PX used by WindowSizeContext. --- src/components/Openings/OpeningSelectionModal.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Openings/OpeningSelectionModal.tsx b/src/components/Openings/OpeningSelectionModal.tsx index 10721d1c..f8c998c6 100644 --- a/src/components/Openings/OpeningSelectionModal.tsx +++ b/src/components/Openings/OpeningSelectionModal.tsx @@ -347,7 +347,7 @@ const TabNavigation: React.FC<{ const { isMobile } = useContext(WindowSizeContext) return ( -
+