fix(editor): repaint existing links when the theme link style changes

Draft
ZE
10d ago
+178-15

Problem

Changing the theme's link color (for example with setGlobalStyles from the global inspector) left existing links in the editor on the old color until a reload. Exported HTML was already correct, so only the live editor display was wrong. This is the upstream fix for the @react-email/editor pnpm patch in resend/resend-monorepo#11930.

Cause

Since #3418, the Link mark's renderHTML inlines the resolved theme link style into each <a class="node-link">. ProseMirror only re-creates a mark's DOM when the mark's attrs change. A theme change doesn't touch link attrs, so the inlined color goes stale. The EmailTheming plugin already injects the same style as scoped .node-link CSS, and that CSS updates live.

Fix

The Link mark now has a mark view (addMarkView). ProseMirror uses mark views only for the live editor DOM.

  • Live editor: the mark view renders the <a> without the theme style, so the live theme CSS paints it. A link's own style (for example a per-link color) stays inline and still wins. If the theming extension isn't registered, there's no live theme CSS, so the mark view keeps inlining the theme style as before.
  • Serialization: renderHTML still inlines the theme style, so getHTML() and copied HTML keep themed links through Visual → HTML → Visual round trips. That's the contract #3418 added and link.spec.ts covers.
  • Email export: unchanged. It resolves link styles through getNodeStyles and the EmailMark renderer.

Both paths build the <a> with one shared renderLinkSpec helper.

The monorepo patch removed the inline theme style from renderHTML entirely. That would also drop link colors from getHTML(), which is why this PR uses a mark view instead.

Tests

New tests in link.spec.ts mount the editor into the document. They check that:

  • an existing link's computed color follows a setGlobalStyles link color change;
  • a link with its own color keeps it;
  • the live <a> doesn't carry the theme inline style;
  • getHTML() serializes the new theme color.

Without the mark view, the repaint test fails with the stale #0670db.

  • vitest --project unit: 54 files, 524 tests pass.
  • vitest --project browser: 4 files, 19 tests pass.
  • tsc --noEmit and biome check are clean.
  • I also ran a throwaway Chromium test (not committed) covering the theme color change, the per-link color, typing inside a link, unlink and re-link. The live colors were right throughout.
Open in Web Open in Cursor 

Summary by cubic

Fixes the live editor so existing links repaint immediately when the theme link style changes, instead of keeping the old color until a reload.

  • The Link mark now has a mark view (addMarkView) that renders live <a> elements without the inlined theme style, so the EmailTheming plugin's scoped .node-link CSS paints them and updates live; without the theming extension, the mark view falls back to inlining the theme style as before.
  • renderHTML still inlines the theme style, keeping themed links in getHTML() and copied HTML; email export is unchanged. Both paths share a renderLinkSpec helper.
  • New tests in link.spec.ts mount the editor into the document and verify live repaint on setGlobalStyles, per-link color precedence, live DOM output, and getHTML() serialization.