Skip to content

Real-time Collaboration: Use minimal save payload in persistCRDTDoc#77050

Merged
alecgeatches merged 1 commit into
WordPress:trunkfrom
chubes4:fix/persist-crdt-doc-minimal-save
May 19, 2026
Merged

Real-time Collaboration: Use minimal save payload in persistCRDTDoc#77050
alecgeatches merged 1 commit into
WordPress:trunkfrom
chubes4:fix/persist-crdt-doc-minimal-save

Conversation

@chubes4
Copy link
Copy Markdown
Contributor

@chubes4 chubes4 commented Apr 6, 2026

What?

Changes persistCRDTDoc to call saveEntityRecord with only the entity ID, instead of the entire editedRecord.

The existing post-type __unstablePrePersist hook still creates and attaches meta._crdt_document during the save. This keeps the CRDT persistence behavior while avoiding a full entity-record round trip through REST validation.

Why?

persistCRDTDoc currently sends the full entity record back through the REST API on page load. This causes 400 Bad Request errors when any field in the record doesn't pass strict REST schema validation — even though the post is otherwise valid.

Two real-world examples:

  1. ping_status: "" — WordPress core stores this when the Discussion settings pingback checkbox is unchecked. The REST schema only accepts "open" or "closed", so the round-trip fails.
  2. Co-Authors Plus — Registers a REST field coauthors that returns objects, while a taxonomy rest_base with the same name expects integers. When the full record is sent back, REST validation rejects the objects.

Both are pre-existing data/schema issues that become visible because persistCRDTDoc unnecessarily round-trips the entire edited entity record. It only needs to trigger a save so the pre-persist hook can persist the CRDT document.

How?

In packages/core-data/src/resolvers.js, persistCRDTDoc now constructs a minimal save payload containing only the entity ID key. It keeps the current { __unstableSkipSyncUpdate: true } option so the save response is not replayed into the sync document.

The resolver test now covers the regression shape by including ping_status: "" on the edited record and asserting that the save payload omits it.

Testing Instructions

  1. Go to Settings → Discussion, uncheck "Allow link notifications from other blogs (pingbacks and trackbacks) on new posts", and save.
  2. Create and publish a new post.
  3. Enable Real-Time Collaboration (Settings → Writing → Enable real-time collaboration, or set wp_collaboration_enabled to 1).
  4. Open the published post in the block editor.
  5. Open DevTools → Network tab.
  6. Before this fix: A POST /wp-json/wp/v2/posts/{id} request can return 400 with "ping_status is not one of open and closed".
  7. After this fix: The CRDT persistence save is triggered with only the entity ID, and the pre-persist hook adds the CRDT meta without sending unrelated REST-invalid fields.

Focused test run:

npm run test:unit -- packages/core-data/src/test/resolvers.js --runInBand

Result: PASS — 36 tests passed.

AI assistance

  • AI assistance: Yes
  • Tool(s): Claude Code (claude-opus-4-6), OpenCode (GPT-5.5)
  • Used for: Bug investigation, drafting the original fix, resolving the branch against current trunk, tightening the regression test, and validating the focused resolver test. The fix logic and test evidence were reviewed by a human contributor.

Closes #77049

@chubes4 chubes4 requested a review from nerrad as a code owner April 6, 2026 06:00
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 6, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: chubes4 <extrachill@git.wordpress.org>
Co-authored-by: alecgeatches <alecgeatches@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@chubes4 chubes4 force-pushed the fix/persist-crdt-doc-minimal-save branch from 4108070 to 6ca408e Compare May 19, 2026 19:36
@chubes4 chubes4 changed the title Real-time Collaboration: Only persist CRDT meta in persistCRDTDoc, not full entity record Real-time Collaboration: Use minimal save payload in persistCRDTDoc May 19, 2026
@alecgeatches alecgeatches self-requested a review May 19, 2026 20:25
@alecgeatches alecgeatches added [Feature] Real-time Collaboration Phase 3 of the Gutenberg roadmap around real-time collaboration [Type] Bug An existing feature does not function as intended labels May 19, 2026
Copy link
Copy Markdown
Contributor

@alecgeatches alecgeatches left a comment

Choose a reason for hiding this comment

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

Great change! Your explanation and reproduction is succinct, and your code changes look good. I've tested that this fixes the issue outlined in #77049. I believe it also fixes another behavior that causes the post to constantly re-save on each post open when a ping_status-like field is present, because the updated doc never hits the server. Great job, thanks!

@alecgeatches alecgeatches merged commit c4bd19b into WordPress:trunk May 19, 2026
48 of 49 checks passed
@github-actions github-actions Bot added this to the Gutenberg 23.3 milestone May 19, 2026
@alecgeatches
Copy link
Copy Markdown
Contributor

alecgeatches commented May 19, 2026

I also want to note that not sending the post title/content/etc is fine. The point of calling persistCRDTDoc() is for the side-effect of committing the CRDT document when we detect it's different from the DB record. In that situation, the database is already showing us there's a change, and the CRDT document we're committing is directly derived from the post data we just loaded, so sending the same post data back in a save doesn't accomplish anything extra. It actually probably solves a short data race possibility in between post data load and re-persistence.

User saves go through saveEntityRecord() instead which does correctly persist all of the changed attributes.

@chubes4
Copy link
Copy Markdown
Contributor Author

chubes4 commented May 20, 2026

Thank you! I had noticed some console errors in my browser while editing posts and went hunting for the root cause. Glad to see it is fixed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Real-time Collaboration Phase 3 of the Gutenberg roadmap around real-time collaboration [Package] Core data /packages/core-data [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Real-time Collaboration: persistCRDTDoc sends entire entity record, causing 400 errors on valid posts

2 participants