Skip to content

WP 7.0: Fix Rewrite & Republish race conditions with Real-Time Collaboration#485

Open
enricobattocchi wants to merge 6 commits intotrunkfrom
feature/rtc-compat
Open

WP 7.0: Fix Rewrite & Republish race conditions with Real-Time Collaboration#485
enricobattocchi wants to merge 6 commits intotrunkfrom
feature/rtc-compat

Conversation

@enricobattocchi
Copy link
Copy Markdown
Member

@enricobattocchi enricobattocchi commented Mar 27, 2026

Context

  • WordPress 7.0 ships Real-Time Collaboration (RTC) enabled by default. RTC is active on sites using Duplicate Post with the block editor because the plugin uses PluginDocumentSettingPanel instead of metaboxes. This creates several issues with the Rewrite & Republish flow when multiple users are involved.

Summary

This PR can be summarized in the following changelog entry:

  • Improves compatibility of the Rewrite & Republish feature with Real-Time Collaboration in WordPress 7.0.

Relevant technical choices:

  • Race condition (item 1): Uses add_post_meta() with $unique = true to claim the slot on the original post before creating the copy. This prevents duplicate copies at the database level. If wp_insert_post() fails, the claim is rolled back.
  • Collaborator redirect (item 2): The RTC status change does not propagate because the republishing user navigates away before the CRDT update is sent. Instead, we poll the REST API every 2 seconds to detect when the copy is republished (dp-rewrite-republish status), trashed, or deleted (404). The restBase is provided via localized PHP data because select('core').getPostType() is a resolver-backed selector that returns undefined at script initialization time.
  • Dynamic button (item 3): Registers _dp_has_rewrite_republish_copy with show_in_rest and reads it via getEntityRecord from the core store. Server-side meta changes bypass the RTC CRDT, so periodic cache invalidation via invalidateResolution every 15 seconds ensures the editor picks up the change. invalidateResolution is safe with unsaved local edits — the store's edits reducer preserves local changes that differ from the server response.
  • All changes are backward-compatible with WP 6.x — the new code paths are harmless no-ops without concurrent editors.

Test instructions

Test instructions for the acceptance test before the PR gets merged

This PR can be acceptance tested by following these steps:

Setup: WordPress 7.0+ with RTC enabled, two browser sessions (different users or normal + incognito).

Item 1 — Race condition:

  1. Open a published post in the block editor.
  2. Click "Rewrite & Republish" to create a copy.
  3. Go back to the same published post and verify the "Rewrite & Republish" button/link is no longer available (a copy already exists).
  4. To test the concurrent scenario more rigorously: open two browser tabs on the same published post's row actions, and click "Rewrite & Republish" in both tabs as quickly as possible. Only one should succeed; the other should show an error message.

Item 2 — Collaborator redirect:

  1. Both users open the same R&R copy in the block editor.
  2. User A clicks "Republish".
  3. Verify User A is redirected to the original post as before.
  4. Verify User B sees a snackbar "Another user has republished this post. Redirecting to the original…" and is redirected after ~3 seconds.
  5. Verify User B sees an info notice on the original post: "Another user has republished the Rewrite & Republish copy you were editing. You are now viewing the original post."

Item 3 — Dynamic button:

  1. Both users open the same published post in the block editor.
  2. Verify both see the "Rewrite & Republish" button.
  3. User A clicks "Rewrite & Republish".
  4. Verify User B's button disappears within ~15 seconds and a snackbar notification appears.

Backward compatibility:

  1. On WP 6.9 (no RTC), verify the normal single-user R&R flow works: create copy → edit → republish → redirect to original.

Relevant test scenarios

  • Changes should be tested with the browser console open
  • Changes should be tested on different posts/pages/taxonomies/custom post types/custom taxonomies
  • Changes should be tested on different editors (Default Block/Gutenberg/Classic/Elementor/other)
  • Changes should be tested on different browsers
  • Changes should be tested on multisite

Test instructions for QA when the code is in the RC

  • QA should use the same steps as above.

QA can test this PR by following these steps:

Impact check

This PR affects the following parts of the plugin, which may require extra testing:

  • The Rewrite & Republish copy creation flow (src/post-duplicator.php)
  • The block editor UI and redirect logic (js/src/duplicate-post-edit-script.js)
  • The republished post watcher notices (src/watchers/republished-post-watcher.php)
  • The localized JS data object (src/ui/block-editor.php)

UI changes

  • This PR changes the UI in the plugin. I have added the 'UI change' label to this PR.

