Skip to content

Refactored email editor into a standalone component#26796

Merged
9larsons merged 3 commits intomainfrom
email-editor-component
Mar 12, 2026
Merged

Refactored email editor into a standalone component#26796
9larsons merged 3 commits intomainfrom
email-editor-component

Conversation

@9larsons
Copy link
Copy Markdown
Contributor

@9larsons 9larsons requested a review from EvanHahn March 12, 2026 13:27
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1168ea05-feb4-4e29-8603-087a29b8a2c5

📥 Commits

Reviewing files that changed from the base of the PR and between c48031b and 4dab860.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • apps/admin/package.json
  • ghost/admin/package.json

Walkthrough

loadKoenig was changed to a named export in koenig-editor-base and re-exported from the design-system index. MemberEmailsEditor was refactored to use a lazy-loaded EmailEditorInner driven by an editor resource from loadKoenig, wrapped with Suspense and ErrorBoundary. The singleParagraph prop was removed from MemberEmailsEditorProps. WelcomeEmailModal now passes editor value from automatedEmail.lexical with a fallback to an empty string. Dependency @tryghost/koenig-lexical was bumped from 1.7.20 to 1.7.21.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: refactoring the email editor into a standalone component, which aligns with the significant restructuring of member-email-editor.tsx and the introduction of lazy-loaded EmailEditorInner composition.
Description check ✅ Passed The description provides references to related issues and pull requests in external systems, which are relevant to understanding the context and intent of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch email-editor-component
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@EvanHahn EvanHahn left a comment

Choose a reason for hiding this comment

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

Clean!

if (stringified !== value) {
onChange(stringified);
}
onChange(JSON.stringify(data));
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.

I think this is fine, but why did it change?

// Keep settings panel copy compact
'[&_[data-kg-settings-panel]_p]:!mb-0',
// Nested-editor (callout, etc.) fixes: align placeholder with text
// 1. Override placeholder font/size/line-height to match the <p> styles
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.

nit: why are these comments removed?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsx (1)

1-2: Avoid adding new admin-x-design-system UI here.

This refactor introduces LoadingIndicator from @tryghost/admin-x-design-system into an apps/admin-x-settings component. Please keep the new fallback in shade or a local primitive so this change does not expand usage of the phased-out package.

As per coding guidelines, "Use shade design system for new components in Admin UI. Avoid admin-x-design-system as it is being phased out."

Also applies to: 143-145

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsx`
around lines 1 - 2, The code imports and uses LoadingIndicator from
`@tryghost/admin-x-design-system` (symbols: LoadingIndicator, useDesignSystem)
which must be avoided; replace any usage of LoadingIndicator used as the
Suspense fallback (and any other UI from admin-x-design-system introduced here)
with a shade design-system primitive or a local lightweight spinner component
(e.g., a Shade Spinner or a local LoadingFallback) and remove the
LoadingIndicator import; ensure ErrorBoundary and Koenig-related imports remain
unchanged but update the Suspense fallback to reference the new shade/local
component instead of LoadingIndicator.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsx`:
- Around line 95-96: The component currently captures the incoming prop via
const initialEditorState = useRef(value) (and similarly at line ~150), which
makes the editor ignore later changes to value/formState.lexical; either rename
the prop to initialValue to signal it’s intentionally one-time, or keep the prop
name and update the editor when value changes by replacing the useRef pattern
with state/effect logic: use a state (or ref-backed setter) to hold the editor
value and add a useEffect that watches value (and/or formState.lexical) and
reinitializes or updates the editor state when value changes; update references
to initialEditorState and ensure the key-based remounting
(key={automatedEmail?.id || 'new'}) remains consistent with this behavior.

---

Nitpick comments:
In
`@apps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsx`:
- Around line 1-2: The code imports and uses LoadingIndicator from
`@tryghost/admin-x-design-system` (symbols: LoadingIndicator, useDesignSystem)
which must be avoided; replace any usage of LoadingIndicator used as the
Suspense fallback (and any other UI from admin-x-design-system introduced here)
with a shade design-system primitive or a local lightweight spinner component
(e.g., a Shade Spinner or a local LoadingFallback) and remove the
LoadingIndicator import; ensure ErrorBoundary and Koenig-related imports remain
unchanged but update the Suspense fallback to reference the new shade/local
component instead of LoadingIndicator.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: acf7b332-119b-4a7e-b792-3ee0040df499

