Skip to content

feat: render loading placeholder for incomplete streaming images - #504

Open
sleitor wants to merge 2 commits into
vercel:mainfrom
sleitor:feat/incomplete-image-placeholder
Open

feat: render loading placeholder for incomplete streaming images#504
sleitor wants to merge 2 commits into
vercel:mainfrom
sleitor:feat/incomplete-image-placeholder

Conversation

@sleitor

@sleitor sleitor commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #503 — incomplete images during streaming now render an animated skeleton placeholder instead of being silently removed. This mirrors the existing behavior for incomplete links.

What changed


  • link-image-handler.ts: Both handleIncompleteUrl() and handleIncompleteText() now emit ![alt](streamdown:incomplete-image) for incomplete images instead of stripping them entirely
  • index.ts: Updated earlyReturn check to also short-circuit on streamdown:incomplete-image; fixed JSDoc for images option
  • Tests updated: removal expectations → placeholder expectations

packages/streamdown

  • lib/image.tsx: ImageComponent detects src === 'streamdown:incomplete-image' and renders an animate-pulse skeleton div with data-incomplete="true"
  • Tests added for the new placeholder behavior

Changeset: remend@patch + streamdown@patch

Before / After

Before: ![logo](./assets/log → (image silently removed, layout shift)

After: ![logo](./assets/log → animated grey skeleton placeholder shown until the stream completes

Test coverage

  • 356 remend tests ✅
  • 964 streamdown tests ✅

All existing tests updated; 4 new tests added for the placeholder behavior.

@vercel

vercel Bot commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

@sleitor is attempting to deploy a commit to the Vercel Team on Vercel.

A member of the Team first needs to authorize it.

@farnabaz farnabaz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the PR @sleitor
Do you mind removing line numbers commit from this PR?

Also I do think streamdown schema will be blocked by rehype-harden package we need to change placeholder to another text. maybe even an 1x1 pixel placeholder

data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>
// or equivalent png

Incomplete images during streaming (e.g. `![alt](https://exampl`) now
render an animated skeleton placeholder instead of being silently removed.

Changes:
- remend: emit `![alt](streamdown:incomplete-image)` for incomplete images
  instead of stripping them entirely, in both handleIncompleteUrl() and
  handleIncompleteText()
- remend: add earlyReturn check for `streamdown:incomplete-image` marker
- streamdown: ImageComponent renders animate-pulse skeleton div when
  src === 'streamdown:incomplete-image'
- Tests updated to reflect new placeholder behavior

Mirrors the existing incomplete link handling (streamdown:incomplete-link).

Closes vercel#503
Replace the streamdown:incomplete-image custom-scheme marker with a
1x1 transparent PNG data: URI. Non-http(s)/data URL schemes can be
stripped by strict URL-sanitizing rehype/remark plugins (e.g.
rehype-harden), which would prevent the placeholder from ever reaching
the ImageComponent's src check.

Addresses review feedback from @farnabaz on vercel#504.
@sleitor
sleitor force-pushed the feat/incomplete-image-placeholder branch from 44ea6a3 to 4a07096 Compare August 30, 2026 17:10
@sleitor

sleitor commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review, @farnabaz!

  • Removed the unrelated lineNumbers commit that got mixed into this branch (rebased, now only the placeholder feature + this fix remain).
  • Replaced the streamdown:incomplete-image custom-scheme marker with a 1x1 transparent PNG data: URI (INCOMPLETE_IMAGE_PLACEHOLDER, exported from remend). This should survive strict URL-sanitizing schemas like rehype-harden, since only http(s)/data schemes are typically allowlisted — a bare custom scheme string would likely get stripped before it reaches the ImageComponent's src check.

All 356 remend + 951 streamdown tests pass, lint is clean. Let me know if you'd like a different data URI (e.g. transparent GIF/SVG) or any other adjustments.

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.

Streaming support for partial images (similar to incomplete link handling)

2 participants