Skip to content

Conversation

@Arukuen
Copy link
Contributor

@Arukuen Arukuen commented Jan 20, 2026

fixes #14

Observations:

  • Issue is not related to additional wrapper when Show Template is on
  • The issue only occurs when the element trigger is Block (using anchors)

Issues:

  • When Show Template is on, the editor renders the blocks twice (once for the template preview, once for the real editor, with both having different clientId) so it causes duplication in the HOC for tracking clientId/anchor pairs.
  • Toggling Show Template re-renders all the blocks changing the clientId.

Summary by CodeRabbit

  • Bug Fixes
    • Improved timeline initialization stability during rendering-mode transitions, reducing glitches and delayed starts when switching editor modes.
    • Enhanced anchor tracking to eliminate duplicate entries in the timeline editor, preventing stale or conflicting anchors and improving editor reliability.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

Introduces rendering-mode–aware initialization and persistent starting styles in the timeline runner hook, and refactors anchor tracking to remove duplicates by tracking the previous anchor reference and cleaning up accordingly.

Changes

Cohort / File(s) Summary
Timeline runner hook
src/editor/components/timeline/runner.js
Added useState for initialStyles, useEffect-driven init flow, prevRenderingMode ref, and initRunner helper. Reads rendering mode from WP data store and conditionally delays initialization via requestAnimationFrame when mode changed. Effect dependencies updated to include interaction, timelineIndex, actions, and renderingMode. Returns remain [runnerRef, initialStyles].
Anchor tracking hook
src/editor/components/timeline/with-tracked-anchors.js
Added useRef prevAnchorRef; effect now early-returns if no anchor, removes previous anchor/clientId pair before adding current, prevents duplicates by removing existing matches, updates prevAnchorRef, and removes the current pair on unmount. Dependency array unchanged.

Sequence Diagram(s)

sequenceDiagram
    participant Hook as useTimelineRunnerRef
    participant Store as WP Data Store
    participant RAF as requestAnimationFrame
    participant Runner as Runner/init logic
    participant Anchors as Anchor replacements

    Hook->>Store: read renderingMode
    alt renderingMode changed
        Hook->>RAF: requestAnimationFrame(callback)
        RAF-->>Hook: callback
        Hook->>Anchors: replace anchors
        Hook->>Runner: configure & init
        Runner-->>Hook: starting styles
    else renderingMode unchanged
        Hook->>Anchors: replace anchors
        Hook->>Runner: configure & init
        Runner-->>Hook: starting styles
    end
    Hook-->>Hook: set initialStyles state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
I hopped through modes and waited yet—
I tracked old anchors, cleared the net.
Raf-timed starts and styles that stay,
A tidy trail for hops today. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'fix/14 editor show template' is vague and uses branch naming convention rather than a clear summary of the fix. Revise the title to clearly describe the main fix, e.g., 'Fix anchor tracking when Show Template is enabled' or 'Prevent duplicate clientId tracking in template preview renders'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The pull request successfully addresses issue #14 by fixing anchor tracking and preventing clientId duplication when Show Template is enabled.
Out of Scope Changes check ✅ Passed All changes in runner.js and with-tracked-anchors.js are directly scoped to fixing the Show Template rendering and anchor tracking issues identified in #14.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 Jan 20, 2026
@github-actions
Copy link

github-actions bot commented Jan 20, 2026

🤖 Pull request artifacts

file commit
pr15-interactions-15-merge.zip 9a10e74

Copy link
Contributor

@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

🤖 Fix all issues with AI agents
In `@src/editor/components/timeline/runner.js`:
- Around line 67-76: The current code returns
runnerRef.current.getStartingActionStyles() synchronously even when initRunner
is deferred, producing stale/empty styles; move the renderingMode change
handling out of this synchronous block into a useEffect that watches
renderingMode and calls initRunner (using setTimeout if needed), then update
local state (e.g., startingStyles) or trigger a re-render when initRunner
completes; change the return to read from that state (instead of calling
getStartingActionStyles() directly) so styles are computed after initRunner
finishes—refer to initRunner, prevRenderingMode.current, renderingMode, and
runnerRef.current.getStartingActionStyles when locating where to apply this
change.

In `@src/editor/components/timeline/with-tracked-anchors.js`:
- Around line 15-43: The effect is missing cleanup and skips removing a previous
anchor when the current anchor becomes undefined; update the useEffect
(referencing useEffect, prevAnchorRef.current, ANCHORS, CLIENT_IDS,
props.clientId, props.attributes?.anchor) so that before the early return you
still remove any prevAnchorRef.current entry from ANCHORS/CLIENT_IDS, and return
a cleanup function that removes the current anchor (or prevAnchorRef.current if
undefined) from ANCHORS and CLIENT_IDS on unmount to avoid stale entries; ensure
you update prevAnchorRef.current appropriately after removals.
🧹 Nitpick comments (1)
src/editor/components/timeline/runner.js (1)

70-70: Consider cleanup for the setTimeout.

If the component re-renders or unmounts before the timeout fires, initRunner would still execute with potentially stale references. Consider storing the timeout ID and clearing it on cleanup or subsequent renders.

