Skip to content

fix(rich-markdown-editor): reliable image selection + serialization/paste polish#5590

Merged
waleedlatif1 merged 4 commits into
stagingfrom
md-editor-audit
Jul 11, 2026
Merged

fix(rich-markdown-editor): reliable image selection + serialization/paste polish#5590
waleedlatif1 merged 4 commits into
stagingfrom
md-editor-audit

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

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)

  • 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 (no drag/grab/resize) 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 stay reactive.
  • Deterministic click-to-select. A handleClickOn plugin sets the image's NodeSelection on 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

  • Resize commits once. Width previews in local state during the drag and commits to the node once on pointer-up — a resize is now a single undo step instead of one transaction per pointermove.
  • Broken-image placeholder. A src that fails to load renders as a visible box with its alt text and stays selectable, instead of a bare broken-icon.

Serialization + paste

  • Bare URLs stay bare. The serializer rewrote bare URLs / <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.
  • Linkify a selection on URL paste. Pasting a single URL (or bare 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

  • Vitest: new round-trip tests for bare-URL/autolink preservation and paste tests for the linkify branch; full editor suite (25 files, 409 tests) green. tsc + biome clean; api-validation passes.
  • Real-browser harness (local): reproduced the stale-editable bug (image stuck draggable=false after 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.

@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 11, 2026 6:12am

Request Review

@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes editor interaction, markdown serialization, and paste-time link creation, but hrefs go through existing normalizeLinkHref and the behavior is covered by new round-trip and paste tests.

Overview
Fixes stale read-only UI after setEditable() toggles (e.g. agent stream settling) by introducing useEditorEditable, which re-reads editability on editor transaction/update events—used in code block and image node views so language picker, drag, and resize affordances stay correct without a full refresh.

Images: plain clicks now always node-select via a handleClickOn plugin (modifier keys still follow links); resize previews width in local state and commits once on pointer-up for a single undo step; failed loads show a visible placeholder and stay selectable.

Save noise: postProcessSerializedMarkdown collapses autolink-style [url](url) / mailto: output back to bare URLs/emails when text matches the destination (skips code and titled links).

Paste: pasting a single URL/www./email over an inline text selection linkifies the selection (scheme-sanitized); empty carets, multi-block selections, and unsafe schemes fall through.

Reviewed by Cursor Bugbot for commit b281258. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR improves the rich markdown editor's image handling, paste behavior, and markdown serialization.

  • Reactive editability for image and code block node views.
  • Deterministic image click selection and one-step resize commits.
  • Broken-image placeholder rendering.
  • Bare URL and email preservation during serialization.
  • URL-over-selection paste linkification with safer href handling.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image.tsx Updates image node views with reactive editability, deterministic click selection, resize preview state, single commit on resize finish, and broken-image styling.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/markdown-paste.ts Adds selection linkification for single pasted links and guards it with inline same-parent selection checks plus href normalization.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/markdown-fidelity.ts Adds post-processing that collapses serializer-produced autolinks back to bare URL or email text where appropriate.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/code-block.tsx Uses the shared editability hook so code block controls respond to editor editability changes.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/use-editor-editable.ts Adds a hook that mirrors editor editability into React state from editor transaction and update events.

Reviews (2): Last reviewed commit: "chore(rich-markdown-editor): drop useles..." | Re-trigger Greptile

…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.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ 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.

@waleedlatif1 waleedlatif1 merged commit 2ba0b58 into staging Jul 11, 2026
18 checks passed
@waleedlatif1 waleedlatif1 deleted the md-editor-audit branch July 11, 2026 06:23
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.

1 participant