RTC: Remove excess autosave notices (when not useful) - #80539
Conversation
…ed CRDT updates or a connection fails
|
Size Change: +1.07 kB (+0.01%) Total Size: 7.77 MB 📦 View Changed
|
…cceeds instead of waiting 3 seconds
|
Flaky tests detected in 56a6042. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/30563767865
|
…o avoid overlap with other layers
…CRDT content was shared, apply to HTTP polling
…into useEntityContainsSnapshot hook
|
@ingeniumed @chriszarate This is ready for another round of reviews! Thank you for your thoughtful first round. I've hopefully addressed all comments above and made these architectural changes:
I've run through the PR description scenarios again to confirm the autosave notice behavior works the same. The only difference is we now show an autosave after a 3-second delay for the Author autosaves solo and RTC backend is unreachable + WS server is present (part 2) scenarios. |
ingeniumed
left a comment
There was a problem hiding this comment.
I can still replicate the same bug that I noticed with the previous implementation, so I'm not recording that once more.
| const snapshotStatus = useEntityContainsSnapshot( { | ||
| postType, | ||
| postId, | ||
| snapshot: localAutosave?.crdt_snapshot, |
There was a problem hiding this comment.
Would store the crdt_snapshot in a utility and use that instead. Would recommend the same for the PHP usages as well. That way it'll be easy to see where it's used and if we do want to change it, do it in one place. Though I fully get if package deps make this tedious.
There was a problem hiding this comment.
It's a bit awkward to get a private key from core-data to this part of the editor, but I agree .crdt_snapshot-type accessors are fragile. Routed everything through constants and utility functions in 247cdfe.
| // contained changes nothing, so equality with the local delete set proves | ||
| // containment. | ||
| const mergedDeleteSet = Y.mergeDeleteSets( [ | ||
| Y.snapshot( ydoc ).ds, |
There was a problem hiding this comment.
Could you re-use the localSnapshot instead?
There was a problem hiding this comment.
Fair question! This was on purpose but wasn't documented, added that in 663df41. The quick answer is that Y.mergeDeleteSets also mutates, and we didn't want to mutate localSnapshot here.
| // Wrap and return the public API. | ||
| return { | ||
| createPersistedCRDTDoc: debugWrap( createPersistedCRDTDoc ), | ||
| entityContainsSnapshot, |
There was a problem hiding this comment.
Is there a benefit to debugWrap this?
| ); | ||
|
|
||
| if ( Number.isFinite( autosavedAt ) ) { | ||
| getSyncManager()?.markEntityAutosaved( |
There was a problem hiding this comment.
The new location of getEntitySnapshot() doesn't fully solve this as its still called before direct autosave changes are applied to the CRDT.
A caller (plugin for instance) can pass new content directly to saveEntityRecord (see the example above). That means not via the editor as it's done now. That content enters the autosave payload, but the snapshot describes the previous Yjs state. On reload, that older state satisfies the snapshot and the recovery notice can be suppressed.
The quick fix is to move the existing direct-record CRDT update before the autosave/non-autosave branch. Essentially move the call in line 840 to line 731
Note: This problem exists in trunk, but due to the excess autosave notice issue there's a workaround. One can hit recover autosave and get those untracked changes. Your PR fixes this workaround and makes the bug unrecoverable.
Hopefully that makes sense.
| $snapshot = $request->get_param( 'crdt_snapshot' ); | ||
|
|
||
| if ( ! is_string( $snapshot ) || '' === $snapshot ) { | ||
| return; |
There was a problem hiding this comment.
Would it be worth clearing the existing metadata, if any, when there’s no valid snapshot to replace it? The likelihood of this happening seems small because the existing Gutenberg code supplies the snapshot.
If an autosave already has _crdt_autosave_snapshot metadata and a later request doesn’t include a valid snapshot, the old snapshot will remain attached to the new content. This would probably require a plugin calling the endpoint directly, an older client, or snapshot capture failing.
I don’t think it’s blocking, but it may be worth considering as a safeguard.
|
@ingeniumed Thank you for taking another look! Quick question here:
Can you let me know which bug you're able to replicate? If it's the revision bug at the top of your last review, local autosaves have been redesigned to also use the new snapshot system, and I'm not able to locally reproduce. Please let me know if you still can reproduce on a fresh build, or otherwise which bug you are referring to. Thanks! |
…core-data + autosaves controller
…tosave snapshots, ensure single-user updates are flushed
Before e988a58 I was able to consistently replicate #80539 (review). But, after 56a6042 I'm no longer able to replicate this consistently. I can still replicate it but it requires me to go in and out 2 times or reload 2 times. It's def better than what used to happen before so IMO this shouldn't be a blocker. This PR is going to get trunk into a much better shape when it comes to these notices. |
|
Recategorizing this as type: bug. |
What?
Closes #80077.
Avoid showing the "There is an autosave of this post that is more recent than the version below" notice in RTC when content in the autosave is already present in the current document.
Note that the line count change is big here, but that is currently ~50% test code. Most functional changes are in
use-autosave-notice.js, which refactors autosave-related functionality from the general provider into a hook, and most of the rest is plumbing for autosave markers in the CRDT.Why?
In RTC, all user autosaves target a revision and not the actual parent post. In a traditional WordPress draft, the post author updates the actual parent post, but other users update revisions. As described in #75105 this makes post reconciliation differ per-user, so instead all users in RTC target revisions until the post is manually saved.
As a result, this makes the autosave notice much more visible in RTC. After an autosave fires, WordPress detects that the latest autosave is newer than the parent post and sets an
autosaveflag on load. Gutenberg reads the flag and shows the more recent autosave notice. Because we're always targeting a revision until a real save happens, the autosave warning is always triggered.This is very common in RTC. If users A and B are working in a document and haven't saved recently, user C joining will always see a more recent autosave notice, despite the fact that the CRDT document is up-to-date and there is no missing content to "restore".
How?
The fix is to store when a user created an autosave in the CRDT doc:
User A and B are editing a post in RTC.
User A makes changes and autosaves.
User C joins, and WordPress detects a newer autosave than the parent post from user A.
User C connects to the CRDT doc and reads user A's autosave marker, whose timestamp matches the autosave that WordPress flagged as new. Both values are the server's own modified time for the same autosave revision, so no client clocks are involved. The marker was written by user A into the CRDT document after the autosave succeeded, which means after the content the autosave captured had already entered the document.
Because Yjs delivers a client's updates in order, any copy of the document that contains User A's marker necessarily contains that content too. User C can trust that all of the prior content in user A's autosave is also present in the live document.
Because the autosave was successfully recorded in the CRDT doc, user C skips showing the autosave notice.
There are a few different scenarios that this PR handles. For the videos below,
AUTOSAVE_INTERVALis set to20seconds to make reproduction faster.Author autosaves while synced with peers (above)
This is the general annoying state where during a collaborative editing session, any user joining a post sees the autosave warning. Now it's hidden:
autosave-with-peers.mov
Above, users A and B join a post. The post contains "saved content", with a new paragraph of "unsaved content" persisted via autosave. User A persists the autosave and also updates the autosave timestamp in the CRDT document. When user C joins, they can match user A's autosave timestamp with the timestamp in the CRDT doc and ignore the "unsaved" content warning.
Author autosaves solo (queue paused)
When a user using HTTP polling edits a post by themselves, we pause sending CRDT updates until another collaborator has joined to avoid unnecessary chatter. In this scenario, a user's autosaved changes will not end up in the live CRDT document, and the autosave warning is desirable:
autosave-solo-2.mov
Above, a user creates a post with content, saves, and later adds unsaved content. After an autosave fires, the user leaves. When the user rejoins, there is a recent autosave but no matching CRDT entry for the autosave, so we show the warning. Note that the warning appears roughly immediately: as soon as a sync happens, we're able to verify the autosave key is missing from the document.
RTC backend is unreachable
This situation is very similar to the one above (where we pause updates to the backend), but we can demonstrate how this works with WebSockets when an sync server connection fails during editing. Below, we use the built-in
npm run rtc:ws:slowsetup but kill the WebSockets server. In the grace period between connecting to a post and seeing the disconnection dialog, a user still may persist an autosave that does not existing CRDT content.A user starts a post with a successful WebSockets connection and saves content. The user then loses access to the sync backend, and during the disconnection grace period add content that produces an autosave. This content is not persisted in the CRDT due to the connection loss. When the user reloads the post, one of two things happen:
If the WebSocket server is still down, the user attempts to connect to the server. After not receiving a response for 3 seconds the editor can't tell if the autosave content has been correctly persisted, and shows the notice. The idea is "fail visible". If we can't know for sure the autosave is available in the CRDT, show the autosave notice.
ws-disconnected-autosave.mov
If the WebSocket server is back, the user will reconnect to the WebSockets CRDT doc and know that the autosave is newer than the content and show the notice immediately:
ws-reconnect-autosave.mov
Note that this requires the transport to implement an
onInitialSynccallback, but if a provider doesn't have that flag the 3-second timeout still will fire either way and show the notice as above.Autosave predates RTC
One issue discussed in fully removing the autosave notice in RTC is pre-RTC autosaves. The solution in this PR will detect that the autosave is not represented in the CRDT after RTC is enabled and still show the notice:
autosave-pre-rtc.mov
Above, we need two users as user A (who created the post) will autosave directly to the post content. With RTC disabled, user A creates a post and saves. User B joins and makes a change that results in an autosave. Later, RTC is enabled for the site. After RTC is enabled, both users (only user B shown in video) can correctly see the autosave notice for the unsaved content.
RTC is disabled
No video here, but when RTC is fully disabled we will still always show the autosave notice when reported by the editor, which is how it currently works.
Testing Instructions
Please see the scenarios above. In general, try some different situations where RTC is enabled and a user fires an
autosaverequest in DevTools. If their changes are represented in the CRDT (available when the page loads), the autosave notice should not be triggered on post load. Otherwise, if content isn't in the CRDT due to solo mode or connection issues, the autosave notice should still be visible.Please give it a try yourself in situations with one and multiple users.
Use of AI Tools
Claude for the whole process.