Skip to content

fix(editor): prevent deletion of globalContent node on select-all + delete#3347

Open
danilowoz wants to merge 2 commits intocanaryfrom
cursor/protect-global-content-node-6816
Open

fix(editor): prevent deletion of globalContent node on select-all + delete#3347
danilowoz wants to merge 2 commits intocanaryfrom
cursor/protect-global-content-node-6816

Conversation

@danilowoz
Copy link
Copy Markdown
Member

@danilowoz danilowoz commented Apr 17, 2026

Problem

When users press CMD+A (select all) followed by Delete/Backspace, the entire document content is replaced — including the globalContent node that stores the theme configuration (styles, theme name, CSS). This causes the theme to be permanently lost from the document.

Solution

Added a ProseMirror globalContentProtector plugin to the GlobalContent extension, closely following the patterns established by the containerEnforcer plugin in container.tsx:

  1. appendTransaction — After any doc-changing transaction, checks if the globalContent node is still present. If it's missing but existed in the previous state, restores it at position 0 with all original attributes (theme, styles, CSS).
  2. Collaboration-aware no-op guard — Skips doc.eq(oldDoc) transactions from Liveblocks stabilisation rounds to avoid premature restoration before real document content arrives (same heuristic used by containerEnforcer).
  3. view-based restoration — In non-collaborative mode, uses the plugin's view hook to track the last-known globalContent node and restore it on update, matching how containerEnforcer proactively checks on view init/update.
  4. The restoration transaction is marked with addToHistory: false so it doesn't pollute the undo stack.

Changes

  • packages/editor/src/extensions/global-content.ts — Added addProseMirrorPlugins() with the globalContentProtector plugin
  • packages/editor/src/extensions/global-content.spec.ts — New test file with 7 tests covering:
    • Select-all + delete preserves globalContent with theme data
    • Multiple delete cycles preserve data
    • No duplication when globalContent already exists
    • Restoration after replaceWith operations
    • No false restoration when globalContent was never present
    • setGlobalContent command functionality

Slack Thread

Open in Web Open in Cursor 

Summary by cubic

Prevents loss of theme configuration by restoring the globalContent node if it’s removed (e.g., select-all + delete). Works in collab and non-collab sessions without polluting undo history.

  • Bug Fixes
    • Added globalContentProtector to reinsert the node at pos 0 with previous attrs; uses view-based restore in non-collab mode and skips no-op doc.eq(oldDoc) transactions to avoid premature restores with Liveblocks.
    • Restoration uses addToHistory: false; tests cover select-all delete, replace operations, no duplication, and setGlobalContent updates.

Written for commit aba11d8. Summary will update on new commits.

…elete

Add a ProseMirror appendTransaction plugin to the GlobalContent extension
that automatically restores the globalContent node (with all its data,
including theme configuration) when it is removed from the document.

This fixes the issue where CMD+A followed by Delete would remove the
globalContent node, causing the theme to be lost. The plugin detects
when the globalContent node disappears from the document after a
transaction that changed the doc, retrieves the previous node's
attributes from the old state, and re-inserts it at position 0.

The restoration transaction is marked as non-historical so it doesn't
pollute the undo stack.

Co-authored-by: Danilo Woznica <danilowoz@users.noreply.github.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-email Ready Ready Preview, Comment Apr 17, 2026 11:47am
react-email-demo Ready Ready Preview, Comment Apr 17, 2026 11:47am

Request Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 17, 2026

⚠️ No Changeset found

Latest commit: aba11d8

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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 17, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@react-email/editor@3347

commit: aba11d8

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files

Confidence score: 5/5

  • This looks low risk to merge: the only finding is low severity (3/10) and appears limited to test behavior rather than production logic.
  • In packages/editor/src/extensions/global-content.spec.ts, using TextSelection instead of the real select-all path may miss the leaf-block edge case, so the test may not fully validate the targeted bug scenario.
  • Pay close attention to packages/editor/src/extensions/global-content.spec.ts - ensure the spec exercises the true select-all flow so the leaf-block edge case is covered.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/editor/src/extensions/global-content.spec.ts">

<violation number="1" location="packages/editor/src/extensions/global-content.spec.ts:46">
P3: Use the real select-all path here; `TextSelection` can miss the leaf-block edge case this bug targets.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

});

const { state } = ed;
const allSelection = TextSelection.create(
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.

P3: Use the real select-all path here; TextSelection can miss the leaf-block edge case this bug targets.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/editor/src/extensions/global-content.spec.ts, line 46:

<comment>Use the real select-all path here; `TextSelection` can miss the leaf-block edge case this bug targets.</comment>

<file context>
@@ -0,0 +1,237 @@
+    });
+
+    const { state } = ed;
+    const allSelection = TextSelection.create(
+      state.doc,
+      0,
</file context>

@danilowoz danilowoz marked this pull request as ready for review April 17, 2026 09:36
… patterns

Adopt two patterns from the container enforcer plugin:

1. Collaboration-aware no-op guard: skip doc.eq(oldDoc) transactions
   from Liveblocks stabilisation rounds to avoid restoring the node
   before the real document arrives.

2. View-based restoration: in non-collaborative mode, use the plugin's
   view hook to track the last-known globalContent node and restore it
   on update, matching how containerEnforcer proactively checks on
   view init/update.

Co-authored-by: Danilo Woznica <danilowoz@users.noreply.github.com>
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.

2 participants