Skip to content

feat(draft): prevent conflicting autosave overwrites - #2776

Merged
Innei merged 2 commits into
masterfrom
agent/draft-conflict-merge
Jul 28, 2026
Merged

feat(draft): prevent conflicting autosave overwrites#2776
Innei merged 2 commits into
masterfrom
agent/draft-conflict-merge

Conversation

@Innei

@Innei Innei commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

  • add optimistic concurrency control to draft updates with expectedVersion and a database compare-and-swap update
  • return a structured 409 DRAFT_VERSION_CONFLICT response instead of silently overwriting a newer draft
  • perform client-side three-way merging for independent fields, Markdown text, metadata, and Lexical blocks with stable IDs
  • pause autosave and publishing for overlapping changes, with explicit actions to use the server version or keep the current content
  • enforce a single draft per non-null content reference with a preflighted partial unique index
  • keep loaded draft baselines accurate and cancel pending autosave timers when saving manually

Root cause

Draft updates previously used unconditional last-write-wins semantics. A browser retained no server revision in its save request, so a stale tab could overwrite a newer draft without either client observing a conflict.

Impact

Editors opened in multiple browsers now preserve non-overlapping changes automatically. Overlapping edits remain local and require an explicit resolution before autosave or publication can continue.

Validation

  • pnpm -C apps/admin exec vitest run src/api/http.test.ts src/features/write/utils/merge-draft-conflict.test.ts — 10 tests passed
  • pnpm -C apps/core exec vitest run test/src/modules/draft/draft.service.spec.ts — 9 tests passed
  • pnpm -C apps/admin typecheck
  • targeted ESLint and git diff --check
  • Drizzle schema and migration checks
  • two-browser E2E coverage for structured 409 responses, automatic non-overlapping merge, both conflict-resolution actions, publish blocking, draft baseline restoration, and duplicate-autosave prevention

@Innei
Innei marked this pull request as ready for review July 28, 2026 08:46
@safedep

safedep Bot commented Jul 28, 2026

Copy link
Copy Markdown

SafeDep Report Summary

Green Malicious Packages Badge Green Vulnerable Packages Badge Green Risky License Badge

No dependency changes detected. Nothing to scan.

View complete scan results →

This report is generated by SafeDep Github App

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2621cff19

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +43 to +44
if (existing) {
throw createAppException(AppErrorCode.DRAFT_VERSION_CONFLICT, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid treating retained published drafts as concurrent creates

When editing a document that was previously published from a draft, markAsPublished() retains that draft row while findByRef() hides it whenever publishedVersion === version. The admin therefore receives no baseline, attempts createDraft() on its first autosave, and this branch reports a version conflict against the retained row. Consequently, every normal editing session for such a document presents a false conflict and requires the user to choose between discarding or forcibly keeping their changes even though no concurrent writer exists; distinguish an unchanged published draft from an actual competing create or expose it as the initial baseline.

Useful? React with 👍 / 👎.

@Innei
Innei merged commit cd00d35 into master Jul 28, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant