Skip to content

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

Draft
zehfernandes wants to merge 1 commit into
canaryfrom
cursor/link-theme-color-live-536f
Draft

zehfernandes wants to merge 1 commit into
canaryfrom
cursor/link-theme-color-live-536f

Conversation

@zehfernandes

@zehfernandes zehfernandes commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

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 fix(editor): emit theme link styles in renderHTML #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.

Written for commit 0e73bd0. Summary will update on new commits.

Review in cubic

Co-authored-by: Zeh Fernandes <ozehfernandes@gmail.com>
@changeset-bot

changeset-bot Bot commented Oct 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0e73bd0

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

This PR includes changesets to release 1 package
Name Type
@react-email/editor 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

@vercel

vercel Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated
react-email Ready Ready Preview Oct 6, 2026 4:33pm UTC
react-email-demo Ready Ready Preview Oct 6, 2026 4:33pm UTC

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Oct 6, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 0e73bd0

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 3 files

Confidence score: 4/5

  • In link.tsx, direct or custom Link setups without .node-link can lose the inline theme style when EmailTheming is present, leaving links unthemed. Preserve the inline style for links without that class.
  • Rename optionsHTMLAttributes and HTMLAttributes in renderLinkSpec to follow the acronym naming convention.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/editor/src/extensions/link.tsx">

<violation number="1" location="packages/editor/src/extensions/link.tsx:32">
P3: Custom agent: **Initialisms and Acronyms Naming Conventions**

The new `renderLinkSpec` helper uses `optionsHTMLAttributes` and `HTMLAttributes`, which violate the naming rule's requirement to normalize acronyms. Rename these local identifiers to `optionsHtmlAttributes` and `htmlAttributes`.</violation>

<violation number="2" location="packages/editor/src/extensions/link.tsx:72">
P2: `EmailTheming` presence alone does not guarantee that a link has the `.node-link` class targeted by its CSS. For direct or custom Link setups without that class, this branch removes the inline theme style and leaves links unthemed; only omit the inline style when the rendered attributes include `.node-link`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

renderLinkSpec(
optionsHTMLAttributes,
props.HTMLAttributes,
hasLiveThemeCss ? '' : resolveThemedLinkStyle(props.editor),

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.

P2: EmailTheming presence alone does not guarantee that a link has the .node-link class targeted by its CSS. For direct or custom Link setups without that class, this branch removes the inline theme style and leaves links unthemed; only omit the inline style when the rendered attributes include .node-link.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/editor/src/extensions/link.tsx, line 72:

<comment>`EmailTheming` presence alone does not guarantee that a link has the `.node-link` class targeted by its CSS. For direct or custom Link setups without that class, this branch removes the inline theme style and leaves links unthemed; only omit the inline style when the rendered attributes include `.node-link`.</comment>

<file context>
@@ -26,6 +28,62 @@ function resolveThemedLinkStyle(editor: Editor): string {
+      renderLinkSpec(
+        optionsHTMLAttributes,
+        props.HTMLAttributes,
+        hasLiveThemeCss ? '' : resolveThemedLinkStyle(props.editor),
+      ),
+    );
</file context>

}

function renderLinkSpec(
optionsHTMLAttributes: Record<string, unknown>,

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: Custom agent: Initialisms and Acronyms Naming Conventions

The new renderLinkSpec helper uses optionsHTMLAttributes and HTMLAttributes, which violate the naming rule's requirement to normalize acronyms. Rename these local identifiers to optionsHtmlAttributes and htmlAttributes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/editor/src/extensions/link.tsx, line 32:

<comment>The new `renderLinkSpec` helper uses `optionsHTMLAttributes` and `HTMLAttributes`, which violate the naming rule's requirement to normalize acronyms. Rename these local identifiers to `optionsHtmlAttributes` and `htmlAttributes`.</comment>

<file context>
@@ -26,6 +28,62 @@ function resolveThemedLinkStyle(editor: Editor): string {
 }
 
+function renderLinkSpec(
+  optionsHTMLAttributes: Record<string, unknown>,
+  HTMLAttributes: Record<string, unknown>,
+  themedStyle: string,
</file context>

This branch was successfully deployed

2 active deployments
Preview – react-email-demo — 0e73bd08 Deployed Oct 6, 2026 by vercel[bot]
Preview – react-email — 0e73bd08 Deployed Oct 6, 2026 by vercel[bot]
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