统一网页前端的 Markdown 与 Mermaid 渲染能力 - #157
Conversation
|
Warning Review limit reached
Next review available in: 17 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughModel-generated text now uses a shared safe Markdown renderer across Quickstart, Session transcripts, and Workbench. Code fences support shared highlighting, closed Mermaid fences render asynchronously with fallbacks, and user or structured content retains non-Markdown handling. ChangesShared Model Markdown Rendering
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant ModelOutput
participant QuickstartTextTurn
participant SessionTracePanel
participant Workbench
participant MarkdownContent
participant MermaidDiagram
ModelOutput->>QuickstartTextTurn: provide assistant text
ModelOutput->>SessionTracePanel: provide transcript text
ModelOutput->>Workbench: provide model output
QuickstartTextTurn->>MarkdownContent: render assistant Markdown
SessionTracePanel->>MarkdownContent: render transcript Markdown
Workbench->>MarkdownContent: render preview or successful output
MarkdownContent->>MermaidDiagram: render closed Mermaid fence asynchronously
MermaidDiagram-->>MarkdownContent: return SVG or preserved source fallback
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
DuckPR reviewer: opencode
Model: anthropic/glm-5.2
✅ 安全实现到位,可以合并。
Reviewed changes — 本 PR 为 Quickstart 助手文本气泡启用 GitHub Flavored Markdown 渲染,新增共享组件 MarkdownContent 并仅对 assistant 角色启用,用户消息保持纯文本,SSE 与后端合同不变。
- 新增共享
MarkdownContent组件(web/src/shared/ui/markdown-content.tsx)— 基于react-markdown@10+remark-gfm@4,通过skipHtml跳过原始 HTML、urlTransform白名单(safeMarkdownUrl仅放行https?:/mailto:///#)和img渲染为null三道防线阻止 XSS;react-markdownv10 的urlTransform在 props 到达自定义a组件之前应用,组合安全模型正确。 QuickstartTextTurn集成(web/src/features/managed-agents/quickstart/chatLayout.tsx)—isUser ? content : <MarkdownContent value={content} />,用户/助手角色分流最小且正确。- 聚焦测试(
ManagedAgentsPage.quickstart.suite.tsx)— 新增 2 个用例覆盖加粗、行内代码、列表、代码块、GFM 表格、安全/危险链接过滤、原始 HTML 跳过与用户消息纯文本。 - 依赖与文档 —
react-markdown/remark-gfm加入package.json并更新bun.lock;设计文档记录渲染与安全边界。
ℹ️ Nitpicks
- Markdown 代码块未接入仓库已有的 highlight.js 体系(
web/src/features/managed-agents/components/CodeBlocks.tsx与web/AGENTS.md约定 quickstart 代码块应产出code.language-*与hljs-*token span)。当前MarkdownContent的code/pre只输出纯文本。是否让模型返回的 Markdown 代码块与 template 驱动的代码块保持一致的语法高亮,属于产品决策,建议作为后续 issue 跟进。
anthropic/glm-5.2 | 𝕏
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
web/src/shared/ui/markdown-content.tsx (2)
57-70: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winHoist
remarkPluginsto avoid a new array on every render.
markdownComponentsis already a module-level constant;remarkPlugins={[remarkGfm]}isn't, so it's reallocated on every render — notably on every streaming delta re-render of assistant messages.♻️ Proposed hoist
+const remarkPlugins = [remarkGfm]; + export function MarkdownContent({ value, className }: { value: string; className?: string }) { return ( <div className={clsx('space-y-3 whitespace-normal break-words', className)}> <ReactMarkdown - remarkPlugins={[remarkGfm]} + remarkPlugins={remarkPlugins} components={markdownComponents} skipHtml urlTransform={safeMarkdownUrl} >🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/shared/ui/markdown-content.tsx` around lines 57 - 70, Hoist the remarkPlugins array used by MarkdownContent to module scope, alongside markdownComponents, and reuse that stable constant in the ReactMarkdown props instead of allocating [remarkGfm] during each render.
10-22: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueDrop anchors for blocked markdown links
For unsafe URLs,
urlTransformreturns'', andMarkdownLinkcurrently renders<a href="">Unsafe link</a>. That’s a non-navigable placeholder anchor rather than a proper link, so render the children as plain text whenhrefis empty and avoid the invalid anchor.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/shared/ui/markdown-content.tsx` around lines 10 - 22, Update MarkdownLink to detect an empty href, including the blocked-URL result from urlTransform, and render children without an anchor in that case. Preserve the existing anchor attributes and external-link behavior for non-empty href values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@web/src/shared/ui/markdown-content.tsx`:
- Around line 6-8: Update safeMarkdownUrl so the site-internal path check
accepts a single-slash path but rejects protocol-relative URLs beginning with
"//". Preserve the existing https, mailto, fragment, and valid internal-path
behavior.
---
Nitpick comments:
In `@web/src/shared/ui/markdown-content.tsx`:
- Around line 57-70: Hoist the remarkPlugins array used by MarkdownContent to
module scope, alongside markdownComponents, and reuse that stable constant in
the ReactMarkdown props instead of allocating [remarkGfm] during each render.
- Around line 10-22: Update MarkdownLink to detect an empty href, including the
blocked-URL result from urlTransform, and render children without an anchor in
that case. Preserve the existing anchor attributes and external-link behavior
for non-empty href values.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: cdc04573-e179-4a99-9a66-d37759a98aaa
⛔ Files ignored due to path filters (1)
web/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (5)
docs/design/fe/managed-agent-quickstart-interactions.mdweb/package.jsonweb/src/features/managed-agents/ManagedAgentsPage.quickstart.suite.tsxweb/src/features/managed-agents/quickstart/chatLayout.tsxweb/src/shared/ui/markdown-content.tsx
There was a problem hiding this comment.
DuckPR reviewer: opencode
Model: anthropic/glm-5.2
✅ 未发现新问题。
Reviewed changes — 本轮覆盖自上一次 DuckPR 评审(70cb839)以来的增量:补齐显式语言代码高亮、新增异步 Mermaid 图表渲染、抽取共享语法高亮模块,并修复上一轮的全部 nitpick。
- 抽取共享语法高亮(
web/src/shared/ui/syntax-highlighting.ts与syntax-code-block.tsx)— 将原CodeBlocks.tsx内联的highlight.js注册、normalizeHighlightLanguage、highlightCodeHTML、highlightBashYAMLCommand与HighlightedCode/SyntaxCodeBlock迁移到shared/ui,并在CodeBlocks.tsx通过export { ... }保留原公共导出(highlightBashYamlCommand/highlightCodeHtml按命名规范别名),MiniCodeBlock/NumberedCodeBlock/ScrollableCodeBlock/TemplateCard等行为不变。机械迁移边界干净,没有把会话级语义泄漏到共享层。 - Markdown 代码围栏接入高亮(
markdown-content.tsx)—pre组件读取子code的language-*class 与文本源,转发给共享SyntaxCodeBlock;remarkPlugins已提升为模块级常量markdownRemarkPlugins,避免每次流式重渲染重新分配数组。 - Mermaid 异步渲染(
mermaid-diagram.tsx+remark-mermaid-fence-state.ts)—mermaid围栏走独立路径:自定义 remark 插件依据positionoffset 标注data-mermaid-closed,未闭合的流式围栏只展示源码;MermaidDiagram用useId生成稳定渲染 ID,模块级renderQueue串行化初始化避免多图表/主题切换互相覆盖配置,renderSequence保证每次mermaid.render拿到唯一 ID。150ms 稳定窗口 +currentflag 防止卸载或源码变化后旧异步结果覆盖新内容;renderKey携带resolvedTheme+source,主题切换正确触发重渲染。 - 安全边界 — Mermaid 固定
securityLevel: 'strict'、startOnLoad: false、suppressErrorRendering: true,先mermaid.parse(source, { suppressErrors: true })再mermaid.render,失败/超限(20,000 字符、flowchart 200 边)走源码 + 本地化状态回退;SVG 经 Mermaid 内置 DOMPurify 清洗后通过dangerouslySetInnerHTML注入,与官方“不可信输入”指引一致。MarkdownLink已按上一轮反馈改为rel="noreferrer noopener",并对空href(含被safeMarkdownUrl拦截为''的 URL)降级为纯文本。 - 测试与文档 — 新增 Mermaid 非法语法、超限、未闭合流式围栏、六种显式语言高亮、协议相对链接拦截、自动链接、任务列表、引用等回归;
test/setup.ts补充getBBox/SVGElement/SVGGraphicsElement/CSSStyleSheet以支撑 Mermaid 在 happy-dom 下渲染;设计文档补全 Mermaid 渲染、安全与回退合同及流程图。
| View workflow run | Using anthropic/glm-5.2 | 𝕏
anthropic/glm-5.2 | 𝕏
There was a problem hiding this comment.
DuckPR reviewer: opencode
Model: anthropic/glm-5.2
✅ 未发现新问题。
Reviewed changes — 本轮覆盖自上一次 DuckPR 评审(7024dba)以来的增量:删除 Session 专属手写 Markdown parser 并接入共享 MarkdownContent、Workbench Response Preview 与 Evaluate 输出接入同一共享组件,并新增跨功能设计文档。
- Session 迁移到共享渲染器(
SessionTracePanel.tsx、sessionTraceModel.ts、types.ts)— 删除parseTranscriptCode/parseTranscriptMarkdownBlocks/isSafeTranscriptMarkdownHref/TranscriptMarkdownBlock及renderTranscriptMarkdown*系列,TranscriptContent直接渲染MarkdownContent。TranscriptTypedContent仍按displayKind(json/log/metric/command)走结构化SyntaxCodeBlock分支,原始 JSON 展示边界不变。确认无残留死代码引用,looksLikeJson/prettyCode仍被 debug JSON 路径使用。 - Workbench 接入共享 Markdown(
evaluate.tsx)—ResponsePreview仅在responseText存在时用MarkdownContent;EvaluateOutputCell仅在成功且有output时使用。运行中状态、错误和空状态保持纯文本,并由**Model** request failed.→alert.querySelector('strong')为null的回归保护。 - 跨功能设计文档(
docs/design/fe/shared-model-output-markdown.md)— 记录四个消费边界、共享合同(CommonMark/GFM、显式语言高亮、Mermaid 安全与回退)、Session 与 Workbench 迁移细节和验收标准,含 Mermaid 消费边界流程图。 - 依赖与测试门禁 —
package.json新增mermaid 11.16.0(锁定版本)、react-markdown ^10.1.0、remark-gfm ^4.0.1、type-fest ^4.41.0(devDependency);Workbench 新增 Response Preview 与 Evaluate 的 Markdown 渲染用例,error 用例改为带 Markdown 标记验证错误不解释为 Markdown;Quickstart/resources suite 随 Session 迁移更新testid并补充blockquote/em/del断言。
anthropic/glm-5.2 | 𝕏
There was a problem hiding this comment.
🧹 Nitpick comments (1)
web/src/features/managed-agents/ManagedAgentsPage.quickstart.suite.tsx (1)
273-287: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAnchor the streaming-fence wait to the Mermaid delay constant instead of a hard-coded 300 ms.
If
MERMAID_RENDER_DELAY_MSis ever raised above 300 ms, this test passes for the wrong reason (nothing has had time to render yet) rather than because the fence is still open.♻️ Suggested approach
Export the delay constant from
web/src/shared/ui/mermaid-diagram.tsxand sleepMERMAID_RENDER_DELAY_MS * 2, or assert viawaitForthatmarkdown-code-blockkeeps the partial source while[data-mermaid-state]stays absent.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/managed-agents/ManagedAgentsPage.quickstart.suite.tsx` around lines 273 - 287, Update the streaming-fence test around the Mermaid rendering delay to use the shared MERMAID_RENDER_DELAY_MS constant instead of a hard-coded 300 ms timeout. Export and import that constant from mermaid-diagram.tsx, then wait at least twice its duration before asserting that no Mermaid diagram or state exists while the partial markdown remains visible.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@web/src/features/managed-agents/ManagedAgentsPage.quickstart.suite.tsx`:
- Around line 273-287: Update the streaming-fence test around the Mermaid
rendering delay to use the shared MERMAID_RENDER_DELAY_MS constant instead of a
hard-coded 300 ms timeout. Export and import that constant from
mermaid-diagram.tsx, then wait at least twice its duration before asserting that
no Mermaid diagram or state exists while the partial markdown remains visible.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e00d1e54-f40d-440c-9e22-1716fdbb6c64
⛔ Files ignored due to path filters (1)
web/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (20)
docs/design/fe/managed-agent-quickstart-interactions.mddocs/design/fe/shared-model-output-markdown.mdweb/package.jsonweb/src/features/managed-agents/ManagedAgentsPage.quickstart.suite.tsxweb/src/features/managed-agents/ManagedAgentsPage.resources.suite.tsxweb/src/features/managed-agents/ManagedAgentsPage.test-utils.tsxweb/src/features/managed-agents/components/CodeBlocks.tsxweb/src/features/managed-agents/sessions/SessionTracePanel.tsxweb/src/features/managed-agents/sessions/sessionTraceModel.tsweb/src/features/managed-agents/types.tsweb/src/features/workbench/WorkbenchPage.test.tsxweb/src/features/workbench/evaluate.tsxweb/src/shared/i18n/messages/en.jsonweb/src/shared/i18n/messages/zh-CN.jsonweb/src/shared/ui/markdown-content.tsxweb/src/shared/ui/mermaid-diagram.tsxweb/src/shared/ui/remark-mermaid-fence-state.tsweb/src/shared/ui/syntax-code-block.tsxweb/src/shared/ui/syntax-highlighting.tsweb/src/test/setup.ts
💤 Files with no reviewable changes (2)
- web/src/features/managed-agents/types.ts
- web/src/features/managed-agents/sessions/sessionTraceModel.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- web/package.json
- web/src/shared/ui/markdown-content.tsx
|
本轮 review 已在 525e871 处理:未闭合 Mermaid fence 的测试不再等待硬编码 300ms,而是同步断言解析结果仍为普通代码块,且没有创建 Mermaid 渲染节点。这个断言直接验证组件边界,也不再与实现中的 debounce 时间耦合。聚焦测试、完整前端测试(386 项)、构建、duplicates、complexity、命名、格式及大文件检查均通过。 |
There was a problem hiding this comment.
DuckPR reviewer: opencode
Model: anthropic/glm-5.2
✅ 未发现新问题。
Reviewed changes — 本轮覆盖自上一次 DuckPR 评审(fe951c4)以来的增量:文档命名与消费边界澄清,以及流式围栏测试同步化。
- 修正设计文档命名(
docs/design/fe/shared-markdown-rendering.md,f856a46)— 标题从「模型输出 Markdown 统一渲染」改为「网页前端 Markdown 与 Mermaid 统一渲染」,背景表述从「模型生成文本」泛化为「Markdown 内容可能来自模型、用户或其他数据源」,与共享能力不限内容来源、由消费页面决定渲染策略的实现一致。 - 澄清消费边界(同文件,
1a70a71)— 将「消费边界」重组为「能力与消费边界」,更新 Mermaid 流程图区分「声明为 Markdown」与「保留原始语义」两条路径,并显式说明新增消费方不得自行实现另一套 parser。 - 稳定流式围栏测试(
ManagedAgentsPage.quickstart.suite.tsx,525e871)— 将does not start Mermaid rendering until a streaming fence is closed由async改为同步,移除await new Promise(setTimeout 300ms)硬编码等待。未闭合围栏经remarkMermaidFenceState标注data-mermaid-closed=false后走SyntaxCodeBlock同步路径,MermaidDiagram组件根本不会挂载,MERMAID_RENDER_DELAY_MS(150ms)render delay 不适用,断言即时成立。这同时回应了 CodeRabbit 关于硬编码 300ms 超时的 nitpick。
anthropic/glm-5.2 | 𝕏

问题
模型输出会出现在 Quickstart、Session transcript 和 Workbench。此前 Quickstart assistant 消息按普通字符串展示,Session 使用功能内手写的有限 Markdown parser,Workbench Preview/Evaluate 仍按纯文本展示,导致同一模型输出在不同页面的语法能力、安全策略和 Mermaid 行为不一致。
Closes #156
Closes #158
Closes #159
实现
MarkdownContent,基于react-markdown与remark-gfm渲染 CommonMark/GFM,并统一接入 Quickstart assistant、Session transcript/thinking、Workbench Response Preview 和 Evaluate 模型输出。mermaid围栏按需动态加载 Mermaid,并针对浅色/深色主题重新渲染;流式围栏闭合前仅展示源码。javascript:与协议相对 URL;外部链接使用noreferrer noopener。验证
bun run build、just web-format-check、bun run lint:naming、just duplicates、just complexity、just large-files通过。已知基线问题
bun run lint在当前基线上已有 88 个错误;本次修改的 Session 文件也包含既有 effect/setState lint 错误,本次没有扩大该问题。just hooks-run的全量私钥扫描会命中两个既有密钥生成测试夹具;其余 hook 全部通过。Summary by CodeRabbit
New Features
Safety Improvements