From 7bcacc73c1d2d83d14d5945999d4595e085b05fa Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 10 Jul 2026 23:12:22 -0700 Subject: [PATCH 1/4] fix(rich-markdown-editor): reliable image selection + resize and broken-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. --- .../rich-markdown-editor/code-block.tsx | 14 ++-- .../rich-markdown-editor/image.tsx | 73 +++++++++++++++---- .../use-editor-editable.ts | 27 +++++++ 3 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/use-editor-editable.ts diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/code-block.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/code-block.tsx index c17577c917b..7db9b1481b9 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/code-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/code-block.tsx @@ -15,6 +15,7 @@ import { NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap import { Check, ChevronDown, Code, Copy, Eye, WrapText } from 'lucide-react' import { looksLikeMermaid, MermaidDiagram } from '../mermaid-diagram' import { detectLanguage } from './detect-language' +import { useEditorEditable } from './use-editor-editable' const PLAIN = 'plain' const MERMAID = 'mermaid' @@ -59,6 +60,7 @@ function CodeBlockView({ node, updateAttributes, editor, getPos }: ReactNodeView const [editingInline, setEditingInline] = useState(false) const [peekSource, setPeekSource] = useState(false) const { copied, copy } = useCopyToClipboard({ resetMs: 1500 }) + const editable = useEditorEditable(editor) const explicitLanguage = node.attrs.language as string | null const text = node.textContent @@ -68,7 +70,7 @@ function CodeBlockView({ node, updateAttributes, editor, getPos }: ReactNodeView // diagram on blur (the Linear/GitHub model). The Show source / Show diagram control drives this by // focusing into / blurring the block; read-only uses {@link peekSource} since there is no caret. useEffect(() => { - if (!isMermaid || !editor.isEditable) { + if (!isMermaid || !editable) { setEditingInline(false) return } @@ -91,9 +93,9 @@ function CodeBlockView({ node, updateAttributes, editor, getPos }: ReactNodeView editor.off('focus', sync) editor.off('blur', sync) } - }, [editor, getPos, isMermaid]) + }, [editor, getPos, isMermaid, editable]) - const showSource = editor.isEditable ? editingInline : peekSource + const showSource = editable ? editingInline : peekSource const showDiagram = isMermaid && text.trim().length > 0 && !showSource // Skip language detection on the mermaid path — the picker/label never render there. @@ -104,7 +106,7 @@ function CodeBlockView({ node, updateAttributes, editor, getPos }: ReactNodeView 'Plain text' const toggleSource = () => { - if (!editor.isEditable) { + if (!editable) { setPeekSource((value) => !value) return } @@ -144,7 +146,7 @@ function CodeBlockView({ node, updateAttributes, editor, getPos }: ReactNodeView )} {!isMermaid && - (editor.isEditable ? ( + (editable ? ( // Editable: a language picker. Read-only: a static label — selecting a language calls // updateAttributes, which would mutate a doc that must not change. @@ -179,7 +181,7 @@ function CodeBlockView({ node, updateAttributes, editor, getPos }: ReactNodeView {label} ))} - {!isMermaid && editor.isEditable && ( + {!isMermaid && editable && (