Documentation

  • I have written documentation for this change. For example, comments in the Relevant technical choices, comments in the code, documentation on Confluence / shared Google Drive / Yoast developer portal, or other.

Quality assurance

  • I have tested this code to the best of my abilities
  • I have added unittests to verify the code works as intended

Innovation

  • No innovation project is applicable for this PR.
  • This PR falls under an innovation project. I have attached the innovation label and noted the work hours.

Fixes Yoast/reserved-tasks#1127

enricobattocchi and others added 3 commits March 27, 2026 21:24
Register _dp_has_rewrite_republish_copy post meta for the REST API and
read it reactively via the core store's getEntityRecord. Since the R&R
copy is created via a server-side admin action (not through the editor),
the meta change does not enter the RTC CRDT document. Periodic cache
invalidation (every 15s) ensures the editor refetches the entity record
and picks up the change.

A snackbar notification informs the user when another collaborator has
created an R&R copy, explaining why the button disappeared.

Ref: Yoast/reserved-tasks#1127

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When another user republishes an R&R copy via RTC, the collaborator is
left editing a deleted post. The RTC status change does not propagate
because the republishing user navigates away before the CRDT update is
sent.

Detect this by polling the REST API every 2 seconds for the copy's
status. When the copy is republished (dp-rewrite-republish), trashed,
or deleted (404), show a snackbar notification and redirect the
collaborator to the original post's edit screen after 3 seconds.

Also adds a dpcollabredirected query param so the original post's edit
page shows an info notice explaining what happened.

Ref: Yoast/reserved-tasks#1127

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claim the slot on the original post using add_post_meta() with
$unique = true before creating the copy. This returns false if the
meta key already exists, preventing duplicate copies when two concurrent
requests both pass the permission check before either sets the meta.

If wp_insert_post() fails, the claim is rolled back by deleting the
meta.

Also fixes Block_Editor_Test to mock get_post_type_object() for the
restBase addition to the localized JS object.

Ref: Yoast/reserved-tasks#1127

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coveralls
Copy link
Copy Markdown

coveralls commented Mar 27, 2026

Pull Request Test Coverage Report for Build 23667162515

Details

  • 33 of 57 (57.89%) changed or added relevant lines in 3 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.1%) to 59.752%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/ui/block-editor.php 16 22 72.73%
src/watchers/republished-post-watcher.php 3 21 14.29%
Totals Coverage Status
Change from base Build 23650305143: -0.1%
Covered Lines: 1636
Relevant Lines: 2738

💛 - Coveralls

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Improves Rewrite & Republish (R&R) behavior under WordPress 7.0 Real-Time Collaboration by preventing duplicate copy creation and ensuring collaborators are redirected/notified when a copy is republished/deleted.

Changes:

  • Prevents concurrent R&R requests from creating multiple copies by claiming a unique meta “slot” before copy creation.
  • Adds block editor UI updates for RTC: hides the R&R button when a copy exists (via REST-exposed meta + periodic cache invalidation) and polls REST to redirect collaborators when the copy is republished/deleted.
  • Adds collaborator-redirect notices and makes the new query arg removable; updates unit tests accordingly.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/post-duplicator.php Adds a DB-level “claim” via unique post meta to avoid duplicate R&R copies under concurrent requests.
src/ui/block-editor.php Registers REST-exposed meta and localizes restBase to support editor-side polling/invalidation.
js/src/duplicate-post-edit-script.js Adds RTC-aware UI: meta-based button hiding, periodic invalidation, and REST polling/redirect logic.
src/watchers/republished-post-watcher.php Adds collaborator redirect notice + removable query arg handling.
tests/Unit/UI/Block_Editor_Test.php Extends unit tests for the new init hook, meta registration, and localized restBase.
tests/Unit/Watchers/Republished_Post_Watcher_Test.php Updates removable query args test to include the new parameter.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

enricobattocchi and others added 2 commits March 27, 2026 21:50
- Use per-post auth_callback for register_post_meta (edit_post cap
  instead of generic edit_posts).
- Fall back to post type name when rest_base is empty, matching the
  pattern used by WP core REST controllers.
- Only redirect collaborator on confirmed deletion (404/410/403), not
  on transient network errors.
- Improve register_post_meta unit test to verify config values.
- Add WP integration tests for R&R race condition: slot claiming,
  duplicate blocking, and rollback on failure.

Ref: Yoast/reserved-tasks#1127

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants