Skip to content

Conversation

@kaeizen
Copy link
Contributor

@kaeizen kaeizen commented Nov 6, 2025

Summary by CodeRabbit

  • New Features

    • Added three guided tours: Block Backgrounds, Responsive Controls, and Hover States.
    • Getting Started now includes a video plus dynamic quick-start cards for easier access.
    • Tours can be triggered via URL parameters for targeted walkthroughs.
  • Style

    • Improved spacing, grid placement, and layout of the Getting Started/quick-start section for better visual flow.
  • Chores

    • Added a data-attribute to a control container to aid selection/testing.

@kaeizen kaeizen self-assigned this Nov 6, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 6, 2025

Walkthrough

Adds three guided tours ("block-backgrounds", "responsive-controls", "hover-states"), URL-query-based tour condition keys, dynamic selection of tour via the editor ?tour= parameter, a data-driven Getting Started quick-start list (QUICK_BUTTONS) with layout/style changes, and a small data-attribute on base control content.

Changes

Cohort / File(s) Summary
Tour conditions & editor integration
src/components/guided-modal-tour/tour-conditions.js, src/plugins/guided-modal-tour/editor-block-tour.js
Adds three TOUR_CONDITIONS keys (block-backgrounds, responsive-controls, hover-states) that check ?tour= URL params; updates editor tour component to read tour URL param and use a dynamic tourId when rendering GuidedModalTour.
Block backgrounds tour
src/lazy-components/modal-tour/tours/block-backgrounds.js
New exported blockBackgrounds object with initialize() and a multi-step steps array that inserts a predefined block structure and orchestrates UI interactions (preStep/postStep, async coordination) to guide block background customization.
Responsive controls tour
src/lazy-components/modal-tour/tours/responsive-controls.js
New exported responsiveControls object (replaces previous blocks export) with initialize() and multi-step steps that insert blocks, toggle responsive controls, switch device views, and update mobile layout attributes during the tour.
Hover states tour
src/lazy-components/modal-tour/tours/hover-states.js
New exported hoverStates object with initialize() and multi-step steps to insert an image block, open relevant panels, toggle hover-related attributes (e.g., imageZoomHover), and drive UI interactions for hover-state features.
Getting Started UI & styles
src/welcome/getting-started.js, src/welcome/getting-started.scss
Adds QUICK_BUTTONS constant and renders quick-start cards dynamically (includes entries for block-backgrounds, responsive-controls, hover-states), inserts video section, and adjusts grid/card styling and spacing (24px gap, layout/grid placement changes).
Base control attribute
src/components/base-control2/index.js
Adds data-attribute={ props.attribute } to the .stk-control-content container, exposing the control attribute as a data-* attribute in the DOM.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant EditorTour as Editor Tour
    participant TourCond as TOUR_CONDITIONS
    participant BlockBG as Block Backgrounds Tour
    participant RespCtrl as Responsive Controls Tour
    participant HoverSt as Hover States Tour
    participant Editor as WP Editor

    User->>EditorTour: Open editor (maybe ?tour=...)
    EditorTour->>TourCond: Read URL param "tour"
    alt tour = block-backgrounds
        EditorTour->>BlockBG: Render GuidedModalTour(tourId)
        BlockBG->>Editor: initialize() -> insertBlocks() / selectBlock()
    else tour = responsive-controls
        EditorTour->>RespCtrl: Render GuidedModalTour(tourId)
        RespCtrl->>Editor: initialize() -> insertBlocks() / selectBlock()
    else tour = hover-states
        EditorTour->>HoverSt: Render GuidedModalTour(tourId)
        HoverSt->>Editor: initialize() -> insertBlocks() / selectBlock()
    else no param or other
        EditorTour->>EditorTour: Render default "blocks" tour
    end

    loop Tour steps
        Note over Editor,EditorTour: preStep opens panels / selects targets
        Tour->>Editor: preStep/postStep (simulate clicks, update attrs)
        Note right of Editor: async UI actions (setTimeouts, DOM clicks)
        User->>Tour: Progress / interact
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus areas:
    • block-backgrounds.js, responsive-controls.js, hover-states.js: verify block creation trees, attribute updates, race conditions with timeouts, and simulated DOM interactions.
    • tour-conditions.js / editor-block-tour.js: ensure URL parsing and dynamic tour selection don't regress default behavior or security expectations.
    • getting-started.js / .scss: confirm dynamic QUICK_BUTTONS data mapping, accessibility of generated links/buttons, and responsive CSS changes.
    • base-control2/index.js: confirm adding data-attribute is safe and doesn't leak sensitive values.

Possibly related PRs

Poem

🐰
I hopped through layouts, colors, and state,
Guided paws to tweak a block's new trait.
Quick-start carrots point the way,
Tours to learn, then hop away—hooray! 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat: More Guided Tours' accurately describes the main change: adding multiple new guided tour features (block-backgrounds, responsive-controls, hover-states) with corresponding tour conditions and integrations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/more-guided-tours

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Nov 6, 2025

🤖 Pull request artifacts

file commit
pr3639-stackable-3639-merge.zip ccad290

