Skip to content

MM-70092: Preserve drafts when a page is soft-deleted - #14

Open
catalintomai wants to merge 2 commits into
masterfrom
MM-70092-page-delete-drafts
Open

MM-70092: Preserve drafts when a page is soft-deleted#14
catalintomai wants to merge 2 commits into
masterfrom
MM-70092-page-delete-drafts

Conversation

@catalintomai

@catalintomai catalintomai commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes MM-70092.

Problem

Deleting a page soft-deletes the page (recoverable via RestorePage) but hard-deleted every user's drafts on that page as a side effect. deleteDraftsForPage scoped its DELETE by PageId and SpaceId — but not by owner — so any space member with delete rights permanently destroyed other collaborators' unpublished work, with no confirmation and no recovery even after restoring the page.

This also made DeletePage inconsistent with DeleteSpace, which already left drafts untouched precisely because that soft-delete is reversible.

Fix

Remove deleteDraftsForPage entirely and let the pre-existing read-side applyDraftLivenessFilter hide drafts whose page is deleted. A page delete's blast radius now matches its reversibility: drafts are hidden while the page is deleted and reappear intact on restore.

The production change is a net deletion. No schema change, no migration, no new filter — the read path already had the exact predicate needed (p.DeleteAt = 0 AND p.OriginalId = '' AND p.SpaceId = d.SpaceId), applied by all five draft read paths.

reparentDraftsForPage is unchanged and still runs: a pending child draft (ParentId = the deleted page) is still reparented to the deleted page's parent rather than left dangling. The two cascades are disjoint — one matches on ParentId, the other on PageId.

Behavior changes

  • A draft on a soft-deleted page is hidden from all reads, not destroyed, and returns on restore.
  • Publishing a draft after a delete→restore round trip now takes the conflict path: DeletePage and RestorePage each bump the page's EditAt, so the draft's write-once BaseEditAt is two bumps stale. A non-force publish returns 409 app.page_draft.publish.edit_conflict.app_error; force=true publishes.
  • A hidden draft still counts against MaxDraftsPerUserPerSpace. This is deliberate — it bounds total row growth — but see follow-ups.

Testing

Full ./server/... suite passes. New and updated coverage:

  • TestDeletePage (store) — drafts hidden across two users while deleted, then field-for-field identical after restore, including UpdateAt (the CAS token DeleteDraftVersion and deletePublishedDraftTx key off).
  • TestDeletePagePreservesEditDraftAndReparentsChildDraft — both cascades in one delete, verified isolated.
  • TestUpsertDraftQuotaCountsDraftsHiddenByPageDelete — pins the quota consequence above.
  • TestRestoreSpaceLeavesIndividuallyDeletedPageDraftHiddenRestoreSpace's stamp-scoped un-cascade carried through to draft visibility.
  • TestDiscardPathsReachDraftHiddenByPageDelete — the discard paths are not liveness-gated, so a hidden draft is still discardable.
  • TestPublishAfterPageDeleteRestoreConflictsThenForceSucceeds (app) — the 409-then-force sequence above.
  • TestDraft — a draft on a live page hidden by its space's soft-delete (exercises the space-liveness JOIN rather than the p.Id IS NULL branch).

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Page deletion no longer hard-deletes drafts. Liveness filtering hides drafts during soft deletion. Restoration reveals eligible drafts and preserves their fields. Publishing can detect stale baselines or force publication.

Changes

Draft lifecycle preservation

Layer / File(s) Summary
Preserve drafts during deletion
server/store/draft_store.go, server/store/page_store.go, server/store/space_store.go, server/store/migrations/...
Page and space deletion preserve drafts and apply liveness filtering. Page deletion still reparents pending child drafts.
Validate visibility and deletion paths
server/store/draft_store_test.go, server/store/store_test.go
Tests cover hidden drafts, restoration, quotas, reparenting, CAS behaviour, explicit deletion, and preserved draft fields.
Handle publishing after restoration
server/app/page_draft_test.go
Publishing with a stale baseline returns an edit conflict and retains the draft. Force publishing succeeds after restoration.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: 2: Dev Review

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: preserving drafts when a page is soft-deleted.
Description check ✅ Passed The description directly explains the problem, fix, behaviour changes, and tests for preserving drafts after page deletion.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch MM-70092-page-delete-drafts

Comment @coderabbitai help to get the list of available commands.

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