Skip to content

Core Data: Replace mutated edits with original record to fix dirty state#77611

Open
karthikeya-io wants to merge 2 commits into
WordPress:trunkfrom
karthikeya-io:fix/dirty-state-after-revisions-restore
Open

Core Data: Replace mutated edits with original record to fix dirty state#77611
karthikeya-io wants to merge 2 commits into
WordPress:trunkfrom
karthikeya-io:fix/dirty-state-after-revisions-restore

Conversation

@karthikeya-io
Copy link
Copy Markdown
Contributor

@karthikeya-io karthikeya-io commented Apr 23, 2026

What?

Closes #77610

This PR fixes the issue where the editor gets stuck in a dirty state ("Changes you made may not be saved" warning) after successfully restoring a visual revision or saving a post that contains meta updates (like when using the Footnotes block).

Why?

When saveEntityRecord prepares to send meta edits to the server, __unstablePrePersist() changes the edits.meta payload by injecting an internal collaborative editing key (_crdt_document).

After the save succeeds, saveEntityRecord dispatches receiveEntityRecords to clear the saved edits from the store. Previously, it passed the mutated edits variable as the persistedEdits argument.

Because the Redux reducer compares the original edits in the store (which lack _crdt_document) against this mutated persistedEdits object using fastDeepEqual, the comparison fails. The reducer incorrectly assumes the save was incomplete and leaves the post permanently "dirty".

How?

This PR updates saveEntityRecord to pass the original, un-mutated record to receiveEntityRecords as persistedEdits. This ensures the equality check succeeds and the editor correctly clears its dirty state.

Testing Instructions

Visual Revisions

  1. Create a new post and publish it.
  2. Make a change to the post to create a new revision, and click "Update".
  3. Open the visual revisions interface and restore the previous revision.
  4. Attempt to refresh the page or navigate away. You should no longer see the "Changes you made may not be saved" warning.

Test 2: Meta-updating Blocks

  1. Open a post.
  2. Add a Footnote block (which updates post meta).
  3. Save the post.
  4. Attempt to refresh the page or navigate away. You should no longer see the "Changes you made may not be saved" warning.

Screenshots or screencast

Before

After

dirty_editor_state.mov

Use of AI Tools

  • Yes, Used Gemini CLI to assist with debugging

@github-actions github-actions Bot added the [Package] Core data /packages/core-data label Apr 23, 2026
@karthikeya-io karthikeya-io marked this pull request as ready for review April 23, 2026 14:52
@karthikeya-io karthikeya-io requested a review from nerrad as a code owner April 23, 2026 14:52
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 23, 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: karthikeya-io <karthikeya01@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: jsnajdr <jsnajdr@git.wordpress.org>

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

@Mamaduka
Copy link
Copy Markdown
Member

I think similar issue was fixed recently. See #77529. cc @alecgeatches

Comment thread packages/core-data/src/actions.js Outdated
undefined,
true,
edits
record
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changing to record means that entityConfig.__unstablePrePersist never runs for this save, while it might fix the mentioned issue, it will break something else.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for checking this. I replaced edits with record in the 6th argument of receiveEntityRecords (which happens after the save). This argument seems to be used as persistedEdits to reset the user's edits from the store. So I think this shouldn't affect __unstablePrePersist or the data we save to the database.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It affects the existing "contract" established before our use of meta._crdt_document. The values modified by __unstablePrePersist were previously counted as persisted edits; now, they will not be. The original behavior seems correct, because that's the actual value saved to the DB.

cc @chriszarate, @jsnajdr

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The __unstablePrePersist callback was originally used to "clean up" the data before sending them to server and saving. Add a missing title, set the correct status, handle auto-drafts. It was introduced in commit 85173ee, it's instructive to read it, not hard to understand.

Today this callback is also used to add the meta._crdt_document field. This doesn't look like a good place to do it. It's not a cleanup, but a regular course of business when RTC is enabled.

These two functions, cleanup and RTC, should be completely separated. The RTC part can happen directly in saveEntityRecord. This function already calls getSyncManager().update, now it could also call getSyncManager().createPersistedCRDTDoc when appropriate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the feedback! I have moved the code that adds _crdt_document to the meta payload directly into saveEntityRecord and passed the edits variable, which includes values modified by __unstablePrePersist, to receiveEntityRecords to reset the edits. Excluding _crdt_document from this variable fixes the dirty state issue, as it is now added directly to the fetch payload.

@t-hamano t-hamano added the [Type] Bug An existing feature does not function as intended label Apr 27, 2026
@karthikeya-io karthikeya-io force-pushed the fix/dirty-state-after-revisions-restore branch from 7e1f385 to b8a3302 Compare April 29, 2026 20:51
@karthikeya-io karthikeya-io force-pushed the fix/dirty-state-after-revisions-restore branch from b8a3302 to 88b40f3 Compare April 29, 2026 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[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.

Restoring a revision using Visual Revisions or saving a post with meta blocks leaves the editor in a dirty state

4 participants