github-actions bot added a commit that referenced this pull request Nov 6, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/welcome/getting-started.js (1)

200-203: Consider making the "Quick Start" label more explicit.

The condition i === 0 assumes the first item should show the label. If the array order changes or items are filtered in the future, this could break.

Consider adding a showLabel property to QUICK_BUTTONS entries:

 const QUICK_BUTTONS = [ {
 	id: 'design-library',
+	showLabel: true,
 	icon: addTemplateIcon,

Then use:

-{ i === 0 && <div className="s-quick-buttons-arrow">
+{ item.showLabel && <div className="s-quick-buttons-arrow">
src/lazy-components/modal-tour/tours/block-backgrounds.js (1)

83-97: Improve resilience of DOM manipulation in tour steps.

The direct querySelector calls and DOM clicks are fragile and may fail if:

  • Selectors change due to WordPress/plugin updates
  • Elements are not yet rendered
  • Class names are dynamically generated differently

Consider:

  1. Adding error handling and logging when elements aren't found
  2. Using data attributes instead of complex class selectors for tour targets
  3. Wrapping DOM operations in try-catch blocks to prevent tour crashes

Example:

preStep: () => {
	try {
		dispatch('core/edit-post').openGeneralSidebar('edit-post/block')
		
		const allBlocks = select('core/block-editor').getBlocks()
		const columnsBlock = allBlocks.find(block => block.name === 'stackable/columns')
		if (columnsBlock) {
			dispatch('core/block-editor').selectBlock(columnsBlock.clientId)
		} else {
			console.warn('Tour: columns block not found')
		}
	} catch (error) {
		console.error('Tour step failed:', error)
	}
},

Also applies to: 110-125, 138-146, 175-188, 193-197

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfefc88 and 1d4042c.

📒 Files selected for processing (5)
  • src/components/guided-modal-tour/tour-conditions.js (1 hunks)
  • src/lazy-components/modal-tour/tours/block-backgrounds.js (1 hunks)
  • src/plugins/guided-modal-tour/editor-block-tour.js (1 hunks)
  • src/welcome/getting-started.js (1 hunks)
  • src/welcome/getting-started.scss (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/plugins/guided-modal-tour/editor-block-tour.js (1)
src/components/guided-modal-tour/index.js (1)
  • GuidedModalTour (27-104)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: PHP 7.3 and WP latest
  • GitHub Check: PHP 8.2 and WP 6.7.2
  • GitHub Check: PHP 7.3 and WP 6.5.5
  • GitHub Check: PHP 8.2 and WP 6.5.5
  • GitHub Check: PHP 8.2 and WP 6.6.2
  • GitHub Check: PHP 8.2 and WP latest
  • GitHub Check: build
🔇 Additional comments (7)
src/components/guided-modal-tour/tour-conditions.js (1)

30-33: LGTM! Consistent tour condition implementation.

The new block-backgrounds tour condition follows the established pattern and correctly checks for the URL parameter to activate the tour.

src/welcome/getting-started.scss (2)

197-200: Good refinement of the SVG sizing rule.

Excluding .s-quick-buttons-arrow from the generic SVG sizing prevents unintended styling conflicts with the decorative arrow element.


239-268: Layout refactor supports the new data-driven cards.

The grid gap adjustment, video positioning, and flex-direction changes align well with the QUICK_BUTTONS rendering strategy in getting-started.js.

src/welcome/getting-started.js (1)

140-156: Verify the purpose of hidden quick-start items.

Two entries (site-kit and design-system-picker) have style: { display: 'none' }. Are these feature-flagged for future release, or should they be removed from the configuration?

Consider one of these approaches:

  • If they're coming soon, add a comment explaining they're feature-flagged
  • If they're experimental, move them to a separate EXPERIMENTAL_BUTTONS array
  • If they're not needed, remove them to reduce maintenance overhead
src/plugins/guided-modal-tour/editor-block-tour.js (1)

5-10: LGTM! Clean integration of the new tour.

The dual-tour rendering is handled correctly. The GuidedModalTour component's internal logic ensures only one tour shows at a time based on URL parameters and conditions.

src/lazy-components/modal-tour/tours/block-backgrounds.js (2)

7-71: Clean block structure initialization.

The nested block creation logic is well-structured and creates appropriate default content for the tour demonstration.


72-210: Well-crafted tour flow and copy.

The step-by-step progression is logical, and the i18n-ready help text provides clear guidance for users learning about backgrounds and containers.

Comment on lines +10 to +65
const blockObject = wp.blocks.createBlock(
'stackable/columns',
{
uniqueId: '1dbe04e',
blockMargin: { bottom: '' },
align: 'full',
containerWidth: 50,
containerHorizontalAlign: 'flex-start',
containerWidthUnit: '%',
},
[
wp.blocks.createBlock(
'stackable/column',
{
uniqueId: 'f957abc',
columnSpacing: {
top: '', right: '', bottom: '', left: '',
},
},
[
wp.blocks.createBlock(
'stackable/heading',
{
uniqueId: 'a8ebea7',
// Retain our text
text: 'Explore the World with Us',
textTag: 'h2',
}
),
wp.blocks.createBlock(
'stackable/text',
{
uniqueId: '57e76a1',
// Retain our text
text: 'Discover breathtaking destinations, plan your next adventure, and make unforgettable memories with our travel guides and tips.',
}
),
wp.blocks.createBlock(
'stackable/button-group',
{ uniqueId: 'e063798' },
[
wp.blocks.createBlock(
'stackable/button',
{
uniqueId: '5d04ca8',
// Retain our text
text: 'Start your journey',
url: '',
}
),
]
),
]
),
]
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential block ID conflicts with hardcoded uniqueIds.

The hardcoded uniqueId values (e.g., '1dbe04e', 'f957abc') could conflict if blocks with these IDs already exist on the page, potentially causing unexpected behavior or errors.

Consider generating unique IDs dynamically:

 const blockObject = wp.blocks.createBlock(
 	'stackable/columns',
 	{
-		uniqueId: '1dbe04e',
+		uniqueId: `tour-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,

Or use a utility function if one exists in the codebase for generating unique block IDs.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/lazy-components/modal-tour/tours/block-backgrounds.js around lines 10 to
65, the blocks are created with hardcoded uniqueId values which can collide with
existing blocks; update the code to stop using fixed IDs by either removing the
uniqueId property (let Gutenberg generate IDs) or replacing each hardcoded
string with a call to a shared ID generator (e.g., const id =
generateUniqueId(); then use uniqueId: id) — ensure you import or use the
project utility if one exists, or add a small helper that produces RFC4122-like
or sufficiently random IDs and call it for every block creation.

Comment on lines +121 to +124
setTimeout( () => {
// Click the tab
document.querySelector( '.edit-post-sidebar__panel-tab.ugb-tab--style:not(.is-active)' )?.click()
}, 100 )
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Race conditions from hardcoded setTimeout delays.

The 100ms delays are arbitrary and may fail on slower systems, during high CPU load, or in different browser environments, causing the tour to break or behave unpredictably.

Consider using requestAnimationFrame or polling with a timeout for more reliable synchronization:

const waitForElement = (selector, timeout = 2000) => {
	return new Promise((resolve, reject) => {
		const startTime = Date.now()
		const check = () => {
			const element = document.querySelector(selector)
			if (element) {
				resolve(element)
			} else if (Date.now() - startTime > timeout) {
				reject(new Error(`Element ${selector} not found`))
			} else {
				requestAnimationFrame(check)
			}
		}
		check()
	})
}

// Usage in preStep:
await waitForElement('.edit-post-sidebar__panel-tab.ugb-tab--style')
document.querySelector('.edit-post-sidebar__panel-tab.ugb-tab--style:not(.is-active)')?.click()

Also applies to: 184-186


I'll verify the review comment by examining the codebase and understanding the context around those hardcoded delays.


🏁 Script executed:

#!/bin/bash
# Check if file exists and view the code around the mentioned lines
if [ -f "src/lazy-components/modal-tour/tours/block-backgrounds.js" ]; then
  echo "=== File found. Showing lines 115-130 ==="
  sed -n '115,130p' "src/lazy-components/modal-tour/tours/block-backgrounds.js"
  echo ""
  echo "=== Showing lines 180-190 ==="
  sed -n '180,190p' "src/lazy-components/modal-tour/tours/block-backgrounds.js"
else
  echo "File not found at expected path"
  # Try to find it
  find . -name "block-backgrounds.js" -type f 2>/dev/null
fi

Length of output: 1368


🏁 Script executed:

#!/bin/bash
# Search for all setTimeout calls in the file to understand usage patterns
echo "=== All setTimeout calls in block-backgrounds.js ==="
rg -n "setTimeout" "src/lazy-components/modal-tour/tours/block-backgrounds.js" -B2 -A4

Length of output: 641


🏁 Script executed:

#!/bin/bash
# Check if there are utility functions for DOM element waiting in the codebase
echo "=== Searching for existing waitFor, waitForElement patterns ==="
rg -n "waitFor|requestAnimationFrame" --type=js -g "!node_modules" | head -30

Length of output: 299062


🏁 Script executed:

#!/bin/bash
# Understand the structure and purpose of this file
echo "=== File context: imports, function definitions ==="
head -50 "src/lazy-components/modal-tour/tours/block-backgrounds.js"

Length of output: 1372


Address race condition risk from hardcoded setTimeout delays in tour component.

Lines 121-124 and 184-186 use arbitrary 100ms delays before querying and clicking DOM elements. On slower systems or under CPU load, the target elements may not exist in the DOM yet, causing the selector to return null and the tour to fail silently. Replace with polling or requestAnimationFrame to ensure elements are present before interaction.

const waitForElement = (selector, timeout = 2000) => {
	return new Promise((resolve, reject) => {
		const startTime = Date.now()
		const check = () => {
			const element = document.querySelector(selector)
			if (element) {
				resolve(element)
			} else if (Date.now() - startTime > timeout) {
				reject(new Error(`Element ${selector} not found`))
			} else {
				requestAnimationFrame(check)
			}
		}
		check()
	})
}

// Usage in preStep:
await waitForElement('.edit-post-sidebar__panel-tab.ugb-tab--style:not(.is-active)')
document.querySelector('.edit-post-sidebar__panel-tab.ugb-tab--style:not(.is-active)')?.click()

Also applies to: 184-186

🤖 Prompt for AI Agents
In src/lazy-components/modal-tour/tours/block-backgrounds.js around lines
121-124 (and similarly at 184-186), the hardcoded setTimeout(100) introduces a
race on DOM availability; replace these timeouts with a small utility that waits
for the element (polling via requestAnimationFrame or a MutationObserver) with a
reasonable timeout, await that promise before calling .click(), and handle
rejection/logging if the element never appears so the tour fails predictably
instead of silently; add the waitForElement helper once and reuse it for both
places.

github-actions bot added a commit that referenced this pull request Nov 6, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
src/lazy-components/modal-tour/tours/responsive-controls.js (2)

65-67: Remove commented-out code.

Dead code should be removed to improve maintainability.

-	// Delete all blocks
-	// const allBlocks = select( 'core/block-editor' ).getBlocks()
-	// dispatch( 'core/block-editor' ).removeBlocks( allBlocks.map( block => block.clientId ) )
-

117-122: Code duplication in preStep and postStep.

The postStep at line 121 duplicates similar logic from the preStep at line 118, and this pattern repeats in subsequent steps (e.g., line 134). This duplication makes the code harder to maintain.

Consider extracting common DOM manipulation patterns into helper functions:

// At the top of the file
const helpers = {
	openResponsiveToggle: () => {
		document.querySelector( '.ugb-sort-control .stk-control-responsive-toggle:not(.is-open) button' )?.click()
	},
	selectDevice: ( device ) => {
		document.querySelector( `.ugb-sort-control .stk-control-responsive-toggle button:not(.is-active)[data-value="${device}"]` )?.click()
	},
}

// Then in steps:
preStep: () => {
	helpers.openResponsiveToggle()
},
postStep: () => {
	helpers.selectDevice( 'mobile' )
},
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d4042c and 3080663.

📒 Files selected for processing (3)
  • src/components/guided-modal-tour/tour-conditions.js (1 hunks)
  • src/lazy-components/modal-tour/tours/responsive-controls.js (1 hunks)
  • src/plugins/guided-modal-tour/editor-block-tour.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/guided-modal-tour/tour-conditions.js
  • src/plugins/guided-modal-tour/editor-block-tour.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: PHP 8.2 and WP 6.6.2
  • GitHub Check: build
  • GitHub Check: PHP 7.3 and WP 6.5.5
  • GitHub Check: PHP 8.2 and WP latest
  • GitHub Check: PHP 8.2 and WP 6.7.2
  • GitHub Check: PHP 7.3 and WP latest
  • GitHub Check: PHP 8.2 and WP 6.5.5
🔇 Additional comments (2)
src/lazy-components/modal-tour/tours/responsive-controls.js (2)

75-199: Well-structured tour flow with good UX guidance.

The tour steps provide clear, progressive guidance through the responsive controls feature with helpful descriptions using interpolated elements for emphasis. The flow logically guides users through enabling, using, and understanding responsive controls.


4-4: No issues found with @wordpress/element dependency.

The codebase widely uses @wordpress/element across 100+ files with no issues. The import of createInterpolateElement from @wordpress/element in the responsive-controls.js file is consistent with this established pattern throughout the codebase, and the package is properly managed in the project's dependency lock file. The original concern is not applicable here.

Comment on lines +10 to +63
const blockObject = wp.blocks.createBlock(
'stackable/columns',
{
uniqueId: '1dbe04e',
blockMargin: { bottom: '' },
align: 'full',
},
[
wp.blocks.createBlock(
'stackable/column',
{
uniqueId: 'f957abc',
hasContainer: true,
columnSpacing: {
top: '', right: '', bottom: '', left: '',
},
},
[
wp.blocks.createBlock(
'stackable/heading',
{
uniqueId: 'a8ebea7',
// Retain our text
text: 'Explore the World with Us',
textTag: 'h2',
}
),
wp.blocks.createBlock(
'stackable/text',
{
uniqueId: '57e76a1',
// Retain our text
text: 'Discover breathtaking destinations, plan your next adventure, and make unforgettable memories with our travel guides and tips.',
}
),
]
),
wp.blocks.createBlock(
'stackable/column',
{
uniqueId: '3dcffca',
},
[
wp.blocks.createBlock(
'stackable/image',
{
uniqueId: 'e063798',
imageExternalUrl: 'https://picsum.photos/id/177/500/700.jpg',
}
),
]
),
]
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Generate unique IDs dynamically to prevent conflicts.

The block creation uses hardcoded uniqueId values ('1dbe04e', 'f957abc', 'a8ebea7', '57e76a1', '3dcffca', 'e063798'). If blocks with these IDs already exist in the editor, this could cause conflicts or unexpected behavior.

Consider using a dynamic ID generation utility if available in Stackable, or generate random IDs at runtime:

+// Helper to generate unique IDs
+const generateUniqueId = () => Math.random().toString(36).substring(2, 9)
+
 const blockObject = wp.blocks.createBlock(
 	'stackable/columns',
 	{
-		uniqueId: '1dbe04e',
+		uniqueId: generateUniqueId(),
 		blockMargin: { bottom: '' },
 		align: 'full',
 	},

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/lazy-components/modal-tour/tours/responsive-controls.js around lines 10
to 63, the blocks are created with hardcoded uniqueId values which can conflict
with existing blocks; replace each hardcoded uniqueId with a dynamically
generated ID at runtime (use the Stackable ID utility if available, e.g.
stackable.utils.generateUniqueId or similar), or fall back to a safe random/UUID
generator to produce unique strings for each block before calling
wp.blocks.createBlock; ensure you generate a fresh ID for every block instance
(parent and all children) and pass those generated IDs into the uniqueId
properties.

'stackable/image',
{
uniqueId: 'e063798',
imageExternalUrl: 'https://picsum.photos/id/177/500/700.jpg',
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

External image dependency may be unreliable.

The tour relies on an external image URL (https://picsum.photos/id/177/500/700.jpg) which could fail if the service is unavailable or slow, potentially disrupting the tour experience.

Consider using a local placeholder image or adding fallback handling for image load failures.

🤖 Prompt for AI Agents
In src/lazy-components/modal-tour/tours/responsive-controls.js around line 57,
the hard-coded external image URL is unreliable; replace it with a local
placeholder asset (import or require a bundled image) or implement fallback
handling by adding an onError handler that swaps the src to a local placeholder
and optionally sets a timeout/placeholder while loading; ensure the local image
is added to the project assets and referenced via the module import so builds
include it and update the tour data to use the local reference or wrapped image
component with the onError fallback.

Comment on lines +86 to +101
preStep: () => {
dispatch( 'core/edit-post' ).openGeneralSidebar( 'edit-post/block' )
// Make sure the Columns block is selected
const allBlocks = select( 'core/block-editor' ).getBlocks()
const columnsBlock = allBlocks.find( block => block.name === 'stackable/columns' )
if ( columnsBlock ) {
dispatch( 'core/block-editor' ).selectBlock( columnsBlock.clientId )
}
setTimeout( () => {
// Open Layout tab if it's not open
document.querySelector( '.edit-post-sidebar__panel-tab.ugb-tab--layout:not(.is-active)' )?.click()
}, 100 )
setTimeout( () => {
document.querySelector( '.ugb-panel--layout:not(.is-opened)' )?.click()
}, 200 )
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Race conditions with hardcoded timeouts.

The preStep uses setTimeout with hardcoded delays (100ms, 200ms) to orchestrate DOM interactions. These delays are brittle and environment-dependent—they may be too short on slower systems or unnecessarily long on faster ones, potentially causing the tour to fail or feel sluggish.

Consider using more reliable synchronization methods:

preStep: async () => {
	dispatch( 'core/edit-post' ).openGeneralSidebar( 'edit-post/block' )
	const allBlocks = select( 'core/block-editor' ).getBlocks()
	const columnsBlock = allBlocks.find( block => block.name === 'stackable/columns' )
	if ( columnsBlock ) {
		dispatch( 'core/block-editor' ).selectBlock( columnsBlock.clientId )
	}
	
	// Wait for DOM to update using requestAnimationFrame or MutationObserver
	await new Promise( resolve => requestAnimationFrame( resolve ) )
	
	document.querySelector( '.edit-post-sidebar__panel-tab.ugb-tab--layout:not(.is-active)' )?.click()
	
	await new Promise( resolve => requestAnimationFrame( resolve ) )
	
	document.querySelector( '.ugb-panel--layout:not(.is-opened)' )?.click()
},

Alternatively, poll for element readiness or use MutationObserver to detect when elements are available.

Comment on lines +146 to +157
postStep: () => {
// Update the order of the columns for mobile by dispatching an attribute update.
const allBlocks = wp.data.select( 'core/block-editor' ).getBlocks()
const columnsBlock = allBlocks.find( block => block.name === 'stackable/columns' )
if ( columnsBlock ) {
// Reverse the order of columns for mobile by updating the mobileOrder attribute.
// The default order is [0, 1]. Swapping makes it [1, 0].
wp.data.dispatch( 'core/block-editor' ).updateBlockAttributes( columnsBlock.clientId, {
columnArrangementMobile: '2,1',
} )
}
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Inconsistent import usage and missing error handling.

Lines 148-149 use wp.data.select and line 153 uses wp.data.dispatch, but select and dispatch were already imported from @wordpress/data at line 3. This inconsistency makes the code harder to maintain.

Additionally, there's no validation that the attribute update succeeded or feedback if the columnsBlock is not found.

 postStep: () => {
-	const allBlocks = wp.data.select( 'core/block-editor' ).getBlocks()
+	const allBlocks = select( 'core/block-editor' ).getBlocks()
 	const columnsBlock = allBlocks.find( block => block.name === 'stackable/columns' )
 	if ( columnsBlock ) {
-		wp.data.dispatch( 'core/block-editor' ).updateBlockAttributes( columnsBlock.clientId, {
+		dispatch( 'core/block-editor' ).updateBlockAttributes( columnsBlock.clientId, {
 			columnArrangementMobile: '2,1',
 		} )
+	} else {
+		console.warn( 'Columns block not found for mobile arrangement update' )
 	}
 },
🤖 Prompt for AI Agents
In src/lazy-components/modal-tour/tours/responsive-controls.js around lines 146
to 157, replace the inconsistent globals wp.data.select/wp.data.dispatch calls
with the already imported select and dispatch from @wordpress/data, check that
columnsBlock exists (and has a clientId) and handle the not-found case (e.g.
early return or log a warning), and wrap the updateBlockAttributes call in a
try/catch or check its result to log an error or provide feedback if the
attribute update fails so the code reliably reports success or failure.

Comment on lines +173 to +178
postStep: () => {
document.querySelector( '.ugb-sort-control .stk-control-responsive-toggle:not(.is-open) button' )?.click()
setTimeout( () => {
document.querySelector( '.ugb-sort-control .stk-control-responsive-toggle button:not(.is-active)[data-value="desktop"]' )?.click()
}, 100 )
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Additional race condition with nested setTimeout.

Similar to the timing issues in Step 1, this postStep uses a nested setTimeout with a 100ms delay that could cause race conditions.

This compounds the timing concerns already raised. Consider implementing the async/await pattern suggested for Step 1 throughout all steps for consistency.

🤖 Prompt for AI Agents
In src/lazy-components/modal-tour/tours/responsive-controls.js around lines 173
to 178, the postStep uses a nested setTimeout(100) which risks race conditions;
replace the nested timeout with an async postStep that awaits DOM-ready/selector
checks and performs clicks sequentially (first click the closed
responsive-toggle button if present, then await the appearance/state change of
the responsive options, then click the desktop button), using a small
polling/wait-for-selector helper or Promise with requestAnimationFrame instead
of setTimeout to ensure deterministic ordering and remove the nested time-based
delay.

github-actions bot added a commit that referenced this pull request Nov 7, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
src/lazy-components/modal-tour/tours/responsive-controls.js (3)

148-155: Call the imported select/dispatch helpers directly.

We already import select and dispatch at the top, but this block still reaches for wp.data.*. Sticking to the imports keeps the bundle self-contained and avoids depending on the global wp namespace (which may not exist in automated contexts).

-				const allBlocks = wp.data.select( 'core/block-editor' ).getBlocks()
+				const allBlocks = select( 'core/block-editor' ).getBlocks()
@@
-					wp.data.dispatch( 'core/block-editor' ).updateBlockAttributes( columnsBlock.clientId, {
+					dispatch( 'core/block-editor' ).updateBlockAttributes( columnsBlock.clientId, {

10-60: Generate block uniqueIds dynamically.

All six blocks are still created with fixed IDs ('1dbe04e', 'f957abc', … 'e063798'). If a user runs this tour more than once—or runs it after the hover-states tour that reuses 'e063798'—Gutenberg encounters duplicate uniqueId attributes and throws invalid-block warnings. This was raised earlier and remains unresolved; we need runtime-generated IDs to keep editor state consistent.

One approach:

 	initialize: () => {
-		// Add some default content that we will select
-
-		const blockObject = wp.blocks.createBlock(
+		const makeUniqueId = ( prefix = 'tour-' ) => `${ prefix }${ Math.random().toString(36).slice(2, 11) }`
+		const blockObject = wp.blocks.createBlock(
 			'stackable/columns',
 			{
-				uniqueId: '1dbe04e',
+				uniqueId: makeUniqueId( 'columns-' ),
@@
 						wp.blocks.createBlock(
 							'stackable/column',
 							{
-								uniqueId: 'f957abc',
+								uniqueId: makeUniqueId( 'column-' ),
@@
 								wp.blocks.createBlock(
 									'stackable/heading',
 									{
-										uniqueId: 'a8ebea7',
+										uniqueId: makeUniqueId( 'heading-' ),
@@
 								wp.blocks.createBlock(
 									'stackable/text',
 									{
-										uniqueId: '57e76a1',
+										uniqueId: makeUniqueId( 'text-' ),
@@
 						wp.blocks.createBlock(
 							'stackable/column',
 							{
-								uniqueId: '3dcffca',
+								uniqueId: makeUniqueId( 'column-' ),
@@
 								wp.blocks.createBlock(
 									'stackable/image',
 									{
-										uniqueId: 'e063798',
+										uniqueId: makeUniqueId( 'image-' ),

86-178: Eliminate hard-coded timeouts from tour steps.

The tour flow is stitched together with setTimeout(100/200) (Lines 94-100, 118-121, 175-177). Those arbitrary sleeps are fragile—on slower editors the selectors aren’t ready yet (steps stall), while on faster environments they just add lag. Please replace them with deterministic waits (e.g., an async waitForElement helper or MutationObserver) so each click happens only after the UI element exists and is in the expected state.

Here’s a sketch of how you can restructure the first occurrence; the same helper can be reused for the other steps:

-				setTimeout( () => {
-					// Open Layout tab if it's not open
-					document.querySelector( '.edit-post-sidebar__panel-tab.ugb-tab--layout:not(.is-active)' )?.click()
-				}, 100 )
-				setTimeout( () => {
-					document.querySelector( '.ugb-panel--layout:not(.is-opened)' )?.click()
-				}, 200 )
+				const waitForElement = selector => new Promise( resolve => {
+					const tick = () => {
+						const node = document.querySelector( selector )
+						return node ? resolve( node ) : requestAnimationFrame( tick )
+					}
+					tick()
+				} )
+
+				waitForElement( '.edit-post-sidebar__panel-tab.ugb-tab--layout' )
+					.then( tab => {
+						if ( ! tab.classList.contains( 'is-active' ) ) {
+							tab.click()
+						}
+						return waitForElement( '.ugb-panel--layout' )
+					} )
+					.then( panel => {
+						if ( ! panel.classList.contains( 'is-opened' ) ) {
+							panel.click()
+						}
+					} )

Apply the same waiting strategy for the mobile/desktop toggles later in the tour.

🧹 Nitpick comments (2)
src/components/base-control2/index.js (1)

112-112: LGTM! Data attribute added for guided tour targeting.

The addition of the data-attribute aligns well with the PR's guided tour functionality, enabling easier targeting of specific controls.

Consider using a more specific attribute name like data-stk-attribute or data-control-attribute to improve clarity in DevTools and avoid potential naming conflicts with other libraries or user code.

-				<div className="stk-control-content" data-attribute={ props.attribute }>
+				<div className="stk-control-content" data-stk-attribute={ props.attribute }>
src/lazy-components/modal-tour/tours/hover-states.js (1)

76-83: Use the imported data helpers instead of wp.data.*.

Inside this module we already depend on the bundled build. Leaning on the global wp.data mixes patterns and can break when the globals are not exposed (e.g., automated tests). Please import select alongside dispatch and call those helpers directly for consistency.

You can streamline it as follows:

-import { dispatch } from '@wordpress/data'
+import { dispatch, select } from '@wordpress/data'
@@
-				const allBlocks = wp.data.select( 'core/block-editor' ).getBlocks()
+				const allBlocks = select( 'core/block-editor' ).getBlocks()
@@
-					wp.data.dispatch( 'core/block-editor' ).updateBlockAttributes( imageBlock.clientId, {
+					dispatch( 'core/block-editor' ).updateBlockAttributes( imageBlock.clientId, {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3080663 and ccad290.

📒 Files selected for processing (4)
  • src/components/base-control2/index.js (1 hunks)
  • src/components/guided-modal-tour/tour-conditions.js (1 hunks)
  • src/lazy-components/modal-tour/tours/hover-states.js (1 hunks)
  • src/lazy-components/modal-tour/tours/responsive-controls.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/guided-modal-tour/tour-conditions.js
🧰 Additional context used
🧬 Code graph analysis (2)
src/lazy-components/modal-tour/tours/hover-states.js (1)
src/lazy-components/modal-tour/tours/responsive-controls.js (3)
  • blockObject (10-63)
  • allBlocks (89-89)
  • allBlocks (148-148)
src/lazy-components/modal-tour/tours/responsive-controls.js (1)
src/lazy-components/modal-tour/tours/hover-states.js (2)
  • blockObject (10-16)
  • allBlocks (77-77)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: build
  • GitHub Check: PHP 7.3 and WP 6.5.5
  • GitHub Check: PHP 8.2 and WP latest
  • GitHub Check: PHP 8.2 and WP 6.7.2
  • GitHub Check: PHP 8.2 and WP 6.5.5
  • GitHub Check: PHP 7.3 and WP latest
  • GitHub Check: PHP 8.2 and WP 6.6.2

Comment on lines +10 to +16
const blockObject = wp.blocks.createBlock(
'stackable/image',
{
uniqueId: 'e063798',
imageExternalUrl: 'https://picsum.photos/id/177/500/700.jpg',
}
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not reuse hard-coded uniqueId values.

'e063798' is already used by the responsive-controls tour, so running both tours in one editor session produces duplicate block uniqueIds. Gutenberg treats those IDs as primary keys—duplicates trigger invalid-block warnings and can break attribute syncing. Please generate a fresh ID each time you insert the block instead of reusing a constant.

Here is one way to do it:

 export const hoverStates = {
 	initialize: () => {
-		// Add some default content that we will select
-
-		const blockObject = wp.blocks.createBlock(
+		const makeUniqueId = ( prefix = 'tour-' ) => `${ prefix }${ Math.random().toString(36).slice(2, 11) }`
+		const blockObject = wp.blocks.createBlock(
 			'stackable/image',
 			{
-				uniqueId: 'e063798',
+				uniqueId: makeUniqueId( 'hover-image-' ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const blockObject = wp.blocks.createBlock(
'stackable/image',
{
uniqueId: 'e063798',
imageExternalUrl: 'https://picsum.photos/id/177/500/700.jpg',
}
)
const makeUniqueId = ( prefix = 'tour-' ) => `${ prefix }${ Math.random().toString(36).slice(2, 11) }`
const blockObject = wp.blocks.createBlock(
'stackable/image',
{
uniqueId: makeUniqueId( 'hover-image-' ),
imageExternalUrl: 'https://picsum.photos/id/177/500/700.jpg',
}
)
🤖 Prompt for AI Agents
In src/lazy-components/modal-tour/tours/hover-states.js around lines 10-16, the
block is created with a hard-coded uniqueId ('e063798') which collides with
other tours; replace the constant with a freshly generated ID each time you
insert the block. Generate a new uniqueId at runtime (for example using a
UUID/timestamp/random generator or the appropriate WP utility) and pass that
generated value into the block attributes instead of the hard-coded string so
each created block has a distinct uniqueId.

Comment on lines +24 to +31
setTimeout( () => {
document.querySelector( '.edit-post-sidebar__panel-tab.ugb-tab--style:not(.is-active)' )?.click()
setTimeout( () => {
document.querySelector( '.ugb-panel--image:not(.is-opened)' )?.click()
const target = document.querySelector( '.stk-control:has([data-attribute="imageZoom"])' )
target?.scrollIntoView( { behavior: 'auto', block: 'center' } )
}, 100 )
}, 200 )
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace brittle timeouts with deterministic waits.

The nested setTimeout(200)setTimeout(100) chain is inherently racey: on slower hosts the elements may not exist yet (tour stalls), while on faster hosts the extra delays make the tour feel unresponsive. Please switch to an explicit wait-for-selector helper (e.g., polling with requestAnimationFrame or a small promise that resolves once document.querySelector finds the panel) so the clicks fire exactly when the UI is ready rather than after arbitrary sleeps.

A safer pattern could look like:

-		setTimeout( () => {
-			document.querySelector( '.edit-post-sidebar__panel-tab.ugb-tab--style:not(.is-active)' )?.click()
-			setTimeout( () => {
-				document.querySelector( '.ugb-panel--image:not(.is-opened)' )?.click()
-				const target = document.querySelector( '.stk-control:has([data-attribute="imageZoom"])' )
-				target?.scrollIntoView( { behavior: 'auto', block: 'center' } )
-			}, 100 )
-		}, 200 )
+		const waitForElement = selector => new Promise( resolve => {
+			const tick = () => {
+				const node = document.querySelector( selector )
+				return node ? resolve( node ) : requestAnimationFrame( tick )
+			}
+			tick()
+		} )
+
+		waitForElement( '.edit-post-sidebar__panel-tab.ugb-tab--style' )
+			.then( tab => {
+				if ( ! tab.classList.contains( 'is-active' ) ) {
+					tab.click()
+				}
+				return waitForElement( '.ugb-panel--image' )
+			} )
+			.then( panel => {
+				if ( ! panel.classList.contains( 'is-opened' ) ) {
+					panel.click()
+				}
+				document
+					.querySelector( '.stk-control:has([data-attribute="imageZoom"])' )
+					?.scrollIntoView( { behavior: 'auto', block: 'center' } )
+			} )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setTimeout( () => {
document.querySelector( '.edit-post-sidebar__panel-tab.ugb-tab--style:not(.is-active)' )?.click()
setTimeout( () => {
document.querySelector( '.ugb-panel--image:not(.is-opened)' )?.click()
const target = document.querySelector( '.stk-control:has([data-attribute="imageZoom"])' )
target?.scrollIntoView( { behavior: 'auto', block: 'center' } )
}, 100 )
}, 200 )
const waitForElement = selector => new Promise( resolve => {
const tick = () => {
const node = document.querySelector( selector )
return node ? resolve( node ) : requestAnimationFrame( tick )
}
tick()
} )
waitForElement( '.edit-post-sidebar__panel-tab.ugb-tab--style' )
.then( tab => {
if ( ! tab.classList.contains( 'is-active' ) ) {
tab.click()
}
return waitForElement( '.ugb-panel--image' )
} )
.then( panel => {
if ( ! panel.classList.contains( 'is-opened' ) ) {
panel.click()
}
document
.querySelector( '.stk-control:has([data-attribute="imageZoom"])' )
?.scrollIntoView( { behavior: 'auto', block: 'center' } )
} )
🤖 Prompt for AI Agents
In src/lazy-components/modal-tour/tours/hover-states.js around lines 24 to 31,
replace the nested setTimeout calls with deterministic waits: implement a small
waitForSelector helper that polls (via requestAnimationFrame or setInterval)
until document.querySelector(selector) returns an element or a timeout elapses,
then use it to await the first selector
'.edit-post-sidebar__panel-tab.ugb-tab--style:not(.is-active)' and click it,
then await the second selector '.ugb-panel--image:not(.is-opened)' and click it,
then query '.stk-control:has([data-attribute="imageZoom"])' and scrollIntoView;
remove the arbitrary 200/100ms sleeps so the actions run only once the UI
elements are present.

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.

2 participants