Skip to content

XMarkdown: incomplete link directly after a list marker renders as raw markdown during streaming #1988

Description

@tibbe

Reproduction link

Minimal snippet below (no CodeSandbox — the behavior is reproducible in any React app).

Steps to reproduce

Render XMarkdown in streaming mode (streaming.hasNextChunk = true) with an incompleteMarkdownComponentMap that maps link to a placeholder component, then feed it a still-incomplete link (closing ) not yet arrived).

import XMarkdown, {type ComponentProps} from '@ant-design/x-markdown';

const streaming = {
  hasNextChunk: true,
  incompleteMarkdownComponentMap: {link: 'incomplete-link'},
};

const components = {
  'incomplete-link': (props: ComponentProps) => {
    const raw = decodeURIComponent(String(props['data-raw'] || ''));
    const label = raw.match(/^\[([^\]]*)\]/)?.[1] ?? raw.slice(1);
    return <a href="#">{label}</a>;
  },
};

// (A) Incomplete link in a paragraph — works as documented.
<XMarkdown components={components} streaming={streaming}>
  {'See [some label](https://exa'}
</XMarkdown>

// (B) The same incomplete link as a list item — broken.
<XMarkdown components={components} streaming={streaming}>
  {'- [some label](https://exa'}
</XMarkdown>

Current behavior

  • (A) renders the incomplete-link placeholder: <a>some label</a>. ✅
  • (B) renders the raw markdown as text — - [some label](https://exa — with the URL fragment turned into an autolink, and the placeholder is never used. ❌

The placeholder only starts working for (B) once the closing ) streams in and the link becomes complete. Because the incomplete state is shown as raw markdown and the complete state as a rendered link, the visible text noticeably changes width (it shrinks) at the moment the link completes — a distracting layout shift in a streaming UI.

The trigger is specifically an incomplete link whose [ immediately follows an unordered list marker (- , * , + ). An incomplete link elsewhere — in a paragraph, or later within the same list item (e.g. - see [some label](https://exa) — is recognized correctly and gets the placeholder. Ordered-list markers (1. ) are also fine. The same raw-rendering happens for incomplete images (![...]() directly after a list marker.

Expected behavior

An incomplete link directly after a list marker should be recognized as an incomplete link and rendered via incompleteMarkdownComponentMap / incomplete-link, the same as in a paragraph — so it doesn't flash as raw markdown and shift layout when it completes.

Version

  • @ant-design/x-markdown: 2.3.0 (the responsible streaming-recognizer code path appears unchanged through the current 2.8.0)
  • React: 19
  • Reproduced in both a browser and jsdom.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions