Skip to content

fix(admin): stop the editor saving over the newer version during a save conflict - #3118

Merged
ascorbic merged 3 commits into
emdash-cms:mainfrom
danielmlr:fix/publish-actions-save-conflict
Sep 21, 2026
Merged

ascorbic merged 3 commits into
emdash-cms:mainfrom
danielmlr:fix/publish-actions-save-conflict

Conversation

@danielmlr

@danielmlr danielmlr commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Prevents the content editor from saving over another writer's newer version while the conflict notice is up.

Conflict recovery (#2902) fetches the current revision token so Save anyway can write, and pauses only autosave. Publishing, scheduling, removing a schedule, unpublishing and date changes (#2972) save pending edits first, so they wrote over the other version with that token, and publishing put that copy live. A save already queued behind the refused one did the same.

While the notice is up the publishing controls are disabled, open menus included; a date or schedule change is refused in its dialog; a save queued before the conflict no longer runs after it; an author or SEO save no longer clears the notice; and a read no longer replaces what the writer typed. Save anyway, discarding, and everything outside a conflict are unchanged. An author or SEO save still writes with the token recovered from the conflict. Withholding that token from those saves would change #2902's recovery, so it is left for a separate change.

Type of change

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

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added and reviewed the user-facing changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/... (n/a: bug fix)
  • I have included screenshots below if this PR changes the UI

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 5

Screenshots / test output

The notice in the content editor after another writer saved the same entry. Publish changes is disabled until the writer decides.

Content editor showing the notice that the entry changed somewhere else after it was opened, with Save anyway in the notice and Publish changes disabled in the sidebar

A publication date change during the conflict is refused in its dialog, and nothing is written.

Change publication date dialog during the conflict, showing the error: This entry changed somewhere else. Save anyway, or reload to get the newer version.

The same refusal in the Arabic locale. The dialog follows the right-to-left direction, and the new message stays English until the catalogs are translated.

The Change publication date dialog in the Arabic admin, laid out right to left, showing the same error in English

Nine cases join publish-autosave-race.test.tsx. Against the unmodified source the first eight fail, and the ninth, which publishes after Save anyway, passes on both:

 × refuses a publication date change and keeps the other writer's version
 × disables the publishing menu until the writer decides
 × disables unpublishing until the writer decides
 × does not publish from a menu that was open when the conflict arrived
 × does not publish when the conflict arrives while the publish waits for autosave
 × does not save a submission that waited for the autosave the conflict refused
 × keeps the conflict when the author changes, so a later edit cannot overwrite
 × refuses a schedule from a dialog that was open when the conflict arrived
 ✓ publishes again once the writer saves anyway

With the change, packages/admin is green:

 Test Files  173 passed (173)
      Tests  2353 passed (2353)

In demos/simple against the real server, Save date and Publish wrote over the other version before the change and do not after it.

…ve conflict

Recovering from a save conflict refreshes the entry's revision token so that
"Save anyway" can write, and the editor holds autosave back while the conflict
notice is up. Every other write could still use that token. Publishing,
scheduling, unscheduling, unpublishing and a publication date change save the
pending edits first without checking the conflict, and a save that was queued
behind the refused one ran after recovery. Each wrote the editor's copy over the
other version, and publishing then put that copy live.

These actions now wait for the writer's decision. The publishing controls are
disabled while the notice is up, including items in a menu that was already
open, and a publication date change is refused with a message in its dialog.
While a conflict is recorded, the save queue sends only a save the writer starts
during it, so a save that was already waiting when the conflict arrived is not
sent. "Save anyway" and discarding clear the conflict as before, and so does a
successful author or SEO save.
@changeset-bot

changeset-bot Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c2faa00

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@emdash-cms/admin Patch
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/plugin-test Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
create-emdash Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

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

@pkg-pr-new

pkg-pr-new Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/auth-atproto

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

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

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

emdash

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

create-emdash

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

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

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

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-cli@3118

@emdash-cms/plugin-test

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-test@3118

@emdash-cms/plugin-types

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-types@3118

@emdash-cms/registry-client

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-client@3118

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-lexicons@3118

@emdash-cms/registry-loader

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-loader@3118

@emdash-cms/registry-moderation

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-moderation@3118

@emdash-cms/registry-verification

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-verification@3118

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/sandbox-workerd@3118

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-field-kit@3118

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: c2faa00

@danielmlr
danielmlr marked this pull request as ready for review September 15, 2026 12:05
@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label Sep 15, 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 the right change, solving the right problem in a way that fits EmDash. The bug — publishing/schedule/unpublish/date-change paths flushing pending edits and writing over a newer version while a save conflict is showing — is real and serious (it can publish stale content). The approach is sound: keep the existing serialize-and-flush behavior, but gate the actions on a conflict ref that is readable before the next render, and drop queued saves that would run after the conflict unless the user explicitly chooses Save anyway. The UI is disabled consistently across the sidebar, mobile header, and distraction-free overlay, including already-open menu items.

I read the diff and full changed files, traced the call sites for PublishActions / SettingsActionBar, checked the PublicationDateDialog error handling, and verified the recovery ref wiring in router.tsx. The conflict guard is applied in the right places (handlePublish, runScheduleChange, and serializeEditorSave), and the explicitSave flag correctly lets Save anyway through. New UI strings are Lingui-wrapped, the Tailwind layout is unchanged, and the changeset accurately describes the user-facing fix for @emdash-cms/admin.

No blockers. The code is clean.

@github-actions

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.

@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-review No maintainer or bot review yet labels Sep 15, 2026
…hange

The notice was cleared by any successful save, including the author and
SEO writes the settings panel sends. Those carry the token recovered from
the conflict, so the server accepts them, and once the notice was gone the
editor's autosave was free to send the writer's older copy with a token the
server also accepts, overwriting the other version with no further warning.

Only a save the writer started clears it now. An author or SEO change still
saves, so nothing the writer typed is dropped, but the entry stays in
conflict until they choose.
@github-actions github-actions Bot added review/needs-rereview New commits since the last review and removed review/approved Approved; no new commits since labels Sep 20, 2026
…ice is up

An author or SEO save during a conflict invalidates the entry, and the read
that follows wrote the newer version into the form, so the copy the notice
promises is still there was gone from the editor while the notice still said
it was there. A read that lands during a conflict no longer replaces the form.

The schedule dialog is the one publishing surface a writer can still reach
once the conflict arrives, because the menu item behind it is disabled only
after it is open, so it gets the case the publication date dialog already had.
@danielmlr danielmlr added the bot:review Trigger an emdashbot code review on this PR label Sep 20, 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 re-review of a tightly scoped bug fix, and the change still looks correct and complete.

The PR addresses a real, serious problem: while the content editor is showing a save-conflict notice, publishing/schedule/unpublish/date-change paths were flushing pending edits first and writing over the newer version, and a save already queued behind a refused autosave could do the same. The fix gates those actions on a conflict ref that is writable before the next render, drops queued saves that would run after the conflict unless the user explicitly chooses Save anyway, and disables the publishing controls so the existing UI cannot be driven into the bad state.

I re-read the full diff and the changed source files, checked the call sites for PublishActions and SettingsActionBar, traced the serializeEditorSave queue logic in router.tsx, and verified the schedule/date-dialog refusal path. The wiring is consistent across the sidebar, mobile header, and distraction-free overlay, including already-open menus. New UI strings are Lingui-wrapped, no raw Tailwind directional classes were introduced, and the patch changeset accurately describes the user-facing fix for @emdash-cms/admin. The new test cases specifically exercise the race and refusal behaviors rather than asserting implementation details back at themselves.

No blockers. The code is clean.

@emdashbot emdashbot Bot added review/approved Approved; no new commits since and removed bot:review Trigger an emdashbot code review on this PR review/needs-rereview New commits since the last review labels Sep 20, 2026
@ascorbic
ascorbic merged commit 667f62e into emdash-cms:main Sep 21, 2026
69 checks passed
@danielmlr
danielmlr deleted the fix/publish-actions-save-conflict branch September 21, 2026 13:01
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