Suggestion using a ref to track timeout
 	const runnerRef = useRef( null )
+	const initTimeoutRef = useRef( null )
 	const renderingMode = select( 'core/editor' ).getRenderingMode()
 	const prevRenderingMode = useRef( renderingMode )

Then in the useMemo:

 		if ( prevRenderingMode.current !== renderingMode ) {
+			if ( initTimeoutRef.current ) {
+				clearTimeout( initTimeoutRef.current )
+			}
-			setTimeout( initRunner, 0 )
+			initTimeoutRef.current = setTimeout( initRunner, 0 )
 		} else {

Comment on lines 15 to 43
useEffect( () => {
if ( props.attributes.anchor ) {
// Remove any previous anchor selector for this clientId.
const index = CLIENT_IDS.indexOf( props.clientId )
if ( index !== -1 ) {
CLIENT_IDS.splice( index, 1 )
ANCHORS.splice( index, 1 )
const anchor = props.attributes?.anchor
if ( ! anchor ) {
return
}

// If this block had a previous anchor, remove it.
if ( prevAnchorRef.current ) {
const oldIndex = ANCHORS.indexOf( prevAnchorRef.current )
if ( oldIndex !== -1 ) {
ANCHORS.splice( oldIndex, 1 )
CLIENT_IDS.splice( oldIndex, 1 )
}
// Keep track of the clientId-anchor pair
CLIENT_IDS.push( props.clientId )
ANCHORS.push( props.attributes.anchor )
}
}, [
props.clientId,
props.attributes?.anchor,
] )

// Remove any existing entry for this anchor.
// This ensures no duplication when the blocks are re-rendered.
const existingIndex = ANCHORS.indexOf( anchor )
if ( existingIndex !== -1 ) {
ANCHORS.splice( existingIndex, 1 )
CLIENT_IDS.splice( existingIndex, 1 )
}

ANCHORS.push( anchor )
CLIENT_IDS.push( props.clientId )

// Store anchor for next render
prevAnchorRef.current = anchor
}, [ props.clientId, props.attributes?.anchor ] )
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing cleanup on unmount and when anchor is removed.

Two issues with the effect:

  1. No cleanup function: When the component unmounts, the anchor entry remains in the global arrays, causing stale data.

  2. Early return doesn't clean up previous anchor: If an anchor is removed from a block (becomes undefined), the early return at line 17-19 skips removing the old entry stored in prevAnchorRef.current.

Proposed fix
 		useEffect( () => {
 			const anchor = props.attributes?.anchor
+
+			// If anchor was removed, clean up the previous entry
 			if ( ! anchor ) {
+				if ( prevAnchorRef.current ) {
+					const oldIndex = ANCHORS.indexOf( prevAnchorRef.current )
+					if ( oldIndex !== -1 ) {
+						ANCHORS.splice( oldIndex, 1 )
+						CLIENT_IDS.splice( oldIndex, 1 )
+					}
+					prevAnchorRef.current = undefined
+				}
 				return
 			}
 
 			// If this block had a previous anchor, remove it.
 			if ( prevAnchorRef.current ) {
 				const oldIndex = ANCHORS.indexOf( prevAnchorRef.current )
 				if ( oldIndex !== -1 ) {
 					ANCHORS.splice( oldIndex, 1 )
 					CLIENT_IDS.splice( oldIndex, 1 )
 				}
 			}
 
 			// Remove any existing entry for this anchor.
 			// This ensures no duplication when the blocks are re-rendered.
 			const existingIndex = ANCHORS.indexOf( anchor )
 			if ( existingIndex !== -1 ) {
 				ANCHORS.splice( existingIndex, 1 )
 				CLIENT_IDS.splice( existingIndex, 1 )
 			}
 
 			ANCHORS.push( anchor )
 			CLIENT_IDS.push( props.clientId )
 
 			// Store anchor for next render
 			prevAnchorRef.current = anchor
+
+			// Cleanup on unmount
+			return () => {
+				const index = ANCHORS.indexOf( anchor )
+				if ( index !== -1 ) {
+					ANCHORS.splice( index, 1 )
+					CLIENT_IDS.splice( index, 1 )
+				}
+			}
 		}, [ props.clientId, props.attributes?.anchor ] )
🤖 Prompt for AI Agents
In `@src/editor/components/timeline/with-tracked-anchors.js` around lines 15 - 43,
The effect is missing cleanup and skips removing a previous anchor when the
current anchor becomes undefined; update the useEffect (referencing useEffect,
prevAnchorRef.current, ANCHORS, CLIENT_IDS, props.clientId,
props.attributes?.anchor) so that before the early return you still remove any
prevAnchorRef.current entry from ANCHORS/CLIENT_IDS, and return a cleanup
function that removes the current anchor (or prevAnchorRef.current if undefined)
from ANCHORS and CLIENT_IDS on unmount to avoid stale entries; ensure you update
prevAnchorRef.current appropriately after removals.

github-actions bot added a commit that referenced this pull request Jan 21, 2026
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.

When Template > Show Template is checked, all interaction trigger will be inaccessible.

2 participants