📥 Commits

Reviewing files that changed from the base of the PR and between 96ae83b and 84117fb.

📒 Files selected for processing (4)
  • apps/admin-x-design-system/src/global/form/koenig-editor-base.tsx
  • apps/admin-x-design-system/src/index.ts
  • apps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsx
  • apps/admin-x-settings/src/components/settings/membership/member-emails/welcome-email-modal.tsx

Comment on lines +95 to +96
// Capture the initial value once — the editor owns its own state after mount
const initialEditorState = useRef(value);
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether current callers treat MemberEmailEditor as a controlled component.
# Expected result: if callers pass a mutable `value` without forcing a remount,
# the current implementation can go stale.

rg -n -C4 '<MemberEmailEditor\b|value=\{|key=\{' --type tsx

Repository: TryGhost/Ghost

Length of output: 85


🏁 Script executed:

#!/bin/bash
# Fix: use correct file type flag for rg
# Search for MemberEmailEditor usages to verify if it's treated as a controlled component

echo "=== Searching for MemberEmailEditor component usages ==="
rg 'MemberEmailEditor' -t ts -C 3

echo ""
echo "=== Checking for value prop assignments ==="
rg 'value\s*=\s*\{|value\s*=' -t ts -C 2 | grep -A 5 -B 5 MemberEmailEditor

echo ""
echo "=== Checking design system imports in the target file ==="
head -20 apps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsx

Repository: TryGhost/Ghost

Length of output: 4255


🏁 Script executed:

#!/bin/bash
# Get actual value prop usage in the caller
echo "=== Value prop usage in welcome-email-modal.tsx ==="
rg -n 'value=|value\s*:\s*' apps/admin-x-settings/src/components/settings/membership/member-emails/welcome-email-modal.tsx -A 2 -B 2

echo ""
echo "=== Full context around MemberEmailEditor instantiation ==="
rg -n '<MemberEmailEditor' apps/admin-x-settings/src/components/settings/membership/member-emails/welcome-email-modal.tsx -A 10 -B 2

echo ""
echo "=== LoadingIndicator usage in member-email-editor.tsx ==="
rg -n 'LoadingIndicator' apps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsx -B 2 -A 2

echo ""
echo "=== Lines 95-96 and surrounding context in member-email-editor.tsx ==="
sed -n '90,155p' apps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsx

echo ""
echo "=== Check for other MemberEmailEditor callers ==="
rg 'MemberEmailEditor' -t ts --type tsx 2>/dev/null | grep -v 'welcome-email-modal\|member-email-editor\.tsx:import\|export' | head -20

Repository: TryGhost/Ghost

Length of output: 6351


value is now only an initial snapshot; caller mutations won't update the editor.

These lines capture the prop value once via useRef, ignoring any subsequent value changes from the caller. While the key-based remounting (key={automatedEmail?.id || 'new'}) resets the component when the email ID changes, it leaves the editor stale if formState.lexical mutates without an ID change. The prop interface suggests a controlled component, but the implementation is uncontrolled for value updates.

Rename this parameter to initialValue or subscribe to value changes and reinitialize the editor when needed.

