Skip to content

🐛 Fixed feature image button becoming unresponsive in Safari#27993

Merged
mike182uk merged 1 commit into
mainfrom
ONC-1746
May 20, 2026
Merged

🐛 Fixed feature image button becoming unresponsive in Safari#27993
mike182uk merged 1 commit into
mainfrom
ONC-1746

Conversation

@mike182uk
Copy link
Copy Markdown
Member

ref https://linear.app/ghost/issue/ONC-1746/

  • in Safari, the "Add feature image" button on posts could stop responding to clicks and stop showing the hand cursor on hover, with the only fix being to refresh the page
  • the editor uses an invisible 175px dropzone overlay above the button that becomes click-receptive only while a drag is in progress, toggled by body[data-user-is-dragging]
  • the attribute was set on dragenter and cleared on dragend/drop/document-leave dragleave. Safari is known to skip dragend when the drag source is removed from the DOM mid-drag (e.g. autosave re-render) or when the drag is released outside the window, leaving the attribute set indefinitely so the overlay permanently swallowed clicks on the button beneath
  • replaced the boolean + unreliable screenX/Y === 0 "left the document" heuristic with a depth counter so nested dragenter/dragleave events stay balanced
  • added window.blur and document.visibilitychange listeners as safety nets that hard-reset state - these recover the next time the user switches apps or tabs, even when Safari has dropped the terminal drag event
  • verified in Safari: natural repro (drag a file in, release outside the window) reproduces the stuck state without the fix and recovers on focus change with the fix; manual and programmatic stuck states both clear via blur/visibility; normal drag/drop and nested drag flows are unaffected

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

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: b02b5c7f-8633-406a-be28-ab1a9ff4b595

📥 Commits

Reviewing files that changed from the base of the PR and between c21500e and 2eda2de.

📒 Files selected for processing (1)
  • ghost/admin/app/services/ui.js

Walkthrough

This PR refactors the drag-state tracking mechanism in the UiService. The implementation replaces a screen-position heuristic approach with a dragDepth counter that accurately tracks nested drag events. A shared resetDragState helper manages the document.body.dataset.userIsDragging flag, setting it only when drag depth transitions from 0 to 1 and clearing it when depth returns to 0. Additional safety handlers reset the drag state unconditionally on dragend, drop, window.blur, and document.visibilitychange events. The cleanup function is updated to remove all the new event listeners instead of the prior cancelDrag handler.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fixed feature image button becoming unresponsive in Safari' accurately describes the main bug fix in the changeset, addressing the core issue resolved by the drag handling improvements.
Description check ✅ Passed The description thoroughly explains the Safari bug, the root cause, the solution approach with depth counter and safety listeners, and testing verification—all directly related to the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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

✨ 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 ONC-1746

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

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)
ghost/admin/app/services/ui.js (1)

203-209: ⚡ Quick win

Call resetDragState() at the start of cleanup to clear drag state.

cleanupBodyDragHandlers() only detaches listeners. If cleanup runs while a drag is active, the data-user-is-dragging flag stays set on the body with no remaining handler able to clear it, leaving dropzone styling stuck. Explicitly calling resetDragState() ensures cleanup is idempotent regardless of drag state.

Proposed fix
     `@action`
     cleanupBodyDragHandlers() {
+        this.resetDragState();
         document.body.removeEventListener('dragenter', this.bodyDragEnterHandler, {capture: true});
         document.body.removeEventListener('dragleave', this.bodyDragLeaveHandler, {capture: true});
         document.body.removeEventListener('dragend', this.resetDragState, {capture: true});
         document.body.removeEventListener('drop', this.resetDragState, {capture: true});
         window.removeEventListener('blur', this.windowBlurHandler);
         document.removeEventListener('visibilitychange', this.visibilityChangeHandler);
     }
🤖 Prompt for 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.

In `@ghost/admin/app/services/ui.js` around lines 203 - 209, Call resetDragState()
at the start of cleanupBodyDragHandlers so any active drag state (e.g. the
data-user-is-dragging flag on document.body) is cleared before listeners are
removed; update cleanupBodyDragHandlers to invoke this.resetDragState() as the
first statement, then proceed to remove bodyDragEnterHandler,
bodyDragLeaveHandler, resetDragState (listener), drop listener,
windowBlurHandler, and visibilityChangeHandler to make cleanup idempotent and
avoid stuck dropzone styling.
🤖 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.

Nitpick comments:
In `@ghost/admin/app/services/ui.js`:
- Around line 203-209: Call resetDragState() at the start of
cleanupBodyDragHandlers so any active drag state (e.g. the data-user-is-dragging
flag on document.body) is cleared before listeners are removed; update
cleanupBodyDragHandlers to invoke this.resetDragState() as the first statement,
then proceed to remove bodyDragEnterHandler, bodyDragLeaveHandler,
resetDragState (listener), drop listener, windowBlurHandler, and
visibilityChangeHandler to make cleanup idempotent and avoid stuck dropzone
styling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 326fcd4e-1758-4ad4-88d5-a1c367629a6e

📥 Commits

Reviewing files that changed from the base of the PR and between 8949d82 and c21500e.

📒 Files selected for processing (1)
  • ghost/admin/app/services/ui.js

ref https://linear.app/ghost/issue/ONC-1746/

- in Safari, the "Add feature image" button on posts could stop responding to clicks and stop showing the hand cursor on hover, with the only fix being to refresh the page
- the editor uses an invisible 175px dropzone overlay above the button that becomes click-receptive only while a drag is in progress, toggled by `body[data-user-is-dragging]`
- the attribute was set on `dragenter` and cleared on `dragend`/`drop`/document-leave `dragleave`. Safari does not reliably fire `dragend` when a drag is released outside the window, leaving the attribute set indefinitely so the overlay permanently swallowed clicks on the button beneath
- replaced the boolean + unreliable `screenX/Y === 0` "left the document" heuristic with a depth counter so nested `dragenter`/`dragleave` events stay balanced
- added `window.blur` and `document.visibilitychange` listeners as safety nets that hard-reset state - these recover the next time the user switches apps or tabs, even when Safari has dropped the terminal drag event
- verified in Safari: dragging a file from Finder into the editor and releasing outside the window reproduces the stuck state without the fix and recovers on focus change with the fix; manual and programmatic stuck states both clear via blur/visibility; normal drag/drop and nested drag flows are unaffected
@mike182uk mike182uk merged commit 405bfbf into main May 20, 2026
41 checks passed
@mike182uk mike182uk deleted the ONC-1746 branch May 20, 2026 15:55
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