-
Notifications
You must be signed in to change notification settings - Fork 33
Remove loading message randomization #3295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove loading message randomization #3295
Conversation
📝 WalkthroughWalkthroughThis update introduces the Traffic Boost feature, significantly expanding both backend and frontend functionality. It adds new REST API endpoints, models, and validation classes to support inbound smart links and traffic boost suggestions, including placement and acceptance workflows. The Smart Link and Inbound Smart Link models are refactored for richer metadata, status management, and ITM parameter handling. On the frontend, a comprehensive React-based dashboard page is introduced, featuring client-side routing, a posts table, and a dedicated Traffic Boost UI with sidebar, preview, and link management components. Supporting code includes new utility functions, provider classes, Redux stores, and extensive SCSS styling. The plugin settings, permissions, and admin menu structure are updated to accommodate the new feature. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant DashboardPage (React)
participant TrafficBoostSidebar (React)
participant TrafficBoostProvider (JS)
participant REST_API
participant Inbound_Smart_Link (PHP)
participant WP_Post
User->>DashboardPage: Navigates to Traffic Boost page
DashboardPage->>TrafficBoostSidebar: Renders sidebar, requests suggestions/inbound links
TrafficBoostSidebar->>TrafficBoostProvider: getInboundLinks(postId)
TrafficBoostProvider->>REST_API: GET /traffic-boost/inbound-links
REST_API->>Inbound_Smart_Link: Query inbound smart links for post
Inbound_Smart_Link->>WP_Post: Load post data
REST_API-->>TrafficBoostProvider: Return inbound links
TrafficBoostProvider-->>TrafficBoostSidebar: Return inbound links
TrafficBoostSidebar-->>DashboardPage: Display inbound links
User->>TrafficBoostSidebar: Clicks "Generate Suggestions"
TrafficBoostSidebar->>TrafficBoostProvider: generateBatchSuggestions(postId, n)
TrafficBoostProvider->>REST_API: POST /traffic-boost/suggestions
REST_API->>Inbound_Smart_Link: Generate suggestions via Suggestions API
Inbound_Smart_Link->>WP_Post: Validate placement, create smart links
REST_API-->>TrafficBoostProvider: Return suggestions
TrafficBoostProvider-->>TrafficBoostSidebar: Update sidebar with new suggestions
User->>DashboardPage: Selects a suggestion
DashboardPage->>TrafficBoostProvider: acceptSuggestion(postId, suggestionId)
TrafficBoostProvider->>REST_API: POST /traffic-boost/accept
REST_API->>Inbound_Smart_Link: Apply smart link to post content
REST_API-->>TrafficBoostProvider: Return status
TrafficBoostProvider-->>DashboardPage: Update UI with applied link
Possibly related PRs
Suggested labels
Suggested reviewers
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Caution
Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
🛑 Comments failed to post (45)
phpstan.neon (1)
16-17: 💡 Verification agent
🧩 Analysis chain
Confirm if type coverage threshold reduction is necessary.
The return type coverage threshold has been lowered from 90% to 88%, and parameter type coverage from 79.2% to 79.1%. While these are small reductions, consider whether this is a temporary accommodation or if additional type improvements should be planned for future PRs.
🏁 Script executed:
#!/bin/bash # Check current type coverage to see if it matches the adjusted thresholds # Run PHPStan with type coverage output vendor/bin/phpstan analyse --memory-limit=2G --configuration=phpstan.neon --no-progress --error-format=json | jq '.meta.type_coverage'Length of output: 203
🏁 Script executed:
#!/bin/bash # Verify installed PHPStan version to confirm support for type_coverage thresholds vendor/bin/phpstan --versionLength of output: 73
Fix phpstan.neon type coverage settings
It looks like PHPStan 1.12.24 doesn’t recognize the
type_coverage
parameters, so your thresholds aren’t being applied. Please correct the config (or install/enable the Type Coverage extension) before adjusting the numbers again.• File: phpstan.neon (around lines 16–17)
– Remove or rename the invalidtype_coverage
block.
– Use the supported format (e.g. underparameters: typeCoverage: returnType: 88, paramType: 79.1
) or add the necessary extension.
• Once the configuration is valid, re-evaluate whether lowering return-type coverage from 90%→88% and param-type coverage from 79.2%→79.1% is required, or if you can plan additional type improvements in upcoming PRs.src/content-helper/editor-sidebar/smart-linking/component.tsx (1)
377-377:
⚠️ Potential issueUpdated to access the raw URL property in SmartLink object.
The property access was updated to use
link.href.raw
instead oflink.href
to align with changes to theSmartLink
type, where thehref
property was changed from a string to an object containingraw
anditm
URL properties.However, there are two related issues that should be addressed:
- At line 379, the console.warn statement still uses
link.href
directly, which may print "[object Object]" instead of the URL.- At line 388, the comparison
sl.href === link.href
will not work correctly sincelink.href
is now an object.Apply this diff to fix these issues:
- console.warn( `PCH Smart Linking: Skipping self-reference link: ${ link.href }` ); + console.warn( `PCH Smart Linking: Skipping self-reference link: ${ link.href.raw }` ); - if ( sl.href === link.href ) { + if ( sl.href.raw === link.href.raw ) {📝 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.if ( link.href.raw.includes( strippedPermalink ) ) { - console.warn( `PCH Smart Linking: Skipping self-reference link: ${ link.href }` ); + console.warn( `PCH Smart Linking: Skipping self-reference link: ${ link.href.raw }` ); } // … other logic … - if ( sl.href === link.href ) { + if ( sl.href.raw === link.href.raw ) { // … }
src/content-helper/common/settings/types/traffic-boost-settings.d.ts (1)
1-8: 🛠️ Refactor suggestion
Interface seems to use a placeholder property.
The
TrafficBoostSettings
interface is defined with a single property namedSetting1
of type string. This appears to be a placeholder rather than a meaningful setting name for the Traffic Boost feature.Consider renaming this property to something more descriptive that accurately represents an actual Traffic Boost setting (e.g.,
enabledFeatures
,boostLimit
, etc.) or add the actual properties that will be needed for Traffic Boost settings.export interface TrafficBoostSettings { - Setting1: string; + // Consider replacing with actual settings properties + enabledFeatures: string[]; + // Add other relevant properties as needed }📝 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./** * Defines the Traffic Boost settings structure. * * @since 3.18.0 */ export interface TrafficBoostSettings { // Consider replacing with actual settings properties enabledFeatures: string[]; // Add other relevant properties as needed }
package.json (3)
81-81:
⚠️ Potential issueDuplicate dependency declaration.
The
lodash.debounce
package is already included in dependencies at line 42. This duplicate declaration should be removed to avoid potential version conflicts.- "lodash.debounce": "^4.0.8",
📝 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.
49-49:
⚠️ Potential issuePotential duplicate dependency.
The
@types/js-cookie
is already added to dependencies at line 39. Having the same package in both dependencies and devDependencies is redundant and could lead to version conflicts.- "@types/js-cookie": "^3.0.6",
📝 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.
51-51: 💡 Verification agent
🧩 Analysis chain
Type definitions match React Router version?
Ensure the
@types/react-router
version is compatible with thereact-router
version at line 83. The React Router v7 might need different type definitions than what's currently specified.
🌐 Web query:
What version of @types/react-router is compatible with react-router v7?
💡 Result:
There is no separate
@types/react-router
package required or available for React Router v7. Starting with React Router v6, and continuing with v7, all type definitions are included directly within the mainreact-router
package itself. The old@types/react-router
package on DefinitelyTyped is not maintained for v6 or later, and is incompatible with v7[1][9].What to use with React Router v7:
- Simply install
react-router@latest
(or the v7 version you need); TypeScript types are bundled.- Do not install
@types/react-router
with v7—it is obsolete for v7 and will lead to type conflicts[1][9].Summary Table
React Router Version Type Definitions Source v5 and earlier @types/react-router
v6 and v7 Bundled with react-router
If you're upgrading from an older version and see
@types/react-router
in your dependencies, you should remove it when moving to v7. This is the recommended, type-safe approach for both JavaScript and TypeScript projects with modern React Router[4][9].Citations:
- 1: https://www.npmjs.com/package/@types/react-router
- 2: https://reactrouter.com
- 3: https://reactrouter.com/upgrading/v6
- 4: https://remix.run/blog/react-router-v7
- 5: React Router v7 Docs - Missing a lot of what I used when migrating to v6 remix-run/react-router#12370
- 6: https://reactrouter.com/upgrading/remix
- 7: https://www.youtube.com/watch?v=h7MTWLv3xvw
- 8: https://www.npmjs.com/package/react-router-typesafe-routes
- 9: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md
- 10: https://www.youtube.com/watch?v=ferLCqcLcGU
Remove obsolete
@types/react-router
dependencyFor React Router v7, all TypeScript definitions are bundled with the main
react-router
package. The separate@types/react-router
package only supports v5 and earlier and will conflict with v7. Please remove it from your dependencies:
- package.json (line 51): delete
- "@types/react-router": "^5.1.20",
<!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **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. `````suggestion { "dependencies": { - "@types/react-router": "^5.1.20", // …other dependencies… }, // …rest of package.json… }
src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/post-details.tsx (1)
31-46:
⚠️ Potential issueSecurity concern with dangerouslySetInnerHTML.
While the component structure is clean and the loading state is handled appropriately, there's a security concern with using dangerouslySetInnerHTML for the post title.
- <div className="post-title" dangerouslySetInnerHTML={ { __html: post?.title.rendered ?? __( '(no title)', 'wp-parsely' ) } } /> + <div className="post-title"> + { post?.title.rendered + ? <span dangerouslySetInnerHTML={ { __html: post.title.rendered } } /> + : __( '(no title)', 'wp-parsely' ) + } + </div>This change ensures that dangerouslySetInnerHTML is only used when necessary (for rendered HTML titles) and not for the fallback text, which should be treated as plain text.
📝 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.return ( <div className="traffic-boost-post-details"> <div className="traffic-boost-post-details-label"> { __( 'Current Post:', 'wp-parsely' ) } </div> <div className="traffic-boost-post-details-content"> <Thumbnail post={ post } size={ 52 } className="traffic-boost-thumbnail" /> { isLoading && <Spinner /> } { ! isLoading && ( <div className="post-title"> { post?.title.rendered ? <span dangerouslySetInnerHTML={ { __html: post.title.rendered } } /> : __( '(no title)', 'wp-parsely' ) } </div> ) } </div> </div> );
🧰 Tools
🪛 Biome (1.9.4)
[error] 44-44: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
src/content-helper/dashboard-page/components/posts-table/style.scss (3)
78-79: 🛠️ Refactor suggestion
Hardcoded color values should use variables per SCSS coding guidelines.
Instead of using the hardcoded color
#757575
, use a corresponding color variable from the variables.scss file.- color: #757575; + color: var(--gray-500);
152-153: 🛠️ Refactor suggestion
Padding uses hardcoded pixel value instead of variables.
The padding contains a hardcoded value of
2px
that should be converted to a variable or to_rem function for consistency.- padding: 2px var(--grid-unit-10); + padding: var(--grid-unit-02) var(--grid-unit-10);If the specific variable doesn't exist, you should convert it to rem units:
- padding: 2px var(--grid-unit-10); + padding: to_rem(2) var(--grid-unit-10);📝 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.border-radius: to_rem(6px); - padding: 2px var(--grid-unit-10); + padding: var(--grid-unit-02) var(--grid-unit-10);
52-52: 🛠️ Refactor suggestion
Fixed pixel values should use to_rem function per coding guidelines.
The
width: to_rem(180px)
syntax might cause issues. Theto_rem
function typically expects a numeric value without the 'px' unit.- width: to_rem(180px); + width: to_rem(180);📝 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.width: to_rem(180);
src/content-helper/dashboard-page/pages/traffic-boost/sidebar/sidebar.tsx (3)
66-96: 🛠️ Refactor suggestion
Consider using React state instead of direct DOM manipulation.
Using direct DOM manipulation with
document.querySelector
and manually updating content isn't the React way and can lead to issues with the Virtual DOM reconciliation.Consider refactoring to use React state and JSX to handle the tab counter display:
// Example approach (simplified) const [suggestionCount, setSuggestionCount] = useState(0); const [inboundCount, setInboundCount] = useState(0); useEffect(() => { setSuggestionCount(suggestions.length); setInboundCount(inboundLinks.length); }, [suggestions.length, inboundLinks.length]); // Then in your JSX: <> {suggestionCount > 0 && <span className="tab-count">{suggestionCount}</span>} </>This would be more in line with React's declarative approach.
96-96:
⚠️ Potential issueFix dependency array in useEffect.
The useEffect dependency array includes
inboundLinks
twice: once as the object and once as its length. This is redundant and could cause unnecessary re-renders.- }, [ inboundLinks, inboundLinks.length, suggestions.length ] ); + }, [ inboundLinks.length, suggestions.length ] );📝 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.}, [ inboundLinks.length, suggestions.length ] );
103-108: 🛠️ Refactor suggestion
Avoid simulating DOM clicks in React.
Simulating clicks with
tab.click()
isn't recommended in React. Instead of manipulating the DOM directly, consider updating your TabPanel implementation to accept and use the selectedTab state:<TabPanel className="traffic-boost-sidebar-tabs" + selected={selectedTab} tabs={ [ // ... ] } onSelect={ ( tab: string ) => setSelectedTab( tab as TrafficBoostSidebarTabs ) } >If the WordPress TabPanel component doesn't support this, consider wrapping it with your own component that does.
Committable suggestion skipped: line range outside the PR's diff.
src/content-helper/dashboard-page/pages/traffic-boost/preview/components/preview-header.tsx (1)
167-176: 🛠️ Refactor suggestion
Remove dead code – condition always short-circuits.
&& false &&
guarantees the “Toggle Frontend Preview” button never appears. If the intention is to keep the feature behind a flag, extract that flag to a prop or constant; otherwise, delete the block to simplify rendering.- { ! isExternalURL( activeLink ) && false && ( + { ! isExternalURL( activeLink ) && ( <Button … /> ) }📝 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.{ ! isExternalURL( activeLink ) && ( <Button icon={ desktop } isPressed={ isFrontendPreview } disabled={ isLoading } iconSize={ 24 } onClick={ onToggleFrontendPreview } label={ __( 'Toggle Frontend Preview', 'wp-parsely' ) } /> ) }
src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/links-list/links-list.tsx (1)
317-333:
⚠️ Potential issuePage selector never fires due to inverted guard.
handlePageChange
exits early when! useScrollbar
, yet the<select>
is only rendered when! useScrollbar
is true. Consequently, manual page selection is broken.- const handlePageChange = ( value?: string ) => { - if ( ! useScrollbar ) { - return; - } + const handlePageChange = ( value?: string ) => { + if ( useScrollbar ) { + return; // Page selector is disabled when scrollbar mode is active. + }Verify navigation after patching.
📝 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.const handlePageChange = ( value?: string ) => { if ( useScrollbar ) { return; // Page selector is disabled when scrollbar mode is active. } if ( ! value ) { return; } let selectedPage = parseInt( value, 10 ); if ( selectedPage > totalPages ) { selectedPage = totalPages; } else if ( selectedPage < 1 ) { selectedPage = 1; } onPageChange?.( selectedPage ); };
src/content-helper/dashboard-page/components/posts-table/component.tsx (1)
198-204: 🛠️ Refactor suggestion
itemsPerPage
freezes to its initial value whenquery.per_page
changes.
useState
initialiser runs only once, so if the parent component updatesquery.per_page
the table will still use the olditemsPerPage
. Replace the state withuseMemo
(or derive directly) and keep a single source of truth.-const [ itemsPerPage ] = useState<number>( query.per_page ?? 10 ); +const itemsPerPage = query.per_page ?? 10;📝 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.const [ currentPage, setCurrentPage ] = useState<number>( 1 ); const [ totalPages, setTotalPages ] = useState<number>( 1 ); - const [ itemsPerPage ] = useState<number>( query.per_page ?? 10 ); + const itemsPerPage = query.per_page ?? 10; const [ isLoading, setIsLoading ] = useState<boolean>( true ); const didFirstSearch = useRef( false );
src/content-helper/dashboard-page/pages/traffic-boost/preview/components/preview-iframe.tsx (2)
140-169:
⚠️ Potential issueEvent listeners added on every load without corresponding removal risk memory leaks.
disableNavigation
attaches several capturing listeners (click
,submit
,contextmenu
) each time the iframe reloads. Over time this accumulates. Keep references so you canremoveEventListener
during iframe cleanup or wrap them inonce: true
.
74-83: 🛠️ Refactor suggestion
crypto.randomUUID()
is not available in all supported browsers.Older Safari (≤ 14.1) and IE do not implement
randomUUID
. Provide a small fallback (e.g. UUID v4 polyfill) to avoid breaking previews on legacy browsers.
src/services/suggestions-api/endpoints/class-endpoint-suggest-inbound-links.php (1)
63-71: 🛠️ Refactor suggestion
Sanitisation: use
sanitize_text_field()
for titles, notesc_attr()
.
esc_attr()
is appropriate when outputting to an HTML attribute, but here the value is stored/processed server-side and later returned in JSON. Usingsanitize_text_field()
avoids HTML-escaping quotes and keeps the title human-readable.-esc_attr( $link['title'] ), +sanitize_text_field( $link['title'] ),Committable suggestion skipped: line range outside the PR's diff.
src/content-helper/dashboard-page/pages/traffic-boost/single-post-component.tsx (3)
270-274: 🛠️ Refactor suggestion
Avoid mutating React state objects in place.
link.smartLink.is_link_replacement = true;
mutates an object that is very likely referenced in Redux/React state, bypassing change detection. Instead, clone the object and call the relevant store action.- if ( didReplaceLink ) { - link.smartLink.is_link_replacement = true; - } + if ( didReplaceLink ) { + updateInboundLink( + { ...link, smartLink: { ...link.smartLink, is_link_replacement: true } }, + link.smartLink.uid + ); + }📝 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.if ( didReplaceLink ) { updateInboundLink( { ...link, smartLink: { ...link.smartLink, is_link_replacement: true } }, link.smartLink.uid ); } link.targetPost.content.raw = postContent;
120-128:
⚠️ Potential issueNull-check
wpWrap
in cleanup to avoid runtime error.If the initial selector fails (
#wpwrap
not found),( wpWrap as HTMLElement ).style.backgroundColor = ''
will throw on unmount. Guard the assignment.- ( wpWrap as HTMLElement ).style.backgroundColor = ''; + if ( wpWrap ) { + ( wpWrap as HTMLElement ).style.backgroundColor = ''; + }📝 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.return () => { // When the component unmounts, make sure to cancel all the provider requests. TrafficBoostProvider.getInstance().cancelAll(); // Disconnect the resize observer. resizeObserver.disconnect(); // Remove the background color of #wpwrap. if ( wpWrap ) { ( wpWrap as HTMLElement ).style.backgroundColor = ''; } // Clean up the store state.
385-468: 🛠️ Refactor suggestion
fetchSuggestions
effect misses key deps — may generate duplicates.The effect depends on
inboundLinks
,settings.maxItems
, and several dispatcher setters, but the dependency array only containspostId
. This can cause:
- Suggestions to be generated before
inboundLinks
load, ignoring the exclusion list.- Settings changes (e.g., different
maxItems
) not being respected.Add the missing dependencies or refactor the logic to a callback that is safe to memoise.
-}, [ postId ] ); // eslint-disable-line react-hooks/exhaustive-deps +}, [ postId, inboundLinks, settings.maxItems ] ); // keep other setters stable via useDispatch📝 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.-}, [ postId ] ); // eslint-disable-line react-hooks/exhaustive-deps +}, [ postId, inboundLinks, settings.maxItems ] ); // eslint-disable-line react-hooks/exhaustive-deps
src/content-helper/dashboard-page/pages/traffic-boost/store.ts (2)
767-799: 🛠️ Refactor suggestion
Duplicate selectors cause confusion and dead weight.
isLoadingSuggestions()
andisSuggestionsLoading()
both expose the same logic (loading.includes('suggestions')
). Please consolidate into a single selector to keep the public store surface minimal and prevent future divergence.Also applies to: 797-800
401-412:
⚠️ Potential issueAvoid mutating the incoming Redux action object.
The reducer mutates
action.loadingType
when it is falsy. Redux reducers should be pure functions and never mutate their inputs (actions or state). This can also violate TypeScript’s readonly expectations for action objects created byactions.setLoading()
.- case 'SET_LOADING': - if ( ! action.loadingType ) { - action.loadingType = 'default'; - } - return { + case 'SET_LOADING': { + const loadingType = action.loadingType ?? 'default'; + return { ...state, - loading: action.isLoading - ? [ ...state.loading, action.loadingType ] - : state.loading.filter( ( type ) => type !== action.loadingType ), + loading: action.isLoading + ? [ ...state.loading, loadingType ] + : state.loading.filter( ( type ) => type !== loadingType ), }; + }📝 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.case 'SET_LOADING': { const loadingType = action.loadingType ?? 'default'; return { ...state, loading: action.isLoading ? [ ...state.loading, loadingType ] : state.loading.filter( ( type ) => type !== loadingType ), }; } case 'SET_ERROR':
src/services/suggestions-api/endpoints/class-endpoint-suggest-inbound-link-positions.php (3)
70-78: 🛠️ Refactor suggestion
Gracefully handle missing canonical URLs.
Parsely::get_canonical_url_from_post()
returns the literal string “no permalink” on failure. Forwarding that value will produce unusable suggestions. ReturnWP_Error
early if either canonical URL is invalid.
85-88: 🛠️ Refactor suggestion
Validate performance blending weight range.
performance_blending_weight
is forwarded verbatim; values outside 0-1 will be rejected upstream and surface as hard-to-diagnose 400 errors. Add a clamp or validation before building$request_body
.
109-128:
⚠️ Potential issueURLs should be sanitised before instantiation.
$link['target_url']
and$link['source_url']
are consumed directly. Although data is remote-origin, always escape withesc_url_raw()
when persisting or further processing.- $link['target_url'], + esc_url_raw( $link['target_url'] ), ... - $smart_link->set_source_post( $source_post, $link['source_url'] ); + $smart_link->set_source_post( $source_post, esc_url_raw( $link['source_url'] ) );📝 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.$suggestions = array(); /** @var LinkPositionResponse $link */ $link = $response[0]; foreach ( $link['anchor_texts'] as $anchor_text_suggestion ) { $smart_link = new Inbound_Smart_Link( esc_url_raw( $link['target_url'] ), esc_attr( $link['title'] ), wp_kses_post( $anchor_text_suggestion['text'] ), $anchor_text_suggestion['offset'] ); // Set the source post and update the canonical URL. $smart_link->set_source_post( $source_post, esc_url_raw( $link['source_url'] ) ); // Set the destination post. $smart_link->set_destination_post( $destination_post ); $suggestions[] = $smart_link; }
src/content-helper/common/base-wordpress-provider.tsx (2)
258-262: 🛠️ Refactor suggestion
rest_endpoint
leaks into query string.You construct the endpoint with
addQueryArgs()
after merging the fullqueryParams
object, sorest_endpoint
itself becomes a query parameter sent to/wp/v2/posts
. Strip it out first:- const restEndpoint = queryParams.rest_endpoint ?? '/wp/v2/posts'; - const posts = await this.apiFetch<Post[]>( { - path: addQueryArgs( restEndpoint, { ...queryParams, _embed: true, context: 'edit' } ), + const { rest_endpoint: restEndpoint = '/wp/v2/posts', ...cleanParams } = queryParams; + const posts = await this.apiFetch<Post[]>( { + path: addQueryArgs( restEndpoint, { ...cleanParams, _embed: true, context: 'edit' } ),📝 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.// Extract and rename/rest_endpoint, defaulting to '/wp/v2/posts', and strip it from the query params. const { rest_endpoint: restEndpoint = '/wp/v2/posts', ...cleanParams } = queryParams; const posts = await this.apiFetch<Post[]>( { path: addQueryArgs( restEndpoint, { ...cleanParams, _embed: true, context: 'edit' } ), method: 'GET',
162-164:
⚠️ Potential issue
wpError.json()
is not guaranteed – can throw a secondary error.
@wordpress/api-fetch
rejects with a plainError
whose shape differs fromResponse
. Callingjson()
on it crashes the handler. Guard with aninstanceof Response
check and fall back to the normalWPErrorObject
.- const errorData = await wpError.json(); - return Promise.reject( new ContentHelperError( errorData.message ?? '', errorData.code, '' ) ); + if ( wpError instanceof Response ) { + const errorData = await wpError.json(); + return Promise.reject( + new ContentHelperError( errorData.message ?? '', errorData.code ?? ContentHelperErrorCode.UnknownError, '' ), + ); + } + // Fallback: re-wrap the original error. + return Promise.reject( + new ContentHelperError( wpError.message ?? '', ContentHelperErrorCode.UnknownError, '' ), + );📝 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.if ( wpError instanceof Response ) { const errorData = await wpError.json(); return Promise.reject( new ContentHelperError( errorData.message ?? '', errorData.code ?? ContentHelperErrorCode.UnknownError, '', ), ); } // Fallback: re-wrap the original error. return Promise.reject( new ContentHelperError( wpError.message ?? '', ContentHelperErrorCode.UnknownError, '', ), ); } finally {
src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/tabs/suggestions-tab.tsx (1)
198-240: 🛠️ Refactor suggestion
⚠️ Potential issueRisk of irreversible list clearing on generation failure.
handleGenerateSuggestions
wipes the currentsuggestions
state before the asyncgenerateBatchSuggestions
call succeeds. If the provider throws early, users permanently lose their existing list and must regenerate. A safer pattern is:- // Clear the suggestions list. - setSuggestions( [] ); - … - await trafficBoostProvider.generateBatchSuggestions( … ); + const previousSuggestions = suggestions; // snapshot + setSuggestions( [] ); + … + await trafficBoostProvider.generateBatchSuggestions( … ); + // restore if nothing was generated + if ( ! select( TrafficBoostStore ).getSuggestions().length ) { + setSuggestions( previousSuggestions ); + }Alternatively, defer the clear until the first
onNewSuggestions
callback.Committable suggestion skipped: line range outside the PR's diff.
src/content-helper/editor-sidebar/smart-linking/utils.ts (2)
436-439: 🛠️ Refactor suggestion
Possible double-encoding of ITM parameters.
addSmartLinkITMParamsToURL( link.href, uid )
is invoked after the raw URL is extracted withremoveITMParamsFromURL
, but it re-uses the originallink.href
which might already contain ITM params. WhileURLSearchParams.set
overrides duplicates, redundant work causes noisy diffs and hampers caching. Prefer:- itm: addSmartLinkITMParamsToURL( link.href, uid ), + itm: addSmartLinkITMParamsToURL( href.raw, uid ),so we always start from a clean URL.
📝 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.const href = { raw: removeITMParamsFromURL( link.href ), - itm: addSmartLinkITMParamsToURL( link.href, uid ), + itm: addSmartLinkITMParamsToURL( href.raw, uid ), };
599-603:
⚠️ Potential issueURL comparison is too fuzzy; may restore wrong link.
a.href.includes( missingSmartLink.href.raw )
matches on substrings, sohttps://example.com/foo
will also matchhttps://example.com/foobar
. Use strict origin/path equality to avoid restoring metadata to an unrelated link:- a.href.includes( missingSmartLink.href.raw ) + new URL( a.href ).href === new URL( missingSmartLink.href.raw ).href(Be sure to wrap in
try/catch
for malformed URLs.)📝 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.const link = Array.from( blockDoc.querySelectorAll( 'a' ) ).find( ( a ) => { return a.textContent === missingSmartLink.text && new URL( a.href ).href === new URL( missingSmartLink.href.raw ).href && ! a.hasAttribute( 'data-smartlink' ); } );
src/content-helper/dashboard-page/pages/traffic-boost/preview/hooks/use-iframe-highlight.ts (2)
588-603:
⚠️ Potential issueDomain check mis-classifies protocol-relative & sub-domain links.
link.href.includes( siteUrl )
will treathttps://blog.example.com
as internal forexample.com
, and may mis-label protocol-relative (//example.com
). Resolve both hosts and compare:const isSameHost = ( url: string, referenceHost: string ): boolean => { try { return new URL( url ).hostname === referenceHost; } catch { return false; } }; … links = links.filter( ( link ) => isSameHost( link.href, siteUrl ) );This aligns with WordPress’ approach to “internal link” detection.
52-60: 🛠️ Refactor suggestion
Style element injected on every render – leads to duplicates & CSS bloat.
injectHighlightStyles
appends a<style>
tag each time it runs. Navigating between posts or toggling the preview quickly piles up identical rules. Before insertion, probeiframeDocument.head
for an existing node with an id/class, e.g.:- const style = iframeDocument.createElement( 'style' ); + if ( iframeDocument.getElementById( 'pch-highlight-styles' ) ) { + return; + } + const style = iframeDocument.createElement( 'style' ); + style.id = 'pch-highlight-styles';This keeps the DOM lean and avoids redundant CSS parsing.
📝 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.const injectHighlightStyles = useCallback( ( iframe: HTMLIFrameElement ) => { const iframeDocument = iframe.contentDocument ?? iframe.contentWindow?.document; if ( ! iframeDocument ) { return; } // Avoid injecting duplicate styles. if ( iframeDocument.getElementById( 'pch-highlight-styles' ) ) { return; } const style = iframeDocument.createElement( 'style' ); style.id = 'pch-highlight-styles'; style.textContent = ` /** Smart link highlight styles. */ /* …rest of CSS… */ `; iframeDocument.head.appendChild( style ); }, [] );
src/content-helper/dashboard-page/pages/traffic-boost/preview/preview.tsx (2)
291-299: 🛠️ Refactor suggestion
Avoid mutating Redux / selector objects in place.
link.isSuggestion = false;
mutates the object that was previously selected from the store.
When using Redux (or any shared-state library) mutating in place breaks referential equality, making downstream selectors, middleware and React memoisation unreliable.Instead create a shallow copy (or better, dispatch a dedicated action):
-// Flag isSuggestion to false. -link.isSuggestion = false; -// Add the link to the inbound links list. -addInboundLink( link ); +const updatedLink = { ...link, isSuggestion: false }; +addInboundLink( updatedLink );📝 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.// Remove suggestion from the list. removeSuggestion( link ); const updatedLink = { ...link, isSuggestion: false }; addInboundLink( updatedLink );
149-172: 🛠️ Refactor suggestion
useEffect
dependency list can trigger an unnecessary re-render loop.
previewUrl
is included in the dependency array but is also updated inside the effect.
IfactivePost
orisFrontendPreview
remain unchanged, the effect will still run twice: once to set the new URL, and immediately again becausepreviewUrl
has changed – even though the computednewUrl
is identical. This costs an extra paint of the iframe.A safer pattern is to derive
newUrl
in auseMemo
(or simply removepreviewUrl
from the dependency list) and compare with the current state before callingsetPreviewUrl
.-}, [ activePost, isFrontendPreview, previewUrl ] ); // … +}, [ activePost, isFrontendPreview ] );Make sure to keep the “only update when changed” early-return guard so users don’t lose the loading spinner.
src/UI/class-dashboard-page.php (1)
169-175: 🛠️ Refactor suggestion
Menu slug contains a hash – WordPress may strip this.
add_submenu_page()
uses'parsely-dashboard-page#/traffic-boost'
as the slug.
Core sanitisation removes the#
fragment, so the submenu can’t be matched later, breaking highlight and permissions checks.Prefer adding the submenu with the canonical slug (
parsely-dashboard-page
) and letting the React router handle the hash internally.
src/content-helper/dashboard-page/pages/traffic-boost/provider.ts (2)
227-271:
⚠️ Potential issueRetry loop may starve when no new suggestions are returned.
maxRetries
is only decremented whennewGeneratedSuggestions.length === 0
or thecatch
fires.
If the API repeatedly returns duplicate suggestions (filtered out byurlExclusionList
),filteredGeneratedSuggestions.length
can be0
, yetnewGeneratedSuggestions.length
>0
. In that case the loop spins forever.Consider decrementing
maxRetries
when no additions were made:- if ( newGeneratedSuggestions.length === 0 ) { + if ( filteredGeneratedSuggestions.length === 0 ) { maxRetries--; continue; }This prevents an infinite loop while still allowing the designated retry budget.
Committable suggestion skipped: line range outside the PR's diff.
579-603: 💡 Verification agent
🧩 Analysis chain
Guard against
undefined
post IDs sent togetPosts()
.
postIds
may containundefined
values wheninboundSmartLink.source?.post_id
is missing (e.g. links to external URLs). Passing such values tothis.getPosts()
causes needless round-trips and—in some back-ends—400 responses.- const postIds = smartLinks.map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id ); + const postIds = smartLinks + .map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id ) + .filter( ( id ): id is number => typeof id === 'number' && id > 0 );The same filter should be applied earlier in
getExistingSuggestions()
when you computepostIds
for the early-return check.
🏁 Script executed:
#!/bin/bash # Find the definition of InboundSmartLink to inspect the source.post_id type rg -n "InboundSmartLink" -C3 # Locate getExistingSuggestions to see how postIds are computed there rg -n "getExistingSuggestions" -C5 src/content-helper/dashboard-page # Confirm occurrences of .post_id in the codebase rg -n "post_id" -C3 srcLength of output: 66808
🏁 Script executed:
#!/bin/bash # Show definitions of SmartLink and InboundSmartLink rg -n "export type SmartLink" -C10 src/content-helper/editor-sidebar/smart-linking/provider.ts rg -n "export type InboundSmartLink" -C10 src/content-helper/editor-sidebar/smart-linking/provider.ts # Show property 'source' in that file rg -n "source" -C5 src/content-helper/editor-sidebar/smart-linking/provider.tsLength of output: 2728
🏁 Script executed:
#!/bin/bash # Display the LinkedPost type definition to verify its fields rg -n "type LinkedPost" -A20 src/content-helper/editor-sidebar/smart-linking/provider.tsLength of output: 573
🏁 Script executed:
#!/bin/bash # Display the full implementation of getExistingSuggestions sed -n '160,240p' src/content-helper/dashboard-page/pages/traffic-boost/provider.tsLength of output: 3111
🏁 Script executed:
#!/bin/bash # Show the definition of BaseWordPressProvider and getPosts method rg -n "class BaseWordPressProvider" -C20 src/common/base-wordpress-provider.tsLength of output: 158
Guard against undefined post IDs to prevent invalid
getPosts()
calls
Map and then filterinboundSmartLink.source?.post_id
so only valid numbers are sent to the WordPress API. Apply this both when you buildpostIds
forgetPosts()
and in your early-return ingetExistingSuggestions()
.Examples:
--- a/src/content-helper/dashboard-page/pages/traffic-boost/provider.ts +++ b/src/content-helper/dashboard-page/pages/traffic-boost/provider.ts @@ private async createTrafficBoostLinks( inboundSmartLinks: InboundSmartLink[] ): Promise<TrafficBoostLink[]> { - const postIds = smartLinks.map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id ); + const postIds = smartLinks + .map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id ) + .filter( ( id ): id is number => typeof id === 'number' && id > 0 ); @@ public async getExistingSuggestions( postId: number ): Promise<TrafficBoostLink[]> { - const postIds = response.data.map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id ); - if ( postIds.length === 0 ) { + const postIds = response.data + .map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id ) + .filter( ( id ): id is number => typeof id === 'number' && id > 0 ); + if ( postIds.length === 0 ) { return []; }📝 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.const getPostsPromises = Object.entries( smartLinksByPostType ).map( async ( [ postRestEndpoint, smartLinks ] ) => { // Get the post IDs from the inbound smart links. - const postIds = smartLinks.map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id ); + const postIds = smartLinks + .map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id ) + .filter( ( id ): id is number => typeof id === 'number' && id > 0 ); // Fetch the posts for the inbound smart links. const fetchedPosts = await this.getPosts( { include: postIds, posts_per_page: 100,
src/rest-api/content-helper/class-endpoint-traffic-boost.php (2)
143-148: 🛠️ Refactor suggestion
ignore_keywords
lacks validation / sanitisation.Other array parameters (
keyword_exclusion_list
,url_exclusion_list
) rely on dedicated validation classes, butignore_keywords
is taken at face value. Malformed data could trigger PHP warnings or bypass expectations further downstream.Add callbacks similar to the other parameters:
- 'ignore_keywords' => array( - 'type' => 'array', - 'description' => __( 'The keywords to ignore.', 'wp-parsely' ), - 'required' => false, - ), + 'ignore_keywords' => array( + 'type' => 'array', + 'description' => __( 'The keywords to ignore.', 'wp-parsely' ), + 'required' => false, + 'validate_callback' => 'wp_parse_list', // or a dedicated Validate_Keyword_List class + 'sanitize_callback' => 'wp_kses_post', // or similar lightweight sanitiser + ),
495-501:
⚠️ Potential issue
in_array()
call risks a warning when$keyword_exclusion_list
is null.The variable is nullable (
$request->get_param()
returns null when the parameter is absent).
Passingnull
toin_array()
triggers a warning in strict-types mode.- if ( in_array( $suggestion->text, $keyword_exclusion_list, true ) ) { + if ( ! empty( $keyword_exclusion_list ) + && in_array( $suggestion->text, (array) $keyword_exclusion_list, true ) ) { continue; }This keeps the logic intact while ensuring safe execution.
📝 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.foreach ( $suggestions as $suggestion ) { // If the ignore keywords are set and the suggested text is in the ignore keywords, skip it. if ( ! empty( $keyword_exclusion_list ) && in_array( $suggestion->text, (array) $keyword_exclusion_list, true ) ) { continue; } /** @var WP_Error|bool $valid_placement Whether the suggestion has a valid placement. */
src/Models/class-smart-link.php (2)
1042-1044: 🛠️ Refactor suggestion
Invalid
orderby
value'date modified'
inWP_Query
.
WP_Query
accepts'modified'
or'modified_gmt'
, not'date modified'
.
The current string falls back to the default order and logs a notice whenWP_DEBUG
is enabled.- 'orderby' => 'date modified', + 'orderby' => 'modified',Adjust to guarantee predictable ordering and suppress unnecessary log noise.
📝 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.'orderby' => 'modified', 'order' => 'ASC', ),
1092-1095:
⚠️ Potential issueNon-static method invoked statically – breaks under
strict_types
.
flush_cache()
callsself::get_cache_group()
, butget_cache_group()
is an instance method.
PHP 8 emits a deprecation notice (and future versions a fatal error) for static invocation of non-static methods.- wp_cache_delete( 'smart-link-uid-map-' . $this->uid . '-' . $this->source_post_id, self::get_cache_group() ); + wp_cache_delete( 'smart-link-uid-map-' . $this->uid . '-' . $this->source_post_id, $this->get_cache_group() );📝 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.protected function flush_cache(): void { // Delete the cache for the smart link UID to post ID association. wp_cache_delete( 'smart-link-uid-map-' . $this->uid . '-' . $this->source_post_id, $this->get_cache_group() ); }
src/Models/class-inbound-smart-link.php (2)
514-540:
⚠️ Potential issue
find_text_node()
offset tracking is broken due to pass-by-value.
$offset
is decremented inside the function, but the recursive calls receive an unchanged copy, so only the first branch ever reaches zero. The nth occurrence can therefore never be found.-private function find_text_node( \DOMNode $node, string $search_text, int $offset ) { +private function find_text_node( \DOMNode $node, string $search_text, int &$offset ) { … - foreach ( $node->childNodes as $child ) { - $result = $this->find_text_node( $child, $search_text, $offset ); + foreach ( $node->childNodes as $child ) { + $result = $this->find_text_node( $child, $search_text, $offset ); if ( null !== $result ) { return $result; } }Passing
$offset
by reference allows the counter to propagate through the traversal, correctly locating the targeted occurrence.📝 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.private function find_text_node( \DOMNode $node, string $search_text, int &$offset ) { /* phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase */ // If the node is a text node, check if it contains the search text. if ( XML_TEXT_NODE === $node->nodeType ) { if ( strpos( $node->textContent, $search_text ) !== false ) { if ( 0 === $offset ) { return $node; } --$offset; } } // If the node has child nodes, recursively search for the text node. if ( $node->hasChildNodes() ) { /** @var \DOMNode $child */ foreach ( $node->childNodes as $child ) { $result = $this->find_text_node( $child, $search_text, $offset ); if ( null !== $result ) { return $result; } } } // If the text node is not found, return null. return null; /* phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase */ }
788-792: 🛠️ Refactor suggestion
Line-level
str_replace()
risks unintended multiple replacements.
str_replace( $original_line, $line_with_smart_link, $source_post_content )
will replace every identical line in the post, not just the one inside the target paragraph. This can corrupt content if the same HTML appears elsewhere.Prefer a position-based replacement using the DOM or
substr_replace()
with exact byte offsets, or scope the replacement to the current paragraph string you already have:-$source_post_content = str_replace( $original_line, $line_with_smart_link, $source_post_content ); +$source_post_content = str_replace( + $original_paragraph_html, // original paragraph + $html_parser->saveHTML( $paragraph_fragment ), // updated paragraph + $source_post_content +);This confines the change to the known paragraph and avoids collateral edits.
📝 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.// Replace the original line with the new HTML. $source_post_content = str_replace( $original_paragraph_html, // original paragraph $html_parser->saveHTML( $paragraph_fragment ), // updated paragraph $source_post_content ); // Backup the original post content and paragraph in a post meta, for rollback purposes.
Description
A small follow-up to #3292 to remove all loading message randomization. The loading messages displayed in the right-hand of the Traffic Boost pane were still randomized.