Skip to content

Conversation

@kaeizen
Copy link
Contributor

@kaeizen kaeizen commented Sep 24, 2025

Summary by CodeRabbit

  • New Features

    • Per-item on-demand preview rendering (shouldRender) to defer previews until needed.
    • Immediate addition of page designs when selected in the Pages tab.
  • Performance

    • Memoized list rendering, batched preview generation, idle-scheduled updates, visibility-triggered loading, and background prefetching of patterns.
  • UI/UX

    • Cleaner spinner fade/hide behavior and more accurate dynamic card heights for smoother transitions.
  • Reliability

    • Stronger loading gating and consistent selection handling to reduce flicker and unnecessary work.

@coderabbitai
Copy link

coderabbitai bot commented Sep 24, 2025

Walkthrough

Reworks list/item rendering: items memoized and no longer ref-forwarded; introduces idle- and visibility-gated shouldRender propagated to useShadowRoot and usePreviewRenderer; preview rendering batched/staged with updateShadowBodySize and rAF/idle DOM writes; spinner and modal/editor styles adjusted; pages prefetch added.

Changes

Cohort / File(s) Summary of Changes
List item & parent wiring
src/components/design-library-list/design-library-list-item.js, src/components/design-library-list/index.js
Converted item from forwardRefmemo; removed forwarded ref; added public shouldRender, designIndex, and presetMarks; parent schedules idle-based shouldRender flips, uses IntersectionObserver for visibility gating, keys items by `design.id
Preview renderer hook
src/components/design-library-list/use-preview-renderer.js
Signature changed to accept shouldRender; introduced internal previewSize, renderedTemplate, shadowBodySizeRef, updateShadowBodySize; batched design fetch/substitution; rendering gated by shouldRender; return expanded to include previewSize and updateShadowBodySize.
Shadow root hook
src/components/design-library-list/use-shadow-root.js
useShadowRoot now accepts shouldRender, uses useState for shadowRoot, effect depends on [shouldRender], sets shadowRoot via setter, returns { hostRef, shadowRoot }.
Design preview updates
src/components/design-library-list/design-preview.js
DesignPreview adds props designIndex, updateShadowBodySize, setIsLoading; uses wrapperRef for DOM writes instead of direct dangerous innerHTML usage; performs staged innerHTML writes (rAF + idle) in some cases and invokes updateShadowBodySize; manages loading state.
Spinner & styles
src/components/design-library-list/editor.scss, src/components/modal-design-library/editor.scss
Replaced granular spinner rules with .stk-spinner-container full-size overlay using opacity transition and .stk-hide-spinner; removed fixed pages-spinner height; tightened modal full-pages grid-row to grid-row: 1 / 4; added footer background-color: #fff.
List render simplification
src/components/design-library-list/index.js
Removed placeholder vs actual-item branching; parent directly renders DesignLibraryListItem with shouldRender/presetMarks; idle-callback + IntersectionObserver toggle shouldRender; added selectedTab prop usage.
Design fetching behavior
src/design-library/index.js
getDesigns now triggers fetchDesignLibrary() side-effect when type === 'pages' to prefetch patterns into cache.
Auxiliary hooks & handlers
src/components/design-library-list/use-auto-scroll.js, src/components/design-library-list/use-shadow-root.js, src/components/design-library-list/use-preview-renderer.js
Added useCallback/useState imports where needed; refactored effects to include shouldRender; added updateShadowBodySize plumbing and local refs; stabilized handlers.
Modal & block interactions
src/components/modal-design-library/modal.js, src/components/modal-design-library/index.js, src/block/design-library/edit.js
Introduced memoized callbacks (onSelectDesign, onClose, onChangeApiVersion) via useCallback; centralized selection logic and passed stable handlers into modal/list.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Parent as DesignLibraryList
  participant ItemWrap as Item wrapper
  participant Item as DesignLibraryListItem
  participant SR as useShadowRoot
  participant PR as usePreviewRenderer
  participant DP as DesignPreview

  Parent->>ItemWrap: render item (shouldRender=false)
  ItemWrap->>ItemWrap: schedule setShouldRender(true) (requestIdleCallback) / observe visibility (IntersectionObserver)
  alt shouldRender becomes true
    ItemWrap->>Item: mount with shouldRender, designIndex, presetMarks
    Item->>SR: useShadowRoot(shouldRender)
    SR-->>Item: { hostRef, shadowRoot }
    Item->>PR: usePreviewRenderer(props, shouldRender, spacingSize, ref, hostRef, shadowRoot, setIsLoading)
    PR-->>Item: { blocks, previewSize, updateShadowBodySize, onClickDesign, ... }
    Item->>DP: mount DesignPreview (passes updateShadowBodySize, setIsLoading)
    DP->>DP: write innerHTML into wrapperRef (direct or staged via rAF/idle)
    DP->>PR: updateShadowBodySize()
    DP->>Item: setIsLoading(false)
  else shouldRender false
    Item-->>Parent: render lightweight placeholder (spinner hidden)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I nibble idle cycles, wait for light,
Shadow roots wake when flags are right.
Wrapper cradles HTML safe and warm,
Sizes settle slowly after the rAF storm.
Hopping previews — code takes flight. 🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The current title emphasizes a scrolling fix, but the changeset is primarily a broad refactor of the DesignLibraryList components, including memoization, removal of ref forwarding, updated hooks (usePreviewRenderer, useShadowRoot), addition of shouldRender gating, and IntersectionObserver-based lazy rendering. Only minor scrollMargin adjustments are present, making scrolling improvements a negligible part of the diff. As a result, the title misrepresents the main scope of the PR and may mislead reviewers scanning history. Please revise the title to summarize the core changes, such as adding memoization and lazy rendering with IntersectionObserver, refactoring preview rendering hooks, and removing ref forwarding—for example: “refactor(design library): add memoization and lazy rendering to DesignLibraryListItem.”
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/design-library-full-pages-scrolling

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 bot added a commit that referenced this pull request Sep 24, 2025
@github-actions
Copy link

github-actions bot commented Sep 24, 2025

🤖 Pull request artifacts

file commit
pr3601-stackable-3601-merge.zip 32c2eef

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/design-library-list/use-shadow-root.js (1)

25-75: Prevent duplicate style injection when shouldRender toggles.

Every time the item re-enters the viewport, this effect runs and appends the same styles again into the shadow root, causing unbounded duplication and performance/memory issues.

Apply a guard to run only once per host (or dedupe by IDs):

-	useEffect( () => {
-	  if ( shouldRender && hostRef.current ) {
+	useEffect( () => {
+	  if ( ! shouldRender || ! hostRef.current || shadowRoot ) {
+	    return
+	  }
 	    const shadow = hostRef.current.shadowRoot || hostRef.current.attachShadow( { mode: 'open' } )
@@
-			styleNodes.forEach( node => {
+			styleNodes.forEach( node => {
 				if ( node.textContent ) {
 					// we use :host in the shadow DOM to target the root
 					node.textContent = node.textContent.replace( /:root/g, ':host' )
 				}
-				shadow.appendChild( node )
+				// Avoid duplicates by ID
+				if ( node.id && shadow.getElementById( node.id ) ) {
+					return
+				}
+				shadow.appendChild( node )
 			} )
@@
-			setShadowRoot( shadow )
-	  }
-	}, [ shouldRender ] )
+			setShadowRoot( shadow )
+	  }
+	}, [ shouldRender, shadowRoot ] )
🧹 Nitpick comments (7)
src/design-library/index.js (1)

66-69: Avoid blocking on prefetch; fire-and-forget and align version.

Awaiting the prefetch will unnecessarily delay returning pages. Also, use the same reset/version for consistency.

-	// pre-fetch patterns
-	if ( type === 'pages' ) {
-		await fetchDesignLibrary()
-	}
+	// Prefetch patterns non-blocking (use same reset/version)
+	if ( type === 'pages' ) {
+		fetchDesignLibrary( reset, LATEST_API_VERSION, 'patterns' )
+			.catch( () => {} )
+	}
src/components/design-library-list/editor.scss (1)

235-241: Verify spinner height matches JS placeholder height to avoid jump.

Item-pages placeholder height defaults to 413px in JS, but spinner container is 343px. Consider aligning to prevent layout jank.

-		height: 343px;
+		height: 413px;

If 343px is intentional, update getCardHeight’s fallback for pages accordingly for consistency.

src/components/design-library-list/index.js (1)

97-111: Avoid global querySelector; derive root from the wrapper to reduce coupling.

Document-level query can pick the wrong container if multiple modals exist. Prefer closest ancestor or pass the container ref.

-		const rootEl = document.querySelector( '.ugb-modal-design-library__designs' )
+		const rootEl = wrapperRef.current?.closest( '.ugb-modal-design-library__designs' )

Alternatively, thread the parent’s containerRef down as a prop and use it directly.

src/components/design-library-list/design-library-list-item.js (1)

71-73: Minor: use destructured selectedNum for consistency.

Nitpick only; no behavior change.

-	if ( ! shouldRender && ! props.selectedNum ) {
+	if ( ! shouldRender && ! selectedNum ) {
 		return <div style={ { height: `${ getCardHeight() }px` } } />
 	}
src/components/design-library-list/use-preview-renderer.js (3)

253-261: Guard against missing design content in pages aggregation.

If a fetched design lacks template/content, replacePlaceholders will throw. Coalesce to empty string.

-				const designsContentForPreview = designs.map( ( design, i ) =>
-					replacePlaceholders( design.template || design.content, categorySlugs[ i ], false )
+				const designsContentForPreview = designs.map( ( design, i ) => {
+					const dc = design.template || design.content || ''
+					return replacePlaceholders( dc, categorySlugs[ i ], false )
+				}
 				).join( '\n' )
@@
-					? designs.map( ( design, i ) =>
-						replacePlaceholders( design.template || design.content, categorySlugs[ i ], true )
-					)
+					? designs.map( ( design, i ) => {
+						const dc = design.template || design.content || ''
+						return replacePlaceholders( dc, categorySlugs[ i ], true )
+					} )
 					: designsContentForPreview

293-305: Handle initialization errors to avoid stuck loading state.

Catch and clear loading on failures to prevent a permanent spinner.

-		initialize().then( () => {
+		initialize().then( () => {
@@
-			renderedTemplate.current = template
-		} )
+			renderedTemplate.current = template
+		} ).catch( () => {
+			setIsLoading( false )
+		} )

145-147: Nit: variable naming.

Use lowerCamelCase for local variables.

-				const CardHeightKey = enableBackground ? 'background' : 'noBackground'
-				newCardHeight[ CardHeightKey ] = cardRect.height
+				const cardHeightKey = enableBackground ? 'background' : 'noBackground'
+				newCardHeight[ cardHeightKey ] = cardRect.height
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cc8b75 and f78d06c.

📒 Files selected for processing (6)
  • src/components/design-library-list/design-library-list-item.js (4 hunks)
  • src/components/design-library-list/editor.scss (1 hunks)
  • src/components/design-library-list/index.js (5 hunks)
  • src/components/design-library-list/use-preview-renderer.js (9 hunks)
  • src/components/design-library-list/use-shadow-root.js (3 hunks)
  • src/design-library/index.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/components/design-library-list/use-preview-renderer.js (6)
src/components/design-library-list/design-library-list-item.js (3)
  • props (27-34)
  • spacingSize (38-38)
  • ref (44-44)
src/components/design-library-list/index.js (2)
  • props (22-30)
  • shouldRender (95-95)
src/components/design-library-list/design-preview.js (1)
  • ref (18-18)
src/components/design-library-list/use-shadow-root.js (2)
  • hostRef (11-11)
  • shadowRoot (12-12)
src/components/design-library-list/util.js (11)
  • blocks (235-235)
  • getCategorySlug (325-335)
  • getCategorySlug (325-335)
  • replacePlaceholders (183-193)
  • replacePlaceholders (183-193)
  • cleanParse (15-47)
  • cleanParse (15-47)
  • addBackgroundScheme (106-152)
  • addBackgroundScheme (106-152)
  • adjustPatternSpacing (154-181)
  • adjustPatternSpacing (154-181)
src/design-library/index.js (4)
  • designs (7-7)
  • fetchDesign (41-46)
  • fetchDesign (41-46)
  • design (102-102)
src/components/design-library-list/use-shadow-root.js (2)
src/components/design-library-list/design-library-list-item.js (1)
  • useShadowRoot (42-42)
src/components/design-library-list/index.js (1)
  • shouldRender (95-95)
src/components/design-library-list/design-library-list-item.js (3)
src/components/design-library-list/index.js (2)
  • props (22-30)
  • shouldRender (95-95)
src/components/design-library-list/use-preview-renderer.js (4)
  • props (41-53)
  • usePreviewRenderer (37-377)
  • usePreviewRenderer (37-377)
  • cardHeight (59-59)
src/components/design-library-list/use-shadow-root.js (4)
  • useShadowRoot (10-77)
  • useShadowRoot (10-77)
  • hostRef (11-11)
  • shadowRoot (12-12)
src/components/design-library-list/index.js (3)
src/components/modal-design-library/modal.js (1)
  • selectedTab (62-62)
src/components/design-library-list/design-library-list-item.js (2)
  • DesignLibraryListItem (26-164)
  • props (27-34)
src/components/design-library-list/use-preview-renderer.js (1)
  • props (41-53)
⏰ 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). (6)
  • 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.6.2
  • GitHub Check: PHP 7.3 and WP 6.5.5
  • GitHub Check: PHP 7.3 and WP latest
  • GitHub Check: PHP 8.2 and WP 6.5.5
🔇 Additional comments (3)
src/components/design-library-list/index.js (3)

29-30: Prop added: selectedTab. Ensure all callers pass it.

This component now depends on selectedTab for item behavior and sizing.

If any call sites omit it, default to 'patterns' to preserve prior behavior.


65-72: Good: stable keys and prop spreading.

Using design.id/designId as key and spreading preview props improves stability and clarity.


119-121: LGTM: child-level render gating via shouldRender.

This simplifies the parent and reduces placeholder churn.

github-actions bot added a commit that referenced this pull request Sep 24, 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/components/design-library-list/use-preview-renderer.js (1)

301-313: Confirm spinner lifecycle is intentional.

You only call setIsLoading(false) here, never setting it back to true for subsequent loads unless adopting the refactor above. Ensure this matches the intended UX; otherwise, spinners won’t appear for later template fetches.

src/components/design-library-list/design-library-list-item.js (1)

114-117: Default scale to 1 to avoid invalid CSS.

When previewSize.scale is undefined (e.g., very early render), scale(undefined) is invalid. Fallback to 1.

-						transform: `scale(${ selectedNum && selectedData ? selectedData.selectedPreviewSize.scale : previewSize?.scale })`,
+						transform: `scale(${ selectedNum && selectedData ? selectedData.selectedPreviewSize.scale : ( previewSize?.scale ?? 1 ) })`,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f78d06c and 0de30d8.

📒 Files selected for processing (4)
  • src/components/design-library-list/design-library-list-item.js (5 hunks)
  • src/components/design-library-list/design-preview.js (2 hunks)
  • src/components/design-library-list/index.js (4 hunks)
  • src/components/design-library-list/use-preview-renderer.js (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/design-library-list/index.js
🧰 Additional context used
🧬 Code graph analysis (3)
src/components/design-library-list/design-preview.js (2)
src/components/design-library-list/use-auto-scroll.js (1)
  • NOOP (6-6)
src/components/design-library-list/use-preview-renderer.js (2)
  • blocks (55-55)
  • updateShadowBodySize (76-86)
src/components/design-library-list/use-preview-renderer.js (5)
src/components/design-library-list/design-library-list-item.js (3)
  • props (27-34)
  • spacingSize (38-38)
  • ref (44-44)
src/components/design-library-list/index.js (2)
  • props (22-30)
  • shouldRender (95-95)
src/components/design-library-list/use-shadow-root.js (3)
  • hostRef (11-11)
  • shadowRoot (12-12)
  • useSelect (14-14)
src/components/design-library-list/util.js (11)
  • blocks (235-235)
  • getCategorySlug (325-335)
  • getCategorySlug (325-335)
  • replacePlaceholders (183-193)
  • replacePlaceholders (183-193)
  • cleanParse (15-47)
  • cleanParse (15-47)
  • addBackgroundScheme (106-152)
  • addBackgroundScheme (106-152)
  • adjustPatternSpacing (154-181)
  • adjustPatternSpacing (154-181)
src/design-library/index.js (4)
  • designs (7-7)
  • fetchDesign (41-46)
  • fetchDesign (41-46)
  • design (102-102)
src/components/design-library-list/design-library-list-item.js (3)
src/components/design-library-list/index.js (2)
  • props (22-30)
  • shouldRender (95-95)
src/components/design-library-list/use-preview-renderer.js (5)
  • props (41-53)
  • usePreviewRenderer (37-390)
  • usePreviewRenderer (37-390)
  • cardHeight (59-59)
  • updateShadowBodySize (76-86)
src/components/design-library-list/use-shadow-root.js (4)
  • useShadowRoot (10-77)
  • useShadowRoot (10-77)
  • hostRef (11-11)
  • shadowRoot (12-12)
⏰ 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). (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 6.5.5
  • GitHub Check: PHP 8.2 and WP 6.6.2
  • GitHub Check: PHP 7.3 and WP latest
🔇 Additional comments (5)
src/components/design-library-list/use-preview-renderer.js (2)

314-316: Good fix: keep prevSelectedTabRef in sync.

This addresses the earlier bug where the “tab didn’t change” optimization broke.


145-158: Card height initialization looks good.

Initial run sets both background/noBackground heights; later runs update the active one only. Matches the intended behavior.

src/components/design-library-list/design-preview.js (1)

54-75: Event listener scoping is correct.

Listeners are attached only when relevant (non-pages) and properly cleaned up.

src/components/design-library-list/design-library-list-item.js (2)

67-70: Operator precedence fix in getCardHeight looks good.

This resolves the earlier ternary/coalescing bug.


72-74: Efficient placeholder rendering before shouldRender.

Lightweight placeholder avoids work while preserving layout.

github-actions bot added a commit that referenced this pull request Sep 24, 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: 0

🧹 Nitpick comments (3)
src/components/design-library-list/use-preview-renderer.js (3)

59-61: Initialize state objects with empty objects instead of empty arrays.

React compares dependency arrays using shallow comparison, and these state variables are used as objects throughout the hook. The initialization should match their expected structure.

-	const [ cardHeight, setCardHeight ] = useState( {} )
-	const [ previewSize, setPreviewSize ] = useState( {} )
+	const [ cardHeight, setCardHeight ] = useState( {} )
+	const [ previewSize, setPreviewSize ] = useState( {} )

Actually, the current initialization is correct - these are objects, not arrays.


260-277: Optimize batch fetching and processing of designs.

The change from individual processing to batch fetching and processing is a good performance improvement. However, there are potential issues with the current implementation.

#!/bin/bash
# Verify if fetchDesign handles concurrent calls properly to avoid race conditions

echo "Searching for fetchDesign implementation to check caching behavior..."
fd -t f -e js fetchDesign | head -5 | xargs rg -A 10 -B 2 "fetchDesign.*async"

echo -e "\nChecking if there's any concurrency handling in the design fetching..."
rg -A 5 -B 5 "Promise\.all.*fetchDesign"

323-347: Improve dependency array and effect logic.

The dependency array includes multiple objects and complex dependencies that could cause unnecessary re-renders. The effect logic has proper guards but could be optimized.

ESLint warns when your effect uses variables not listed in the dependency array, and omitting dependencies might use stale values. Consider splitting this effect or using more specific dependencies:

#!/bin/bash
# Check if any of the dependencies in this useEffect are objects that get recreated frequently

echo "Checking for object dependencies that might cause unnecessary re-renders..."
rg -A 3 -B 3 "containerScheme|backgroundScheme" --type js

echo -e "\nLooking for where these scheme objects are created..."
ast-grep --pattern 'containerScheme = $_'
ast-grep --pattern 'backgroundScheme = $_'

The current dependency list mixes primitive values with potentially complex objects, which could lead to performance issues.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0de30d8 and 52cde3d.

📒 Files selected for processing (2)
  • src/components/design-library-list/design-preview.js (2 hunks)
  • src/components/design-library-list/use-preview-renderer.js (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/design-library-list/design-preview.js
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/design-library-list/use-preview-renderer.js (5)
src/components/design-library-list/index.js (2)
  • props (22-30)
  • shouldRender (95-95)
src/components/design-library-list/design-library-list-item.js (3)
  • props (27-34)
  • spacingSize (38-38)
  • ref (44-44)
src/components/design-library-list/use-shadow-root.js (3)
  • hostRef (11-11)
  • shadowRoot (12-12)
  • useSelect (14-14)
src/components/design-library-list/util.js (11)
  • blocks (235-235)
  • getCategorySlug (325-335)
  • getCategorySlug (325-335)
  • replacePlaceholders (183-193)
  • replacePlaceholders (183-193)
  • cleanParse (15-47)
  • cleanParse (15-47)
  • addBackgroundScheme (106-152)
  • addBackgroundScheme (106-152)
  • adjustPatternSpacing (154-181)
  • adjustPatternSpacing (154-181)
src/design-library/index.js (4)
  • designs (7-7)
  • fetchDesign (41-46)
  • fetchDesign (41-46)
  • design (102-102)
⏰ 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 latest
  • GitHub Check: PHP 8.2 and WP 6.5.5
  • GitHub Check: PHP 7.3 and WP 6.5.5
  • GitHub Check: PHP 8.2 and WP 6.6.2
  • GitHub Check: PHP 8.2 and WP 6.7.2
  • GitHub Check: PHP 7.3 and WP latest
  • GitHub Check: build
🔇 Additional comments (9)
src/components/design-library-list/use-preview-renderer.js (9)

37-53: Function signature change aligns with new architecture.

The updated function signature properly destructures props and adds shouldRender as a dedicated parameter. This matches the architectural changes across the PR for implementing an idle-gated rendering system.


76-86: LGTM on the new updateShadowBodySize function.

The function correctly extracts shadow body size calculation into a reusable utility. The parameter handling and property extraction are well-structured.


127-140: Fix the preview size initialization logic.

The condition Object.keys( prev ).length === 0 correctly identifies when the state is uninitialized (empty object), but this appears to address a previous issue mentioned in the past review comments.


145-158: Similar initialization pattern for cardHeight needs consistency check.

The cardHeight initialization uses ! Object.keys( prev ).length which is equivalent to Object.keys( prev ).length === 0. This is consistent with the previewSize logic above.


230-237: LGTM on template-specific state management.

The early return guard and state reset logic properly handles template changes. The spinner re-enabling and ref clearing ensures clean state between different templates.


315-317: Track template changes properly for optimization.

The renderedTemplate.current = template assignment correctly tracks which template has been rendered, enabling the early return optimization in the useEffect.


319-321: Separate useEffect for selectedTab tracking is correct.

This addresses the previous review comment about prevSelectedTabRef not being updated. The separate effect ensures the ref stays in sync with selectedTab changes.


350-363: Handle background changes with proper shouldRender gating.

The separate effect for background changes is well-structured and properly gated with shouldRender. The comparison logic prevents unnecessary re-renders when the background state hasn't changed.


392-394: Return object includes new properties for external coordination.

The expanded return object now includes previewSize, cardHeight, and updateShadowBodySize, which enables better coordination with parent components for sizing operations.

github-actions bot added a commit that referenced this pull request Sep 24, 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: 1

🧹 Nitpick comments (1)
src/components/design-library-list/index.js (1)

95-121: Remove unused ref to avoid noise

wrapperRef isn’t used. Drop the ref and its declaration.

-const DesignLibraryItem = props => {
-	const wrapperRef = useRef( null )
+const DesignLibraryItem = props => {
 	const [ shouldRender, setShouldRender ] = useState( false )
@@
-	return (
-		<div ref={ wrapperRef }>
+	return (
+		<div>
 			<DesignLibraryListItem { ...props } shouldRender={ shouldRender } />
 		</div>
 	)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52cde3d and 03079c4.

📒 Files selected for processing (3)
  • src/components/design-library-list/design-library-list-item.js (5 hunks)
  • src/components/design-library-list/design-preview.js (2 hunks)
  • src/components/design-library-list/index.js (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/components/design-library-list/design-preview.js (2)
src/components/design-library-list/use-auto-scroll.js (1)
  • NOOP (6-6)
src/components/design-library-list/use-preview-renderer.js (2)
  • blocks (55-55)
  • updateShadowBodySize (76-86)
src/components/design-library-list/design-library-list-item.js (3)
src/components/design-library-list/index.js (2)
  • props (22-30)
  • shouldRender (96-96)
src/components/design-library-list/use-preview-renderer.js (5)
  • props (41-53)
  • usePreviewRenderer (37-395)
  • usePreviewRenderer (37-395)
  • cardHeight (59-59)
  • updateShadowBodySize (76-86)
src/components/design-library-list/use-shadow-root.js (4)
  • useShadowRoot (10-77)
  • useShadowRoot (10-77)
  • hostRef (11-11)
  • shadowRoot (12-12)
src/components/design-library-list/index.js (3)
src/components/modal-design-library/modal.js (1)
  • selectedTab (62-62)
src/components/design-library-list/design-library-list-item.js (2)
  • DesignLibraryListItem (26-167)
  • props (27-34)
src/components/design-library-list/use-preview-renderer.js (1)
  • props (41-53)
⏰ 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). (6)
  • GitHub Check: PHP 8.2 and WP 6.7.2
  • GitHub Check: PHP 8.2 and WP 6.6.2
  • GitHub Check: PHP 7.3 and WP latest
  • GitHub Check: PHP 8.2 and WP latest
  • GitHub Check: PHP 8.2 and WP 6.5.5
  • GitHub Check: PHP 7.3 and WP 6.5.5
🔇 Additional comments (4)
src/components/design-library-list/design-preview.js (1)

83-105: Unify update timing, add Safari-safe idle fallback, avoid redundant writes, and clean up scheduled tasks

  • Always call updateShadowBodySize whenever content changes (also in the immediate path).
  • Use a setTimeout fallback for requestIdleCallback instead of requestAnimationFrame to avoid nested rAF.
  • Skip DOM writes when the HTML hasn’t changed.
  • Add cleanup to cancel scheduled callbacks.
  • Include selectedTab, designIndex, and updateShadowBodySize in deps to prevent stale closures.
 	useEffect( () => {
 		const wrapper = wrapperRef.current
 
 		if ( ! wrapper ) {
 			return
 		}
 
-		const ric = window.requestIdleCallback || window.requestAnimationFrame
-		const sanitizedHTML = safeHTML( blocks )
+		const ric = typeof window.requestIdleCallback !== 'undefined'
+			? window.requestIdleCallback
+			: ( cb => setTimeout( cb, 0 ) )
+		const newHTML = safeHTML( blocks )
+		let idleId
+		let rafId
+
+		const apply = () => {
+			if ( wrapper.innerHTML !== newHTML ) {
+				wrapper.innerHTML = newHTML
+				updateShadowBodySize()
+			}
+		}
 
-		if ( selectedTab !== 'pages' || designIndex < 9 ) {
-			// insert HTML for patterns and for the first 9 pages
-			wrapper.innerHTML = sanitizedHTML
-			return
-		}
+		if ( selectedTab !== 'pages' || designIndex < 9 ) {
+			// Patterns and first 9 pages: update immediately if changed
+			apply()
+			return
+		}
 
-		requestAnimationFrame( () => {
-			ric( () => {
-				wrapper.innerHTML = sanitizedHTML
-				updateShadowBodySize()
-			} )
-		} )
-	}, [ blocks ] )
+		rafId = requestAnimationFrame( () => {
+			idleId = ric( apply )
+		} )
+
+		return () => {
+			if ( rafId ) cancelAnimationFrame( rafId )
+			if ( typeof cancelIdleCallback !== 'undefined' && idleId ) {
+				cancelIdleCallback( idleId )
+			} else if ( idleId ) {
+				clearTimeout( idleId )
+			}
+		}
+	}, [ blocks, selectedTab, designIndex, updateShadowBodySize ] )
src/components/design-library-list/index.js (1)

95-114: Idle-gated rendering is a solid improvement

The lazy mount via requestIdleCallback with a safe timeout fallback is appropriate and will reduce initial load. Cleanup logic looks correct.

src/components/design-library-list/design-library-list-item.js (2)

42-53: ShadowRoot + preview renderer integration looks good

Passing shouldRender to useShadowRoot and wiring updateShadowBodySize through to DesignPreview aligns with the new rendering model.


72-74: Placeholder short-circuit avoids unnecessary work

Early return when not shouldRender and unselected is a clean way to prevent heavy initialization.

github-actions bot added a commit that referenced this pull request Sep 24, 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: 1

🧹 Nitpick comments (3)
src/components/design-library-list/editor.scss (1)

191-198: Opacity transition looks good; add reduced-motion guard.

To respect users’ motion preferences, disable the opacity transition when prefers-reduced-motion is set.

 .stk-block-design__host-container {
 	position: relative;
 	transition: opacity 0.3s cubic-bezier(0.2, 0.6, 0.4, 1);
   	will-change: opacity;
+	@media (prefers-reduced-motion: reduce) {
+		transition: none;
+	}
 	&.stk-is-loading {
 		opacity: 0;
 	}
 	&.stk-show {
 		opacity: 1;
 	}
 }
src/components/design-library-list/design-preview.js (1)

16-20: Default setIsLoading to NOOP to avoid undefined calls.

Prevents runtime errors if the prop isn’t passed.

 	onMouseDown = NOOP,
 	updateShadowBodySize = NOOP,
-	setIsLoading,
+	setIsLoading = NOOP,
src/components/design-library-list/design-library-list-item.js (1)

108-114: Expose hidden state to assistive tech.
While opacity toggles visibility, focusable descendants could still be reachable. Mark the host container aria-hidden when loading/not ready.

-				<div
-					className={ `stk-block-design__host-container ${ ! shadowRoot || isLoading ? 'stk-is-loading' : 'stk-show' }` }
+				<div
+					className={ `stk-block-design__host-container ${ ! shadowRoot || isLoading ? 'stk-is-loading' : 'stk-show' }` }
+					aria-hidden={ !shadowRoot || isLoading }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03079c4 and 4d0b3de.

📒 Files selected for processing (3)
  • src/components/design-library-list/design-library-list-item.js (4 hunks)
  • src/components/design-library-list/design-preview.js (2 hunks)
  • src/components/design-library-list/editor.scss (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/components/design-library-list/design-preview.js (2)
src/components/design-library-list/use-auto-scroll.js (1)
  • NOOP (6-6)
src/components/design-library-list/use-preview-renderer.js (2)
  • blocks (55-55)
  • updateShadowBodySize (76-86)
src/components/design-library-list/design-library-list-item.js (4)
src/components/design-library-list/index.js (2)
  • props (22-30)
  • shouldRender (96-96)
src/components/design-library-list/use-preview-renderer.js (8)
  • props (41-53)
  • usePreviewRenderer (37-395)
  • usePreviewRenderer (37-395)
  • shadowBodySizeRef (65-65)
  • previewSize (60-60)
  • cardHeight (59-59)
  • blocks (55-55)
  • updateShadowBodySize (76-86)
src/components/design-library-list/use-shadow-root.js (4)
  • useShadowRoot (10-77)
  • useShadowRoot (10-77)
  • hostRef (11-11)
  • shadowRoot (12-12)
src/components/design-library-list/design-preview.js (3)
  • ref (21-21)
  • DesignPreview (12-124)
  • DesignPreview (12-124)
⏰ 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). (6)
  • GitHub Check: PHP 7.3 and WP latest
  • GitHub Check: PHP 8.2 and WP 6.6.2
  • GitHub Check: PHP 8.2 and WP latest
  • 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.7.2
🔇 Additional comments (5)
src/components/design-library-list/editor.scss (1)

244-249: Confirm parent height; 100% may collapse without an explicit container height.

If the parent of .stk-spinner-container doesn’t have a definite height, the spinner may not center. Verify this in all breakpoints/tabs.

src/components/design-library-list/design-preview.js (1)

119-121: Pointer-events guard on wrapper is a nice safety.
Prevents accidental interactions with previewed blocks.

src/components/design-library-list/design-library-list-item.js (3)

42-45: Good: gating shadow root creation with shouldRender.
Reduces work for offscreen/untoggled items.


49-53: Plumbing updateShadowBodySize and setIsLoading through hooks/components is solid.
Keeps sizing and loading state controlled from the source.

Please confirm that updateShadowBodySize is invoked for all page previews (including early ones) so auto‑scroll has correct dimensions.


63-66: Use computed enableBackground and nullish coalescing to respect 0 heights.
This avoids mixing props.enableBackground with the hook value and prevents 0 from falling through.

-	const key = props.enableBackground ? 'background' : 'noBackground'
-	return cardHeight?.[ key ] || ( props.selectedTab === 'pages' ? 413 : 250 )
+	const key = enableBackground ? 'background' : 'noBackground'
+	return cardHeight?.[ key ] ?? ( selectedTab === 'pages' ? 413 : 250 )

github-actions bot added a commit that referenced this pull request Sep 24, 2025
github-actions bot added a commit that referenced this pull request Sep 24, 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: 0

🧹 Nitpick comments (2)
src/components/design-library-list/index.js (1)

38-40: Guard against null ref before scrolling to top

Prevents a potential runtime error if the ref isn’t set yet.

 useEffect( () => {
-  containerRef.current.scrollTop = 0
+  if ( containerRef.current ) {
+    containerRef.current.scrollTop = 0
+  }
 }, [ designs ] )
src/components/design-library-list/design-library-list-item.js (1)

110-113: Default scale to 1 to avoid invalid CSS

previewSize?.scale can be undefined briefly; fall back to 1.

-            transform: `scale(${ selectedNum && selectedData ? selectedData.selectedPreviewSize.scale : previewSize?.scale })`,
+            transform: `scale(${ selectedNum && selectedData ? selectedData.selectedPreviewSize.scale : ( previewSize?.scale ?? 1 ) })`,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f214178 and 4942604.

📒 Files selected for processing (3)
  • src/components/design-library-list/design-library-list-item.js (5 hunks)
  • src/components/design-library-list/design-preview.js (2 hunks)
  • src/components/design-library-list/index.js (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/components/design-library-list/design-preview.js (2)
src/components/design-library-list/use-auto-scroll.js (1)
  • NOOP (6-6)
src/components/design-library-list/use-preview-renderer.js (2)
  • blocks (55-55)
  • updateShadowBodySize (76-86)
src/components/design-library-list/design-library-list-item.js (5)
src/components/design-library-list/index.js (2)
  • props (22-30)
  • shouldRender (96-96)
src/components/design-library-list/use-preview-renderer.js (8)
  • props (41-53)
  • usePreviewRenderer (37-395)
  • usePreviewRenderer (37-395)
  • shadowBodySizeRef (65-65)
  • previewSize (60-60)
  • cardHeight (59-59)
  • blocks (55-55)
  • updateShadowBodySize (76-86)
src/components/design-library-list/use-shadow-root.js (4)
  • useShadowRoot (10-77)
  • useShadowRoot (10-77)
  • hostRef (11-11)
  • shadowRoot (12-12)
src/components/design-library-list/design-preview.js (3)
  • ref (21-21)
  • DesignPreview (12-125)
  • DesignPreview (12-125)
src/components/design-library-list/use-auto-scroll.js (3)
  • useAutoScroll (8-147)
  • useAutoScroll (8-147)
  • onMouseDown (140-140)
src/components/design-library-list/index.js (3)
src/components/modal-design-library/modal.js (1)
  • selectedTab (62-62)
src/components/design-library-list/design-library-list-item.js (2)
  • props (27-34)
  • DesignLibraryListItem (26-164)
src/components/design-library-list/use-preview-renderer.js (1)
  • props (41-53)
⏰ 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 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.6.2
  • GitHub Check: PHP 8.2 and WP 6.5.5
  • GitHub Check: build
🔇 Additional comments (5)
src/components/design-library-list/index.js (2)

63-73: Prop wiring looks good

Passing stable key, selectedTab, designIndex, and preview props into each item is correct and aligns with downstream hooks.


96-114: Idle-gated shouldRender init is sound

Staggered requestIdleCallback/setTimeout with cleanup is appropriate.

src/components/design-library-list/design-library-list-item.js (2)

63-66: Use computed enableBackground and local selectedTab

Avoid mixing props and computed values; prevents desync and respects 0 heights with ??.

 const getCardHeight = () => {
-  const key = props.enableBackground ? 'background' : 'noBackground'
-  return cardHeight?.[ key ] ?? ( props.selectedTab === 'pages' ? 413 : 250 )
+  const key = enableBackground ? 'background' : 'noBackground'
+  return cardHeight?.[ key ] ?? ( selectedTab === 'pages' ? 413 : 250 )
 }

116-124: Preview props pass-through looks correct

shadowRoot guard and passing updateShadowBodySize/setIsLoading are appropriate.

src/components/design-library-list/design-preview.js (1)

84-112: Ensure loading clears for early path, avoid redundant writes, and fix effect deps

Early branch never clears loading; spinners can stay visible for patterns and first 9 pages. Also, avoid needless DOM writes and include selectedTab/designIndex in deps so logic reruns on tab/index changes.

 useEffect( () => {
   const wrapper = wrapperRef.current

   if ( ! wrapper || ! blocks ) {
     return
   }

   const ric = window.requestIdleCallback || ( cb => setTimeout( cb, designIndex * 20 ) )
   const sanitizedHTML = safeHTML( blocks )

   if ( selectedTab !== 'pages' || designIndex < 9 ) {
     // insert HTML for patterns and for the first 9 pages
-    wrapper.innerHTML = sanitizedHTML
-    // No need to call setIsLoading(false) here; isLoading is already false at this point.
+    if ( wrapper.innerHTML !== sanitizedHTML ) {
+      wrapper.innerHTML = sanitizedHTML
+    }
+    // Keep sizes in sync for early pages.
+    if ( selectedTab === 'pages' ) {
+      updateShadowBodySize()
+    }
+    // Reveal after write.
+    ric( () => setIsLoading( false ) )
     return
   }

   setIsLoading( true )

   requestAnimationFrame( () => {
     ric( () => {
-      wrapper.innerHTML = sanitizedHTML
+      if ( wrapper.innerHTML !== sanitizedHTML ) {
+        wrapper.innerHTML = sanitizedHTML
+      }
       updateShadowBodySize()
       requestAnimationFrame( () => {
         ric( () => setIsLoading( false ) )
       } )
     } )
   } )
-}, [ blocks ] ) // Only depend on blocks; selectedTab and designIndex changes will cause blocks to update
+}, [ blocks, selectedTab, designIndex ] )

github-actions bot added a commit that referenced this pull request Sep 25, 2025
- apply useCallbacks, useMemo, and memo
- fix preview when loading
github-actions bot added a commit that referenced this pull request Sep 25, 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

🧹 Nitpick comments (4)
src/components/modal-design-library/index.js (1)

28-35: Stabilize callback deps to satisfy exhaustive-deps and avoid stale closures

Include the setter in the dependency array. It’s stable, but this silences lint and documents intent.

-const onChangeApiVersion = useCallback( v => setApiVersion( v ), [] )
+const onChangeApiVersion = useCallback( v => setApiVersion( v ), [ setApiVersion ] )
src/components/design-library-list/index.js (2)

95-121: Cap Safari idle fallback delay to avoid multi‑second stalls

setTimeout(…, designIndex * 20) can defer rendering by several seconds for large lists on browsers without requestIdleCallback (e.g., Safari). Use a small constant delay.

-    if ( typeof requestIdleCallback !== 'undefined' ) {
-        id = requestIdleCallback( () => ! shouldRender ? setShouldRender( true ) : {} )
-    } else {
-        // fallback
-        id = setTimeout( () => setShouldRender( true ), designIndex * 20 )
-    }
+    if ( typeof requestIdleCallback !== 'undefined' ) {
+        id = requestIdleCallback( () => { if ( ! shouldRender ) setShouldRender( true ) } )
+    } else {
+        id = setTimeout( () => { if ( ! shouldRender ) setShouldRender( true ) }, 16 )
+    }

132-147: Remove unsupported IntersectionObserver option

scrollMargin is not a valid observer option; it has no effect. Keep rootMargin.

-        }, {
-            root: rootEl,
-            rootMargin: '500px',
-            scrollMargin: '500px',
-            threshold: 0,
-        } )
+        }, {
+            root: rootEl,
+            rootMargin: '500px',
+            threshold: 0,
+        } )
src/components/design-library-list/design-library-list-item.js (1)

106-124: Spinner visibility depends on setIsLoading from DesignPreview early path

This relies on the preview setting isLoading to false; ensure the DesignPreview early branch clears it (see linked comment).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4c853a and e8945f5.

📒 Files selected for processing (7)
  • src/block/design-library/edit.js (3 hunks)
  • src/components/design-library-list/design-library-list-item.js (2 hunks)
  • src/components/design-library-list/design-preview.js (2 hunks)
  • src/components/design-library-list/index.js (5 hunks)
  • src/components/modal-design-library/editor.scss (1 hunks)
  • src/components/modal-design-library/index.js (2 hunks)
  • src/components/modal-design-library/modal.js (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
src/components/modal-design-library/modal.js (1)
src/components/design-library-list/util.js (1)
  • blocksForSubstitution (213-213)
src/components/design-library-list/index.js (2)
src/components/design-library-list/design-library-list-item.js (2)
  • props (28-36)
  • DesignLibraryListItem (27-164)
src/hooks/use-preset-controls.js (1)
  • getPresetMarks (114-126)
src/components/design-library-list/design-library-list-item.js (5)
src/components/design-library-list/index.js (4)
  • props (23-31)
  • props (96-96)
  • presetMarks (102-102)
  • shouldRender (98-98)
src/components/design-library-list/use-preview-renderer.js (8)
  • props (41-53)
  • usePreviewRenderer (37-395)
  • usePreviewRenderer (37-395)
  • shadowBodySizeRef (65-65)
  • previewSize (60-60)
  • cardHeight (59-59)
  • blocks (55-55)
  • updateShadowBodySize (76-86)
src/components/design-library-list/use-shadow-root.js (4)
  • useShadowRoot (10-77)
  • useShadowRoot (10-77)
  • hostRef (11-11)
  • shadowRoot (12-12)
src/components/design-library-list/design-preview.js (3)
  • ref (21-21)
  • DesignPreview (12-125)
  • DesignPreview (12-125)
src/components/design-library-list/use-auto-scroll.js (3)
  • useAutoScroll (10-149)
  • useAutoScroll (10-149)
  • onMouseDown (142-142)
src/block/design-library/edit.js (2)
src/components/design-library-list/util.js (2)
  • disabledBlocks (212-212)
  • blocksForSubstitution (213-213)
src/util/blocks.js (1)
  • disabledBlocks (555-555)
src/components/modal-design-library/index.js (2)
src/deprecated/v2/components/design-library-control/index.js (1)
  • apiVersion (30-30)
src/deprecated/v2/components/modal-design-library/index.js (1)
  • apiVersion (45-45)
src/components/design-library-list/design-preview.js (4)
src/components/design-library-list/use-auto-scroll.js (1)
  • NOOP (8-8)
src/components/design-library-list/index.js (1)
  • wrapperRef (97-97)
src/components/design-library-list/use-preview-renderer.js (2)
  • blocks (55-55)
  • updateShadowBodySize (76-86)
src/components/design-library-list/use-shadow-root.js (1)
  • shadowRoot (12-12)
⏰ 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). (6)
  • GitHub Check: PHP 8.2 and WP 6.7.2
  • GitHub Check: PHP 8.2 and WP latest
  • GitHub Check: PHP 8.2 and WP 6.6.2
  • GitHub Check: PHP 7.3 and WP 6.5.5
  • GitHub Check: PHP 7.3 and WP latest
  • GitHub Check: PHP 8.2 and WP 6.5.5
🔇 Additional comments (8)
src/components/modal-design-library/editor.scss (1)

46-49: Explicit row span looks good

Locking the full-pages designs pane to 1 / 4 keeps it consuming every track in the three-row grid after dropping the custom template override. This should help the content stay flush with the footer. Nicely done.

src/block/design-library/edit.js (2)

331-332: LGTM on memoized onClose

Stable and side‑effect free.


402-404: LGTM on passing memoized handlers

Passing memoized onClose/onSelect helps avoid unnecessary re-renders downstream.

src/components/modal-design-library/modal.js (1)

432-433: LGTM on consolidating selection handler

Passing a single, memoized onSelectMulti streamlines list item behavior and prevents prop churn.

src/components/design-library-list/index.js (2)

66-74: LGTM on stable keys and passing new props

Good use of design.id || design.designId and forwarding selectedTab/designIndex.


151-152: LGTM on deferring render via shouldRender + preset marks

Clean placeholder approach; avoids heavy work offscreen.

src/components/design-library-list/design-library-list-item.js (1)

63-66: Use computed enableBackground and nullish coalescing for height

Aligns with preview renderer state and respects 0 heights.

-const getCardHeight = () => {
-    const key = props.enableBackground ? 'background' : 'noBackground'
-    return cardHeight?.[ key ] ?? ( props.selectedTab === 'pages' ? 413 : 250 )
-}
+const getCardHeight = () => {
+    const key = enableBackground ? 'background' : 'noBackground'
+    return cardHeight?.[ key ] ?? ( selectedTab === 'pages' ? 413 : 250 )
+}
src/components/design-library-list/design-preview.js (1)

84-113: Early branch leaves spinner visible; Safari fallback delays are excessive; missing size update for early pages

  • isLoading starts true in the item and isn’t cleared on the early path.
  • setTimeout(cb, designIndex * 20) can delay long lists by seconds on Safari (no rIC).
  • Not updating sizes for the first 9 pages can break auto-scroll.

Apply:

-    const ric = window.requestIdleCallback || ( cb => setTimeout( cb, designIndex * 20 ) )
+    const ric = window.requestIdleCallback || ( cb => setTimeout( cb, 16 ) )
     const sanitizedHTML = safeHTML( blocks )

-    if ( selectedTab !== 'pages' || designIndex < 9 ) {
-        // insert HTML for patterns and for the first 9 pages
-        wrapper.innerHTML = sanitizedHTML
-        // No need to call setIsLoading(false) here; isLoading is already false at this point.
-        return
-    }
+    if ( selectedTab !== 'pages' || designIndex < 9 ) {
+        // Insert HTML for patterns and early pages
+        if ( wrapper.innerHTML !== sanitizedHTML ) {
+            wrapper.innerHTML = sanitizedHTML
+        }
+        if ( selectedTab === 'pages' ) {
+            updateShadowBodySize()
+        }
+        ric( () => setIsLoading( false ) )
+        return
+    }

@@
-    }, [ blocks, shadowRoot ] ) // Only depend on blocks and shadowRoot; selectedTab and designIndex changes will cause blocks to update
+    }, [ blocks, shadowRoot, selectedTab, designIndex, updateShadowBodySize ] )

github-actions bot added a commit that referenced this pull request Sep 25, 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

🧹 Nitpick comments (3)
src/components/design-library-list/design-preview.js (1)

16-20: Default setIsLoading to NOOP to prevent hard crashes if omitted.

Prevents runtime errors if a caller forgets to pass setIsLoading.

  designIndex,
  onMouseDown = NOOP,
  updateShadowBodySize = NOOP,
- setIsLoading,
+ setIsLoading = NOOP,
src/components/design-library-list/index.js (1)

141-145: Remove unsupported IntersectionObserver option.

scrollMargin is not a valid IntersectionObserver option; it’s a CSS property and is ignored here.

 		}, {
 			root: rootEl,
 			rootMargin: '500px',
-			scrollMargin: '500px',
 			threshold: 0,
 		} )
src/components/design-library-list/design-library-list-item.js (1)

108-112: Default scale to 1 to avoid invalid CSS during first render.

Prevents transform: scale(undefined) before previewSize is computed.

-						transform: `scale(${ selectedNum && selectedData ? selectedData.selectedPreviewSize.scale : previewSize?.scale })`,
+						transform: `scale(${ selectedNum && selectedData ? selectedData.selectedPreviewSize.scale : ( previewSize?.scale ?? 1 ) })`,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8945f5 and 0e11f07.

📒 Files selected for processing (5)
  • src/components/design-library-list/design-library-list-item.js (3 hunks)
  • src/components/design-library-list/design-preview.js (2 hunks)
  • src/components/design-library-list/editor.scss (1 hunks)
  • src/components/design-library-list/index.js (5 hunks)
  • src/components/design-library-list/use-preview-renderer.js (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/design-library-list/editor.scss
🧰 Additional context used
🧬 Code graph analysis (4)
src/components/design-library-list/design-preview.js (3)
src/components/design-library-list/use-auto-scroll.js (1)
  • NOOP (8-8)
src/components/design-library-list/use-preview-renderer.js (2)
  • blocks (55-55)
  • updateShadowBodySize (75-85)
src/components/design-library-list/use-shadow-root.js (1)
  • shadowRoot (12-12)
src/components/design-library-list/use-preview-renderer.js (4)
src/components/design-library-list/design-library-list-item.js (3)
  • props (28-36)
  • spacingSize (38-38)
  • ref (44-44)
src/components/design-library-list/index.js (3)
  • props (23-31)
  • props (96-96)
  • shouldRender (98-98)
src/components/design-library-list/util.js (11)
  • blocks (235-235)
  • getCategorySlug (325-335)
  • getCategorySlug (325-335)
  • replacePlaceholders (183-193)
  • replacePlaceholders (183-193)
  • cleanParse (15-47)
  • cleanParse (15-47)
  • addBackgroundScheme (106-152)
  • addBackgroundScheme (106-152)
  • adjustPatternSpacing (154-181)
  • adjustPatternSpacing (154-181)
src/design-library/index.js (4)
  • designs (7-7)
  • fetchDesign (41-46)
  • fetchDesign (41-46)
  • design (102-102)
src/components/design-library-list/design-library-list-item.js (5)
src/components/design-library-list/index.js (4)
  • props (23-31)
  • props (96-96)
  • presetMarks (102-102)
  • shouldRender (98-98)
src/components/design-library-list/use-preview-renderer.js (7)
  • props (41-53)
  • usePreviewRenderer (37-379)
  • usePreviewRenderer (37-379)
  • shadowBodySizeRef (64-64)
  • previewSize (59-59)
  • blocks (55-55)
  • updateShadowBodySize (75-85)
src/components/design-library-list/use-shadow-root.js (4)
  • useShadowRoot (10-77)
  • useShadowRoot (10-77)
  • hostRef (11-11)
  • shadowRoot (12-12)
src/components/design-library-list/design-preview.js (3)
  • ref (21-21)
  • DesignPreview (12-127)
  • DesignPreview (12-127)
src/components/design-library-list/use-auto-scroll.js (3)
  • useAutoScroll (10-149)
  • useAutoScroll (10-149)
  • onMouseDown (142-142)
src/components/design-library-list/index.js (3)
src/components/design-library-list/design-library-list-item.js (2)
  • props (28-36)
  • DesignLibraryListItem (27-163)
src/components/modal-design-library/modal.js (1)
  • selectedTab (64-64)
src/hooks/use-preset-controls.js (1)
  • getPresetMarks (114-126)
⏰ 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). (6)
  • GitHub Check: PHP 7.3 and WP 6.5.5
  • GitHub Check: PHP 8.2 and WP latest
  • GitHub Check: PHP 7.3 and WP latest
  • GitHub Check: PHP 8.2 and WP 6.7.2
  • GitHub Check: PHP 8.2 and WP 6.6.2
  • GitHub Check: PHP 8.2 and WP 6.5.5
🔇 Additional comments (3)
src/components/design-library-list/use-preview-renderer.js (2)

126-139: Preview size init fix looks good.

The switch to checking for an empty prev object ensures both heights are initialized on first run.


303-305: Good: prevSelectedTabRef kept in sync.

This addresses the stale-ref issue for tab-change logic.

src/components/design-library-list/design-preview.js (1)

84-114: Ensure effect reacts to tab/index changes and avoid redundant writes; update size for early pages.

The effect doesn’t depend on selectedTab/designIndex, so branch changes won’t run when blocks are unchanged. Also, skip redundant innerHTML writes and update size for first 9 pages to keep auto‑scroll sizing accurate.

Apply:

-	useEffect( () => {
+	useEffect( () => {
 		const wrapper = wrapperRef.current
 
 		if ( ! wrapper || ! blocks ) {
 			return
 		}
 
 		setIsLoading( true )
 
 		const ric = window.requestIdleCallback || ( cb => setTimeout( cb, designIndex * 20 ) )
 		const sanitizedHTML = safeHTML( blocks )
 
 		if ( selectedTab !== 'pages' || designIndex < 9 ) {
 			// insert HTML for patterns and for the first 9 pages
-			wrapper.innerHTML = sanitizedHTML
+			if ( wrapper.innerHTML !== sanitizedHTML ) {
+				wrapper.innerHTML = sanitizedHTML
+			}
+			if ( selectedTab === 'pages' ) {
+				updateShadowBodySize()
+			}
 			requestAnimationFrame( () => {
 				ric( () => setIsLoading( false ) )
 			} )
 			return
 		}
 
 		requestAnimationFrame( () => {
 			ric( () => {
-				wrapper.innerHTML = sanitizedHTML
+				if ( wrapper.innerHTML !== sanitizedHTML ) {
+					wrapper.innerHTML = sanitizedHTML
+				}
 				updateShadowBodySize()
 				requestAnimationFrame( () => {
 					ric( () => setIsLoading( false ) )
 				} )
 			} )
 		} )
-	}, [ blocks, shadowRoot ] ) // Only depend on blocks and shadowRoot; selectedTab and designIndex changes will cause blocks to update
+	}, [ blocks, shadowRoot, selectedTab, designIndex ] )

github-actions bot added a commit that referenced this pull request Sep 25, 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 (4)
src/components/modal-design-library/modal.js (1)

432-432: Ensure onSelectMulti is referentially stable to preserve list item memoization

Passing a re-created handler on every selection can force a re-render of the memoized list. After applying the functional update refactor and memoizing addDesign, onSelectDesign’s identity will be stable across selections.

Please profile DesignLibraryList re-renders (React DevTools Profiler) while selecting/deselecting items and switching tabs. Expect only affected items to re-render, not the entire list.

src/components/design-library-list/design-library-list-item.js (2)

105-105: Hide spinner when not rendering to reduce visual noise

Show spinner only while actively rendering and loading.

-<div className={ `stk-spinner-container ${ isLoading || ! shouldRender ? '' : 'stk-hide-spinner' }` }><Spinner /></div>
+<div className={ `stk-spinner-container ${ isLoading && shouldRender ? '' : 'stk-hide-spinner' }` }><Spinner /></div>

109-111: Default scale to 1 to avoid invalid CSS

previewSize?.scale can be undefined initially, producing an invalid transform.

- transform: `scale(${ selectedNum && selectedData ? selectedData.selectedPreviewSize.scale : previewSize?.scale })`,
+ transform: `scale(${ selectedNum && selectedData ? selectedData.selectedPreviewSize.scale : ( previewSize?.scale ?? 1 ) })`,
src/components/design-library-list/use-preview-renderer.js (1)

212-301: Add cancellation guard for async initialize to prevent stale updates

Avoid setState after unmount/template change and stop spinner reliably on errors.

 useEffect( () => {
   if ( ! shouldRender || renderedTemplate.current === template ) {
     return
   }
 
+  let isCancelled = false
+
   // Reset per-template state and show spinner
   setIsLoading( true )
   categoriesRef.current = []
   hasBackgroundTargetRef.current = false
@@
-  initialize().then( () => {
+  initialize().then( () => {
+    if ( isCancelled ) return
     // We need to parse the content because this is what we use to insert the blocks in the Editor
     const [ parsedBlocks, blocksForSubstitution ] = parseDisabledBlocks( _parsedBlocks )
     const parsedBlocksForInsertion = _parsedBlocksForInsertion ? parseDisabledBlocks( _parsedBlocksForInsertion )[ 0 ] : null
     blocksForSubstitutionRef.current = blocksForSubstitution
 
     setContent( parsedBlocks )
     setContentForInsertion( parsedBlocksForInsertion )
     setIsLoading( false )
     renderedTemplate.current = template
-  } )
+  } ).catch( () => {
+    if ( ! isCancelled ) {
+      setIsLoading( false )
+    }
+  } )
-}, [ template, shouldRender ] )
+  return () => {
+    isCancelled = true
+  }
+}, [ template, shouldRender ] )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e11f07 and be47133.

📒 Files selected for processing (3)
  • src/components/design-library-list/design-library-list-item.js (3 hunks)
  • src/components/design-library-list/use-preview-renderer.js (8 hunks)
  • src/components/modal-design-library/modal.js (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/components/modal-design-library/modal.js (1)
src/components/design-library-list/util.js (1)
  • blocksForSubstitution (213-213)
src/components/design-library-list/use-preview-renderer.js (5)
src/components/design-library-list/design-library-list-item.js (3)
  • props (28-36)
  • spacingSize (38-38)
  • ref (44-44)
src/components/design-library-list/index.js (3)
  • props (23-31)
  • props (96-96)
  • shouldRender (98-98)
src/components/design-library-list/use-shadow-root.js (3)
  • hostRef (11-11)
  • shadowRoot (12-12)
  • useSelect (14-14)
src/components/design-library-list/util.js (11)
  • blocks (235-235)
  • getCategorySlug (325-335)
  • getCategorySlug (325-335)
  • replacePlaceholders (183-193)
  • replacePlaceholders (183-193)
  • cleanParse (15-47)
  • cleanParse (15-47)
  • addBackgroundScheme (106-152)
  • addBackgroundScheme (106-152)
  • adjustPatternSpacing (154-181)
  • adjustPatternSpacing (154-181)
src/design-library/index.js (4)
  • designs (7-7)
  • fetchDesign (41-46)
  • fetchDesign (41-46)
  • design (102-102)
src/components/design-library-list/design-library-list-item.js (5)
src/components/design-library-list/use-preview-renderer.js (7)
  • props (41-53)
  • usePreviewRenderer (37-379)
  • usePreviewRenderer (37-379)
  • shadowBodySizeRef (64-64)
  • previewSize (59-59)
  • blocks (55-55)
  • updateShadowBodySize (75-85)
src/components/design-library-list/index.js (4)
  • props (23-31)
  • props (96-96)
  • presetMarks (102-102)
  • shouldRender (98-98)
src/components/design-library-list/use-shadow-root.js (4)
  • useShadowRoot (10-77)
  • useShadowRoot (10-77)
  • hostRef (11-11)
  • shadowRoot (12-12)
src/components/design-library-list/design-preview.js (3)
  • ref (21-21)
  • DesignPreview (12-127)
  • DesignPreview (12-127)
src/components/design-library-list/use-auto-scroll.js (3)
  • useAutoScroll (10-149)
  • useAutoScroll (10-149)
  • onMouseDown (142-142)
⏰ 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). (6)
  • GitHub Check: PHP 8.2 and WP latest
  • GitHub Check: PHP 8.2 and WP 6.5.5
  • GitHub Check: PHP 8.2 and WP 6.6.2
  • GitHub Check: PHP 7.3 and WP 6.5.5
  • GitHub Check: PHP 8.2 and WP 6.7.2
  • GitHub Check: PHP 7.3 and WP latest
🔇 Additional comments (2)
src/components/modal-design-library/modal.js (2)

32-34: LGTM on importing useCallback

Import looks correct and aligns with new memoized handler usage.


134-166: Stabilize onSelectDesign and use functional state updates to prevent stale closures and excess re-renders

Current deps include arrays, recreating the callback on every selection and defeating list item memoization; also non‑functional setState risks lost updates under rapid clicks. Use functional updates and depend only on selectedTab and a memoized addDesign.

Apply within this hunk:

-const onSelectDesign = useCallback( ( designId, category, parsedBlocks, blocksForSubstitution, selectedPreviewSize ) => {
-  if ( selectedTab === 'pages' ) {
-    const selectedDesign = [ {
-      designId, category, designData: parsedBlocks, blocksForSubstitution, selectedPreviewSize,
-    } ]
-    addDesign( selectedDesign )
-
-    return
-  }
-
-  const newSelectedDesigns = [ ...selectedDesignIds ]
-  // We also get the design data from displayDesigns
-  // already instead of after clicking the "Add
-  // Designs" button since displayDesigns can change
-  // when the user is switching tabs (block/ui
-  // kits/wireframes) and the data can be lost.
-  const newSelectedDesignData = [ ...selectedDesignData ]
-
-  if ( newSelectedDesigns.includes( designId ) ) {
-    const i = newSelectedDesigns.indexOf( designId )
-    newSelectedDesigns.splice( i, 1 )
-    setSelectedDesignIds( newSelectedDesigns )
-    newSelectedDesignData.splice( i, 1 )
-    setSelectedDesignData( newSelectedDesignData )
-  } else {
-    newSelectedDesigns.push( designId )
-    setSelectedDesignIds( newSelectedDesigns )
-    newSelectedDesignData.push( {
-      designId, category, designData: parsedBlocks, blocksForSubstitution, selectedPreviewSize,
-    } )
-    setSelectedDesignData( newSelectedDesignData )
-  }
-}, [ selectedTab, selectedDesignIds, selectedDesignData ] )
+const onSelectDesign = useCallback( ( designId, category, parsedBlocks, blocksForSubstitution, selectedPreviewSize ) => {
+  if ( selectedTab === 'pages' ) {
+    addDesign( [ { designId, category, designData: parsedBlocks, blocksForSubstitution, selectedPreviewSize } ] )
+    return
+  }
+  setSelectedDesignIds( prevIds => {
+    const idx = prevIds.indexOf( designId )
+    if ( idx !== -1 ) {
+      setSelectedDesignData( prevData => {
+        const nextData = prevData.slice()
+        nextData.splice( idx, 1 )
+        return nextData
+      } )
+      const nextIds = prevIds.slice()
+      nextIds.splice( idx, 1 )
+      return nextIds
+    }
+    setSelectedDesignData( prevData => ( [
+      ...prevData,
+      { designId, category, designData: parsedBlocks, blocksForSubstitution, selectedPreviewSize },
+    ] ) )
+    return [ ...prevIds, designId ]
+  } )
+}, [ selectedTab, addDesign ] )

Additionally (outside this hunk), memoize addDesign so onSelectDesign can safely depend on it:

// Replace current addDesign with:
const addDesign = useCallback( designs => {
  setIsMultiSelectBusy( true )
  const cb = () => setIsMultiSelectBusy( false )
  props.onSelect( designs, cb, selectedTab )
}, [ props.onSelect, selectedTab ] )

github-actions bot added a commit that referenced this pull request Sep 25, 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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef7881b and 32c2eef.

📒 Files selected for processing (1)
  • src/components/design-library-list/editor.scss (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: kaeizen
PR: gambitph/Stackable#3601
File: src/components/design-library-list/use-preview-renderer.js:307-331
Timestamp: 2025-09-25T08:17:15.543Z
Learning: In the design library preview renderer hook (src/components/design-library-list/use-preview-renderer.js), selectedTab changes trigger content updates through a chain of effects rather than direct dependencies. The first useEffect (template processing) responds to selectedTab changes indirectly and updates content state, which then triggers the second useEffect (rendering/scaling) that depends on content. This eliminates the need to add selectedTab to the second effect's dependency array.

github-actions bot added a commit that referenced this pull request Sep 25, 2025
@bfintal bfintal merged commit 1f652ed into develop Sep 29, 2025
8 of 9 checks passed
@bfintal bfintal deleted the fix/design-library-full-pages-scrolling branch September 29, 2025 06:11
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.

3 participants