I've had an interaction with a Gutenberg user that experienced content loss while using the editor. Here are the potential steps to reproduce the issue.
At this point, the content loss happens. It's not really clear what caused it. The notice might have been dismissed, switching to writing other posts/tabs could be a factor too.
Expected Result
What I managed to reproduce:
I created a very long post and ran wp-env stop. The notice is there regardless of the scroll position, although it is dismissable:

I tried refreshing the page, there was another notice:


The autosave worked and the content is present in window.sessionStorage under some ID. The issue is that WordPress creates a new draft post every time the post editor is opened, which means a new ID, which means the autosave code uses a different cache key after refreshing the page:

Post 375 is not available for me from wp-admin, but If I manually navigate to post.php?post=375&action=edit then I am notified about backup being available:

Clicking restore brings my post back:

It seems like sessionStorage autosave should take wasSaved flag into account and then offer to restore a backup even if the post id doesn't match.
cc @mcsf
Good debugging, thanks @adamziel It does seem like a single auto-draft local save is the way to go regardless of the id.
That said, I'm not sure if it's the only issue because unless I'm mistaken the "auto-save" triggers after a minute which means it's unlikely that the long post is still an auto-draft when the error happens.
@youknowriad I kept digging and it also seems like the AutosaveMonitor isn't overly reliable:
If I'm constantly typing, this fragment will kick in every now and then and reschedule the autosave +1 minute each time it runs. I also noticed it tends to be non-deterministic: there are times where I keep typing and the initial autosave kicks in after the first minute, and there are times where I keep typing and no autosave would kick in for 10 or more minutes.
I noticed the debounce mode was added in this PR by @aduth and @mcsf:
https://github.com/WordPress/gutenberg/pull/16490
Is there any specific reason we need that debounce? I can't think of a use case where I'd like to delay the autosave indefinitely as long as I keep typing - quite the opposite, I'd like it to preserve as much of my typing as possible. I would get rid of it and just let it run every interval seconds. cc @gziolo who reviewed that other PR
@adamziel I think at the time the debounce was added, the threshold was 10 seconds and not one minute and running auto-saves each 10 seconds is too much and it can lead to a high number of revisions which is bad for WP mysql performance I think.
One option could be to have two timeouts, a debounced one (1 minute) and non-debounced one (like 10mn or so).
Any thoughts @mtias @mcsf
It would also be good to display the "you are offline" notice as soon as either an autosave OR a heartbeat fails, currently it only shows in response to an autosave failure.
@youknowriad What is the approach to the post history? Is it supposed to be immutable? I wonder if it would make sense to have a non-debounced 1-minute autosave that would create a new revision once every 10 runs and amend the last autosaved revision the other 9 times.
Great debugging, @adamziel!
What is the approach to the post history? Is it supposed to be immutable? I wonder if it would make sense to have a non-debounced 1-minute autosave that would create a new revision once every 10 runs and amend the last autosaved revision the other 9 times.
I'm speaking from memory and intuition, but I believe that traditionally consecutive autosaves are squashed together, while explicit user-triggered saves always create a new revision.
So a flow of
save draft | autosave | autosave | autosave | save draft | autosave
will result in the revision history:
rev | rev (autosave) | rev | rev (autosave)
One option could be to have two timeouts, a debounced one (1 minute) and non-debounced one (like 10mn or so).
This might work. We should also look to make sure that the debounce fuse is good, i.e. should we shorten the idle time required to force an autosave?
Would it help to consider the first (server-side) autosave more important than the following ones? The first autosave is crucial to obtain a reliable post ID with which to save/restore saves. So maybe we aggressively autosave a draft after one minute even if the editor is not idle. From that point, we know that we'll have sessionStorage autosaves at the very least. WDYT?
Would it help to consider the first (server-side) autosave more important than the following ones? The first autosave is crucial to obtain a reliable post ID with which to save/restore saves. So maybe we aggressively autosave a draft after one minute even if the editor is not idle. From that point, we know that we'll have sessionStorage autosaves at the very least. WDYT?
https://github.com/WordPress/gutenberg/pull/23928 would solve the sessionStorage issue for posts without a "reliable" post ID.
I'm speaking from memory and intuition, but I believe that traditionally consecutive autosaves are squashed together, while explicit user-triggered saves always create a new revision.
In that case, what's the advantage of debounced autosave over the "always run every 60 seconds" model? It seems like the number of revisions would be pretty reasonable in both cases.
In that case, what's the advantage of debounced autosave over the "always run every 60 seconds" model? It seems like the number of revisions would be pretty reasonable in both cases.
Yes, I agree. The poor answer is that the debouncing mechanism was already around before I touched Autosave. I'm tempted to say that there's currently no good reason to keep it that way, but of course I may be missing some history. Ultimately, I think it's worth reimplementing.
23928 would solve the sessionStorage issue for posts without a "reliable" post ID.
I think it's a fine start, but it's worth pointing out that it breaks down as soon as a user has two new drafts sitting side by side. So we're left with a couple of options:
auto-draft backups in session storage, and afterwards presenting the user with a UI allowing them to recover the right auto-draft.Apart from the improvements to the logic, this seems like a case of poor UI where we could just surface any local-drafts whenever you start a new post, with the ability to discard them. This would mean that even if the connection between the IDs gets lost, a user would still be able to browse local copies and resurrect a text.
Apart from the improvements to the logic, this seems like a case of poor UI where we could just surface any local-drafts whenever you start a new post, with the ability to discard them. This would mean that even if the connection between the IDs gets lost, a user would still be able to browse local copies and resurrect a text.
This could use some design input so I created a new issue here https://github.com/WordPress/gutenberg/issues/23955
Early progress on AutosaveMonitor refactor: https://github.com/WordPress/gutenberg/pull/23962
Documentation for the AutosaveMonitor proposed in https://github.com/WordPress/gutenberg/pull/26663
Closing this one since https://github.com/WordPress/gutenberg/issues/23955 is a separate issue and I haven't seen any more reports of the problem described here.
Most helpful comment
Early progress on AutosaveMonitor refactor: https://github.com/WordPress/gutenberg/pull/23962