Also applies to: 150

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsx`
around lines 95 - 96, The component currently captures the incoming prop via
const initialEditorState = useRef(value) (and similarly at line ~150), which
makes the editor ignore later changes to value/formState.lexical; either rename
the prop to initialValue to signal it’s intentionally one-time, or keep the prop
name and update the editor when value changes by replacing the useRef pattern
with state/effect logic: use a state (or ref-backed setter) to hold the editor
value and add a useEffect that watches value (and/or formState.lexical) and
reinitializes or updates the editor state when value changes; update references
to initialEditorState and ensure the key-based remounting
(key={automatedEmail?.id || 'new'}) remains consistent with this behavior.

@github-actions
Copy link
Copy Markdown
Contributor

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 23004296265 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@ErisDS
Copy link
Copy Markdown
Member

ErisDS commented Mar 12, 2026

🤖 Velo CI Failure Analysis

Classification: 🟠 SOFT FAIL

  • Workflow: CI
  • Failed Step: Run yarn nx run @tryghost/admin-x-settings:test:acceptance
  • Run: View failed run
    What failed: CI failure - likely code issue
    Why: The failure appears to be related to code changes. Check the error output for details.
    Action:
    Review the error logs and fix the issue in your code.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsx (1)

1-2: Consider migrating away from admin-x-design-system imports.

This file imports ErrorBoundary, LoadingIndicator, loadKoenig, and useDesignSystem from @tryghost/admin-x-design-system. The coding guidelines indicate this package is being phased out in favor of @tryghost/shade. While this may be acceptable for now given these utilities may not yet have shade equivalents, consider tracking migration for future work.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsx`
around lines 1 - 2, This import block currently pulls ErrorBoundary,
LoadingIndicator, loadKoenig, and useDesignSystem from
`@tryghost/admin-x-design-system`; update the module usage by replacing these
imports with equivalents from `@tryghost/shade` where available (or create a TODO
comment to track migration if equivalents don't yet exist) — specifically check
for and switch ErrorBoundary, LoadingIndicator, loadKoenig, and useDesignSystem
usages in MemberEmailEditor (member-email-editor.tsx) to their `@tryghost/shade`
counterparts, or add a clear TODO and centralized issue reference in the file to
track migrating these symbols off admin-x-design-system.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsx`:
- Around line 1-2: This import block currently pulls ErrorBoundary,
LoadingIndicator, loadKoenig, and useDesignSystem from
`@tryghost/admin-x-design-system`; update the module usage by replacing these
imports with equivalents from `@tryghost/shade` where available (or create a TODO
comment to track migration if equivalents don't yet exist) — specifically check
for and switch ErrorBoundary, LoadingIndicator, loadKoenig, and useDesignSystem
usages in MemberEmailEditor (member-email-editor.tsx) to their `@tryghost/shade`
counterparts, or add a clear TODO and centralized issue reference in the file to
track migrating these symbols off admin-x-design-system.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6914e45a-1771-4ac3-a9b2-ca8545c669e6

📥 Commits

Reviewing files that changed from the base of the PR and between 84117fb and c48031b.

📒 Files selected for processing (2)
  • apps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsx
  • apps/admin-x-settings/src/components/settings/membership/member-emails/welcome-email-modal.tsx

@github-actions
Copy link
Copy Markdown
Contributor

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 23006020400 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.19%. Comparing base (3ad6d52) to head (4dab860).
⚠️ Report is 17 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #26796      +/-   ##
==========================================
- Coverage   73.19%   73.19%   -0.01%     
==========================================
  Files        1534     1534              
  Lines      121048   121065      +17     
  Branches    14636    14643       +7     
==========================================
+ Hits        88601    88612      +11     
- Misses      31415    31421       +6     
  Partials     1032     1032              
Flag Coverage Δ
admin-tests 54.31% <ø> (ø)
e2e-tests 73.19% <ø> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ErisDS
Copy link
Copy Markdown
Member

ErisDS commented Mar 12, 2026

🤖 Velo CI Failure Analysis

Classification: 🔴 HARD FAIL

  • Workflow: CI
  • Failed Step: Prepare E2E CI job
  • Run: View failed run
    What failed: Operation timed out
    Why: Infrastructure issue detected: operation timed out. This is typically not caused by code changes.
    Action:
    Try re-running the workflow. If it persists, investigate the infrastructure.

@9larsons 9larsons enabled auto-merge (squash) March 12, 2026 21:52
@9larsons 9larsons merged commit 225c979 into main Mar 12, 2026
56 of 61 checks passed
@9larsons 9larsons deleted the email-editor-component branch March 12, 2026 22:00
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.

3 participants