Image block briefly shows previous image when an uploaded image is inserted above an existing image
Initial checklist
- I agree to follow the code of conduct
- I searched issues and discussions and couldn't find an existing report for this exact behavior.
Affected packages and versions
@milkdown/crepe:7.20.0@milkdown/components:7.20.0prosemirror-view:1.41.4
Link to runnable example
I don't have a small public reproduction yet. The issue was observed in an integration that uses Crepe image block plus a custom drag-and-drop upload plugin that inserts an image-block node after upload completes.
Steps to reproduce
- Create a Milkdown/Crepe editor with the
ImageBlockfeature enabled. - Add an existing image block with
src = A. - Upload or programmatically insert a new image block with
src = Babove the existing image block. - Observe the newly inserted image block while image
Bis being loaded/decoded.
The behavior is easiest to reproduce when the new image is inserted directly above an existing image block.
Expected behavior
The newly inserted image block should not display the existing image below it. It should either show an empty/loading state or show image B once it is ready.
Actual behavior
The new image block is inserted correctly, but it briefly displays the image from the existing image block below it (src = A) before updating to the new uploaded image (src = B).
This looks like a stale DOM/image presentation issue rather than an upload result issue. The document node has the correct new src, but the reused image DOM can visually show the previous bitmap until the new image is applied and loaded.
Suspected cause
The current image-block NodeView update implementation appears to accept any node of the same type:
update: (updatedNode) => {
if (updatedNode.type !== initialNode.type) return false
bindAttrs(updatedNode)
return true
}When a new image block is inserted above an existing image block, ProseMirror's view diff can try to reuse a later existing NodeView for the new node via updateNextNode. Since the image-block NodeView returns true for any image-block regardless of src, an old NodeView for image A can be reused to represent the new node for image B.
bindAttrs(updatedNode) updates the Vue refs, but that does not guarantee that the underlying <img> presentation is immediately replaced. The old <img> element can keep showing the previously decoded image until Vue patches the DOM and the browser loads/decodes the new src.
Possible fix
The image-block NodeView could return false when the visual identity of the image changes, especially when src changes:
let currentNode = initialNode
update: (updatedNode) => {
if (updatedNode.type !== currentNode.type) return false
if (updatedNode.attrs.src !== currentNode.attrs.src) return false
bindAttrs(updatedNode)
currentNode = updatedNode
return true
}This would force ProseMirror to recreate the NodeView when the image src changes, avoiding reuse of a DOM node that may still display the previous image bitmap.
Other attributes such as caption/ratio could still be updated in place, so normal caption editing and resizing should continue to work without recreating the NodeView.
Runtime
Chrome
OS
macOS
Build and bundle tools
Vite / React integration using Crepe
Source: Milkdown/milkdown