Skip to content

Image block briefly shows previous image when an uploaded image is inserted above an existing image #2394

Description

@ZhiguangJiang

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.0
  • prosemirror-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

  1. Create a Milkdown/Crepe editor with the ImageBlock feature enabled.
  2. Add an existing image block with src = A.
  3. Upload or programmatically insert a new image block with src = B above the existing image block.
  4. Observe the newly inserted image block while image B is 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions