Uh oh!
There was an error while loading. Please reload this page.
fix(rich-markdown-editor): reliable image selection + serialization/paste polish - #5590
Conversation
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Images: plain clicks now always node-select via a Save noise: Paste: pasting a single URL/ Reviewed by Cursor Bugbot for commit b281258. Configure here. |
Greptile SummaryThis PR improves the rich markdown editor's image handling, paste behavior, and markdown serialization.
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (2): Last reviewed commit: "chore(rich-markdown-editor): drop useles..." | Re-trigger Greptile |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…en-image polish - Reactive editability. The editor runs with shouldRerenderOnTransaction:false, so a node view that read editor.isEditable once at render kept a stale value after setEditable() toggled (e.g. an agent stream settling into the doc), leaving a pasted image showing read-only affordances and code blocks stuck on their read-only label until a full refresh. A shared useEditorEditable hook subscribes to the editor's update/transaction events so both node views track editability reactively. - Deterministic click-to-select. A handleClickOn plugin sets the image's NodeSelection on a plain click so selecting never depends on ProseMirror's click-vs-drag arbitration; grab-anywhere drag-reorder is kept, and modified clicks (Cmd/Ctrl to follow a linked badge) fall through. - Resize commits once. The width previews in local state during the drag and commits to the node once on pointer-up (or pointer-cancel), so a resize is a single undo step and an interrupted drag isn't lost. - Broken-image placeholder. A src that fails to load renders as a visible box with its alt text and stays selectable, instead of collapsing to a bare broken-icon.
…lize The normalizing serializer rewrote a bare URL or <url>/<email> autolink to [url](url) / [a@b.com](mailto:a@b.com) on every save, churning every README's links. postProcessSerializedMarkdown now collapses a link back to its bare form when the visible text already equals the destination (a plain http(s) URL, or an email behind mailto:) — GFM re-autolinks it, so the round-trip is identical with a far quieter diff. Titled links, explicit links, and any link inside a fenced/inline code region are left untouched. Idempotent.
…over it Pasting a single URL (or a bare www. host / email) over a non-empty text selection within one block now wraps the selection in a link, keeping the visible text. www. gets https://, an email gets mailto:, and the href is scheme-sanitized (javascript:/data: rejected; mailto: requires a real user@host address). Collapsed carets, cross-block selections, multi-word pastes, node selections, and code contexts fall through to normal paste.
biome 2.0's noUselessStringRaw flags HIGHLIGHT_BODY — its pattern has no escape sequences, so String.raw is equivalent to a plain template literal (byte-identical value; interpolated into the other String.raw regexes unchanged). Pre-existing on staging; the repo-wide lint gate blocks CI on it.
a66e02a to
b281258Comparewaleedlatif1
commented
Jul 11, 2026
waleedlatif1
commented
Jul 11, 2026
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit b281258. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
Summary
A pass over the rich-markdown editor to fix a reported image bug and close the highest-value gaps toward SOTA parity. Every change was reproduced-then-fixed and re-verified; 409 editor tests pass with no regressions.
Image selection — the reported bug (reliable now)
shouldRerenderOnTransaction:false, so a node view that readeditor.isEditableonce at render kept a stale value aftersetEditable()toggled (e.g. an agent stream settling into the doc) — leaving a pasted image showing read-only affordances (no drag/grab/resize) and code blocks stuck on their read-only label until a full refresh. A shareduseEditorEditablehook subscribes to the editor'supdate/transactionevents so both node views stay reactive.handleClickOnplugin sets the image'sNodeSelectionon click, so selecting never depends on ProseMirror's click-vs-drag arbitration. Grab-anywhere drag-reorder is kept (validated against how Notion/BlockNote/Lexical/Obsidian/TipTap handle this).Image polish
Serialization + paste
<url>/<email>autolinks to[url](url)on every save, churning every README's links. They now round-trip in their bare form (GFM re-autolinks them) with a far quieter diff — titled/explicit links and links inside code are untouched, and it's idempotent.www./email) over a non-empty text selection wraps it in a link (keeping the text) —www.→https://, email→mailto:, href scheme-sanitized. Carets, multi-word pastes, node selections, and code contexts fall through.Testing
draggable=falseafter settle) → fixed; click selects the node; resize is a single undo step.Not included (documented follow-ups)
Ordered-list nested-indent and table-cell-code pipe escaping need serializer-internals surgery (the serializer uses a fixed indent with no per-marker option); plain-paste, image-upload placeholder, and alt-text editing touch the editor component that can't currently build in the harness for empirical verification. Left as deliberate, scoped follow-ups rather than unverified edits.