ant-design / ant-design/x

[XMarkdown] 流式渲染无法识别带 0-3 空格缩进的围栏代码块,流式中间态内容丢失、fence 状态卡死

Open
#2,017 1 comment 1 reaction 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
4.8k
Forks
1.2k
Avg merge
3d 3h
Merged PRs (30d)
2

Description

### 重现步骤

在线示例链接:暂无(最小复现见下方代码,使用 `@ant-design/x-markdown` 的 streaming 模式)

步骤:
1. 以流式模式(`streaming={{ hasNextChunk: true }}`)渲染如下 markdown —— 围栏代码块(fenced code block)前缩进了 2 个空格(例如位于列表项内;CommonMark 允许 fence 前有 0-3 个空格):

````tsx
const input = ' ```js\nconst a = [1,2];\n ```';

````

2. 观察流式输出:`[1,2]` 中的 `[` 没有被当作代码块内容保护,触发了链接识别器挂起;最终输出丢失 closing fence 的末尾字符(实测输出以 ` `` ` 结尾,缺少 3 个反引号)。
3. 若 opening fence 缩进、closing fence 顶格(` ```js ... ``` `),`feedFenceState` 会把顶格的 closing fence 误认为新的 opening,`inFenced` 永久为 `true`,之后所有内容都走代码块路径。

### 当前行为

`feedFenceState`(`packages/x-markdown/src/XMarkdown/hooks/useStreaming.ts`)只在行首为列 0 时识别 opening fence(`lineFenceLen === 0 && (char === '`' || char === '~')`)。行首任意前导空格会先命中 else 分支置 `lineFenceRunEnded = true`,反引号不再累积,缩进的 fence 完全不可见。后果(影响流式中间态;最终渲染交给 `marked` 仍是正确的):

1. 缩进代码块的内容不被 `isInCodeBlock` 保护,按普通文本运行 per-char token 识别器,`[`、`<`、`$` 开头的内容被挂起为未完成 token(闪烁 / 占位符抖动);
2. 之后顶格的 closing fence 被当作新的 opening,`inFenced` latch 为 `true` 且无法退出,后续所有内容走 `commitCache` + code 路径,不再做任何 token 识别。

### 预期行为

与 CommonMark / `marked` 一致:fence 前允许 0-3 个空格(例如列表项内嵌套的代码块),流式状态下同样应正确识别 opening 与 closing fence,流式中间态与最终渲染一致。

### 上下文

缩进的围栏代码块(如列表项内的代码块)是常见 markdown 写法。当前流式模式下这类内容会闪烁、被吞,或导致后续内容渲染路径错误,与最终(非流式)渲染结果不一致。

### 版本

- `@ant-design/x-markdown` 2.9.0(npm 发布版)与 `main` 分支 `b529d8e9`(2026-07-31)均存在。

---
---
**真实流式渲染 Bug 现场对比图:**
(左侧为无缩进正常状态,右侧为带缩进触发挂起 Bug 的状态)
![UI Bug Visual](https://cdn.vectojs.org/upstream-bugs/x-bug-visual.png)

Contributor guide

No contributing guide indexed for this repository

Research direction

Start in `packages/x-markdown/src/XMarkdown/hooks/useStreaming.ts` and read `feedFenceState`, then reproduce the issue with the provided `XMarkdown` streaming example. Check how opening and closing fences with 0–3 leading spaces affect code-block protection and the `inFenced` state. Done means both fences are recognized correctly and streaming intermediate output preserves the code and resumes normal token recognition afterward.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.