Skip to content

fix: Prevent draft autosave state loss and editor overwriting#283

Open
orrc wants to merge 3 commits into
emdash-cms:mainfrom
orrc:orrc/draft-autosave-fixes
Open

fix: Prevent draft autosave state loss and editor overwriting#283
orrc wants to merge 3 commits into
emdash-cms:mainfrom
orrc:orrc/draft-autosave-fixes

Conversation

@orrc

@orrc orrc commented Apr 5, 2026

Copy link
Copy Markdown

What does this PR do?

Fixes a cluster of related draft/autosave bugs in the admin editor for revision-enabled content.

This started from a verified repro on a published post:

  • Editing "Content" and waiting for autosave appeared to work locally
  • However, the autosave PUT response returned stale content
  • A redundant follow-up GET request also returned stale base-row data
  • Any edits to "Excerpt" were overwritten immediately after autosave
  • After page refresh, the rich text editor appeared to lose the saved draft content even though the draft revision had actually been updated

Root cause turned out to be three separate compounding issues:

  1. packages/core

    • revision-backed autosave wrote the draft correctly, but the PUT response was built from the stale base content row instead of the active draft revision
  2. packages/admin

    • autosave handling in the edit page was not safe for revision-backed content
    • initially this caused a redundant post-autosave GET
    • more importantly, an autosave response could overwrite newer local edits if the user kept typing while the request was in flight
  3. packages/admin

    • PortableTextEditor treated its initial value as mount-only state
    • when parent state changed after mount (for example after draft load, reload, or reset), the live TipTap editor did not reconcile to that new value

What this PR changes:

  • Returns the correct, draft-aware data from autosave responses for revision-backed content.
  • Removes the redundant post-autosave GET call from the client (as the necessary data is in the autosave response).
  • Only applies an autosave response back into edit-page queries when the local editor state still matches the snapshot that was sent.
  • Reconciles post-mount external value changes into the rich-text editor.

Relates to #272, but extends beyond that admin-only reset loop to also fix the stale revision-backed autosave response, the redundant post-autosave refetch, and the in-flight autosave overwrite edge case.

Type of change

  • Bug fix
  • Feature (requires approved Discussion)
  • Refactor (no behavior change)
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm --silent lint:json | jq '.diagnostics | length' returns 0
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...

AI-generated code disclosure

  • This PR includes AI-generated code
    But with a lot of prior verification, including with Playwright, and a lot of discussion on the solution

orrc and others added 3 commits April 6, 2026 00:11
Revision-backed autosaves were updating the draft revision but returning the base content row, so the API response could immediately disagree with the saved draft state.

Reload the current draft revision after a successful save and merge its data and draft slug into the returned item. Add a regression test covering skipRevision updates on a revision-backed collection.

Co-authored-by: Codex <codex@openai.com>
Update the admin autosave path so a PUT response is only applied back into the edit-page queries when the local editor state still matches the snapshot that was sent.

This keeps autosave from triggering an extra GET for the same item, but avoids overwriting newer local edits with a stale response that arrives after the user keeps typing.

Add focused regressions for the query-cache update helper and the ContentEditor snapshot guard.

Co-authored-by: Codex <codex@openai.com>
Reconcile PortableTextEditor with post-mount value updates from parent state, while avoiding unnecessary resets for equivalent content.

Add browser regressions covering external value replacement without onChange noise and preserving clean history for equivalent updates.

Co-authored-by: Codex <codex@openai.com>
@changeset-bot

changeset-bot Bot commented Apr 5, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: c3020e0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

github-actions Bot commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@orrc

orrc commented Apr 5, 2026

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Apr 5, 2026
@github-actions

github-actions Bot commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@ascorbic

Copy link
Copy Markdown
Collaborator

Hey, could you resolve the conflicts and add a changeset?

@pkg-pr-new

pkg-pr-new Bot commented Apr 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@283

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@283

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@283

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@283

emdash

npm i https://pkg.pr.new/emdash@283

create-emdash

npm i https://pkg.pr.new/create-emdash@283

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@283

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@283

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@283

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@283

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@283

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@283

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@283

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@283

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@283

commit: c3020e0

@ascorbic

Copy link
Copy Markdown
Collaborator

Thanks for the thorough work here. Since this was opened, #302 has been merged, which fixes the admin-side form reset (replacing invalidateQueries with setQueryData cache patching). However, this PR contains two fixes that #302 doesn't address and that are still worth landing:

  1. Server-side stale revision response – mergeDraftRevisionIntoItem in emdash-runtime.ts. The autosave PUT for revision-backed content was returning stale base-row data instead of draft revision data.
  2. PortableText reconciliation – TipTap not updating when its value prop changes after mount.

Would you be up for extracting those into one or two smaller PRs? Both would be good to land, but the server-side fix in particular is self-contained and should be straightforward to land on its own.

@github-actions

Copy link
Copy Markdown
Contributor

This PR has been inactive for 14 days. It will be closed automatically in 7 days if there is no further activity.

If you're still working on this, please push an update or leave a comment.

@github-actions github-actions Bot removed the stale label May 27, 2026
@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label May 30, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label May 31, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a well-targeted, sound bug-fix PR. The approach—fixing the server-side autosave response for revision-backed collections, eliminating the redundant post-autosave refetch, guarding against stale-response overwrites with a snapshot comparison, and making the rich-text editor reconcile post-mount value changes—is exactly what the three compounding issues call for.

I traced the full flow in ContentEditPageContentEditorPortableTextEditor and in the runtime handleContentUpdate path. The server-side merge of draft revision data into the response item is done correctly and _rev remains valid because encodeRev only uses version and updatedAt, which are untouched. The client-side snapshot guard (currentDataRef.current === autosaveSnapshot) works because serializeEditorState returns a string and === compares primitive values. The cache update helper applyAutosaveResultToQueryCache updates both the content item and the draft revision query with matching keys, so the merged item memo stays consistent without a full invalidation. TipTap reconciliation uses a stable Portable-Text signature (stripping auto-generated _keys) to avoid false-positive setContent calls, and emitUpdate: false prevents onChange loops.

Tests cover all three fixes. I found no logic bugs, security issues, or regressions in the changed code.

The only remaining issue is a missing changeset. Both packages/core and packages/admin are published packages, and AGENTS.md requires a changeset for any behavior change in a published package. The PR checklist even leaves this box unchecked.

bylines: bodyWithoutRev.bylines,
});

if (result.success && result.data && usesDraftRevisions && result.data.item.draftRevisionId) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[needs fixing] This PR changes the behavior of the published emdash package (the autosave response now returns merged draft-revision data) and of @emdash-cms/admin (new autosave cache-updating logic and editor reconciliation). AGENTS.md requires a changeset for any published-package behavior change:

If your change affects a published package's behavior, add a changeset. Without one, the change won't trigger a package release.

Please add a changeset with pnpm changeset, edit it to cover both emdash (patch) and @emdash-cms/admin (patch), and describe the user-facing fixes.

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels May 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants