Skip to content

🐛 Fixed link selection in automations email editor#29176

Merged
EvanHahn merged 2 commits into
mainfrom
NY-1394_atmns-editor-links
Jul 9, 2026
Merged

🐛 Fixed link selection in automations email editor#29176
EvanHahn merged 2 commits into
mainfrom
NY-1394_atmns-editor-links

Conversation

@troyciesco

@troyciesco troyciesco commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

closes https://linear.app/ghost/issue/NY-1394/

  • Koenig portals its link input/toolbar to document.body, but the automations email modal's Radix dialog is modal, so its focus trap couldn't contain those portals — clicks and focus never reliably reached them
  • attempted a Koenig-side fix (render portals into a container inside the dialog instead), but that surfaced a new focus fight between Radix's trap and Lexical's own focus handling
  • fixed in Ghost instead: the dialog is non-modal, and the rest of the admin UI is made inert while it's open, so nothing outside the editor can steal focus/clicks and Koenig's body-level portals work fine
  • inert is also more robust for accessibility than a JS focus trap — it's natively enforced by the browser, so background content is reliably unreachable by keyboard and screen readers regardless of where Koenig portals its UI

before - link input didn't work, and escape would close the entire editor instead of the link input/list:
Screenshot 2026-07-08 at 10 22 01 AM

after - input works, scroll works, escape works, other koenig cards still work. it's hard to demo but there was also an issue with my original approach where if i just set modal={false}, then tab could potentially move your control to the body (behind the editor). that's the main reason for this larger fix:
link-fix-after

@nx-cloud

nx-cloud Bot commented Jul 8, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit e250faa

Command Status Duration Result
nx run-many --target=build --projects=tag:publi... ✅ Succeeded <1s View ↗
nx run-many -t test:unit -p @tryghost/posts,@tr... ✅ Succeeded 2m 20s View ↗
nx run @tryghost/admin:build ✅ Succeeded 57s View ↗
nx run-many -t lint -p @tryghost/posts,@tryghos... ✅ Succeeded 39s View ↗
nx run ghost:build:assets ✅ Succeeded 2s View ↗
nx run ghost:build:tsc ✅ Succeeded 8s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-07-09 12:27:49 UTC

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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: bc861c49-f4a6-46f3-b3ef-449bd560981b

📥 Commits

Reviewing files that changed from the base of the PR and between 0d0dab1 and e250faa.

📒 Files selected for processing (1)
  • apps/posts/src/views/Automations/components/email-modal/email-content-modal.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/posts/src/views/Automations/components/email-modal/email-content-modal.tsx

Walkthrough

The email content modal now detects focus inside Koenig portals, keeps background body children inert while the dialog is mounted, and changes Escape and outside-interaction handling so Koenig popups are not dismissed unintentionally. The dialog is rendered non-modally and uses the dialog content node for mounting and cleanup logic.

Possibly related PRs

  • TryGhost/Ghost#28883: Adjusts pointer-events behavior on the same [data-kg-portal] surface used by Koenig popups.

Suggested reviewers: EvanHahn

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: improving link selection behavior in the automations email editor.
Description check ✅ Passed The description matches the change set and explains the modal focus and inert behavior fix in detail.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch NY-1394_atmns-editor-links

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/posts/src/views/Automations/components/email-modal/email-content-modal.tsx`:
- Around line 232-251: The inert handling in the email content modal only
snapshots document.body.children once, so body-level portals mounted after the
modal opens can remain interactive behind it. Update the useEffect in
email-content-modal.tsx to keep enforcing inert on newly added body children
while the dialog is open, either by re-running the inert pass when body children
change or by observing document.body; keep the existing dialogContentRef and
dialogPortalWrapper logic intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 63c77390-a036-4190-8d46-e2f0e01e35ca

📥 Commits

Reviewing files that changed from the base of the PR and between b5944d9 and 50cbe85.

📒 Files selected for processing (1)
  • apps/posts/src/views/Automations/components/email-modal/email-content-modal.tsx

Comment thread apps/posts/src/views/Automations/components/email-modal/email-content-modal.tsx Outdated
@troyciesco troyciesco requested a review from EvanHahn July 8, 2026 14:26

@EvanHahn EvanHahn 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.

I could be convinced that this is a good solution, but it feels like the right answer is to fix something about Koenig (and/or our usage of Radix). I know you tried that stuff, so maybe there's no better alternative.

Comment thread apps/posts/src/views/Automations/components/email-modal/email-content-modal.tsx Outdated
Comment thread apps/posts/src/views/Automations/components/email-modal/email-content-modal.tsx Outdated
@troyciesco troyciesco force-pushed the NY-1394_atmns-editor-links branch 2 times, most recently from f74bed9 to 0d0dab1 Compare July 8, 2026 19:58
ref https://linear.app/ghost/issue/NY-1394/

- Koenig portals its link input/toolbar to document.body, but the
  automations email modal's Radix dialog is modal, so its focus trap
  couldn't contain those portals — clicks and focus never reliably
  reached them
- attempted a Koenig-side fix (render portals into a container inside
  the dialog instead), but that surfaced a new focus fight between
  Radix's trap and Lexical's own focus handling
- fixed in Ghost instead: the dialog is non-modal, and the rest of the
  admin UI is made inert while it's open, so nothing outside the editor
  can steal focus/clicks and Koenig's body-level portals work fine
- inert is also more robust for accessibility than a JS focus trap —
  it's natively enforced by the browser, so background content is
  reliably unreachable by keyboard and screen readers regardless of
  where Koenig portals its UI
ref https://linear.app/ghost/issue/NY-1394/

- while responding to a CodeRabbit review comment on the previous
  commit, found that the effect making the background admin UI inert
  while the email editor is open was a silent no-op: dialogContentRef
  was a plain useRef, but Radix's Portal primitive renders null on its
  first pass and only mounts the real DOM node one render later (via
  its own useLayoutEffect, an SSR-safety trick since document isn't
  available server-side) — so dialogContentRef.current was still null
  by the time our effect ran, it bailed out immediately, and with an
  empty dependency array it never got a second chance
- verified via devtools: document.querySelectorAll('[inert]') returned
  nothing before this fix, meaning the background was fully
  focusable/clickable the entire time the modal was open
- fixed by tracking the node as state via a callback ref instead of a
  plain ref, so the effect re-runs once Radix actually attaches it
- this also let us properly re-test CodeRabbit's suggestion of adding
  a MutationObserver to catch late-mounting body children: with the
  inert-lock actually working, that suggestion does break the stacked
  discard-confirm AlertDialog, confirming it shouldn't be applied
@troyciesco troyciesco force-pushed the NY-1394_atmns-editor-links branch from 0d0dab1 to e250faa Compare July 9, 2026 12:19
@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for this team, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@troyciesco troyciesco requested a review from EvanHahn July 9, 2026 14:39
@EvanHahn EvanHahn merged commit 1a3b541 into main Jul 9, 2026
42 checks passed
@EvanHahn EvanHahn deleted the NY-1394_atmns-editor-links branch July 9, 2026 14:44
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