Skip to content

fix: respect line breaks in labels containing KaTeX math - #7962

Open
YBJ0000 wants to merge 3 commits into
mermaid-js:developfrom
YBJ0000:bug/7873_br-ignored-in-katex-labels
Open

YBJ0000 wants to merge 3 commits into
mermaid-js:developfrom
YBJ0000:bug/7873_br-ignored-in-katex-labels

Conversation

@YBJ0000

@YBJ0000 YBJ0000 commented Jul 12, 2026 •

Copy link
Copy Markdown

📑 Summary

In flowchart labels, line breaks stopped working as soon as the label also contained a $$…$$ KaTeX expression. Input<br/>$$x$$ rendered as a single line Inputx, and a hard-wrapped label such as

plainmath["line 0
line 1
$$x=42$$"]

collapsed onto one line too — while the same labels without math broke into separate lines as expected.

Resolves #7873
Resolves #7194

📏 Design Decisions

renderKatexUnsanitized already handles multi-line labels: it splits the text on <br/> and wraps each line in its own <div> (this is how sequence diagrams render multi-line KaTeX labels today). Two separate things prevented labels from reaching it in that shape.

<br/> was destroyed before rendering (#7873). addHtmlSpan replaced every <br/> with \n before calling renderKatexSanitized. After that replacement the split never finds anything, the whole label lands in one <div>, and the literal newline renders as nothing in HTML. Fixed by passing the label through unchanged.

Hard line breaks never became <br/> (#7194). The flowchart lexer (<string>[^"]+ in flow.jison) returns node text verbatim, so a hard-wrapped label arrives at createText with literal \n. For labels without math, markdownToHTML / nonMarkdownToHTML convert those to <br/>, but the KaTeX branch bypasses both (label: hasKatex(text) ? inputForKatex : …). Fixed by applying the same \n → <br/> rule that markdownToHTML uses for plain-text tokens.

The second half of this fix follows @omkarht's diagnosis in #7202 — credit to them for locating the inputForKatex path. I used the narrow replace(/\n */g, '<br/>') rather than running the label through markdownToHTML, because the latter wraps the label in <p>, and since renderKatexUnsanitized then splits on <br/>, the opening and closing <p> tags end up in different <div>s. Note also that #7202 cannot take effect on its own: on current develop the <br/> it produces are immediately converted back to \n by addHtmlSpan, so the two edits cancel out. Removing that replace is the prerequisite.

Multi-line math inside a single $$…$$ is unaffected — katexRegex has no s flag, so such text is not detected as KaTeX and never enters this branch.

Verified in the demo server that both forms now render on separate rows, matching the control diagrams without math, and that single-line math labels are unaffected.

Four unit tests cover: a text line + math line via <br/>, two math lines via <br/>, a hard-wrapped label ending in math, and math followed by a hard line break.

📋 Tasks

Make sure you

  • 📖 have read the contribution guidelines
  • 💻 have added necessary unit/e2e tests.
  • 📓 have added documentation. — not needed: behavioral bug fix, no API or syntax change
  • 🦋 If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

Review Summary

🎉 Fix correctly preserves <br/> breaks in KaTeX flowchart node and edge labels by passing the original label (with <br/> intact) to the existing KaTeX line-splitting/rendering logic.

🎉 Added focused unit tests covering both text-plus-math (Input<br/>$$x$$) and multiple math-only lines ($$x$$<br/>$$f(x)$$), verifying each <br/>-separated line renders as its own row.

🎉 Included a changeset recording the behavior fix.

No blocking or important issues identified; sanitization is maintained via renderKatexSanitized.

Verdict: APPROVE

@changeset-bot

changeset-bot Bot commented Jul 12, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 48f388c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
mermaid Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@netlify

netlify Bot commented Jul 12, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for mermaid-js ready!

Name Link
🔨 Latest commit 48f388c
🔍 Latest deploy log https://app.netlify.com/projects/mermaid-js/deploys/6a63fbf809423100086dd595
😎 Deploy Preview https://deploy-preview-7962--mermaid-js.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added the Type: Bug / Error Something isn't working or is incorrect label Jul 12, 2026
@coderabbitai

coderabbitai Bot commented Jul 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

KaTeX-enabled flowchart labels now preserve <br/> and hard line breaks. Rendering converts hard breaks to HTML rows, while tests cover text and multiple math expressions.

Changes

KaTeX label line breaks

Layer / File(s) Summary
Preserve breaks in KaTeX rendering
packages/mermaid/src/rendering-util/createText.ts
KaTeX HTML labels retain the original label text, convert hard line breaks to <br/>, and use named rendering and sanitization helpers.
Validate separated KaTeX rows
packages/mermaid/src/rendering-util/createText.spec.ts, .changeset/katex-label-line-breaks.md
Tests verify separate rows for HTML and hard breaks around text and KaTeX expressions; the changeset records the patch behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes and tests address #7873 by preserving <br/> breaks in flowchart labels with KaTeX math.
Out of Scope Changes check ✅ Passed The hard-line-break handling and tests are consistent with the stated PR objectives and do not appear unrelated.
Title check ✅ Passed The title clearly and concisely summarizes the main fix: preserving line breaks in KaTeX labels.
Description check ✅ Passed The description follows the template with a summary, linked issues, design decisions, and task checklist.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 12, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@mermaid-js/examples

npm i https://pkg.pr.new/@mermaid-js/examples@7962

mermaid

npm i https://pkg.pr.new/mermaid@7962

@mermaid-js/layout-elk

npm i https://pkg.pr.new/@mermaid-js/layout-elk@7962

@mermaid-js/layout-tidy-tree

npm i https://pkg.pr.new/@mermaid-js/layout-tidy-tree@7962

@mermaid-js/mermaid-zenuml

npm i https://pkg.pr.new/@mermaid-js/mermaid-zenuml@7962

@mermaid-js/parser

npm i https://pkg.pr.new/@mermaid-js/parser@7962

@mermaid-js/tiny

npm i https://pkg.pr.new/@mermaid-js/tiny@7962

commit: 48f388c

@codecov

codecov Bot commented Jul 12, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.63%. Comparing base (f3dea58) to head (48f388c).
⚠️ Report is 32 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #7962      +/-   ##
===========================================
+ Coverage    77.51%   77.63%   +0.11%     
===========================================
  Files          564      564              
  Lines        74906    74906              
  Branches     14617    12667    -1950     
===========================================
+ Hits         58065    58152      +87     
+ Misses       15842    15758      -84     
+ Partials       999      996       -3     
Flag Coverage Δ
e2e 70.86% <100.00%> (+0.23%) ⬆️
unit 74.87% <100.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
packages/mermaid/src/rendering-util/createText.ts 97.76% <100.00%> (+0.37%) ⬆️

... and 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@argos-ci

argos-ci Bot commented Jul 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ✅ No changes detected - Jul 25, 2026, 12:17 AM

YBJ0000 and others added 2 commits July 12, 2026 17:55
The KaTeX branch in createText bypasses markdownToHTML/nonMarkdownToHTML,
so hard line breaks in a label source never became <br/>, and
renderKatexSanitized -- which splits its input on <br/> -- saw a single
line. Apply the same \n -> <br/> rule markdownToHTML uses for plain-text
tokens, rather than running the label through the markdown lexer, which
would wrap it in <p> and leave the tags split across separate <div>s.

Diagnosis of the inputForKatex path is due to @omkarht in mermaid-js#7202.

Resolves mermaid-js#7194

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@YBJ0000

YBJ0000 commented Jul 24, 2026

Copy link
Copy Markdown
Author

Extended to also cover #7194

I've pushed a follow-up commit that extends this PR to fix #7194 as well. Some context on why the
two belong together, since at first glance they look like competing fixes.

I measured the four relevant configurations with a createText unit probe (jsdom,
forceLegacyMathML: true). The number is how many <div> rows the label is split into — 1 means
"collapsed onto a single line", i.e. the bug:

Configuration a<br/>b<br/>$$x=42$$ a\nb\n$$x=42$$ (hard line breaks)
develop (baseline) 1 ❌ 1 ❌
Original scope of this PR 3 ✅ 1 ❌
#7202's createText.ts hunk alone, on develop 1 ❌ 1 ❌
This PR, extended 3 ✅ 3 ✅

Why they're the same root cause. renderKatexUnsanitized splits its input on
lineBreakRegex and gives each line its own <div>. Two separate things were preventing labels
from ever reaching it in that shape:

  1. addHtmlSpan did node.label.replace(common.lineBreakRegex, '\n') immediately before calling
    renderKatexSanitized, destroying the very <br/> markers it splits on. That's the original
    fix in this PR.
  2. Hard line breaks never became <br/> in the first place. The flowchart lexer
    (<string>[^"]+ in flow.jison) returns node text verbatim, so plainmath["line 0⏎line 1⏎$$x=42$$"]
    arrives with literal \n. For non-math labels markdownToHTML/nonMarkdownToHTML convert
    those to <br/>, but the KaTeX branch bypasses both
    (label: hasKatex(text) ? inputForKatex : decodedReplacedText). That's Impossible to add newlines to flowchart nodes with maths in them #7194, and it's the new
    one-line addition here.

Row 3 of the table is worth flagging: because of (1), the fix proposed in #7202 currently has no
observable effect on develop — the <br/> it produces are turned straight back into \n by
addHtmlSpan, so the two edits cancel out. @omkarht's diagnosis of the inputForKatex path was
right, it just couldn't take effect until that replace was removed. Credit for the second half
of this fix goes to them; #7202 can be closed as covered here if the maintainers agree, or I'm
happy to drop the second commit and let it be rebased on top instead.

On the implementation. I used text.replace(/\n */g, '<br/>') rather than #7202's
markdownToHTML(...). Both fix the issue, but running the label through the markdown lexer wraps
it in <p>, and since renderKatexUnsanitized then splits on <br/>, the opening and closing
<p> tags land in different <div>s. The narrow replacement uses the exact rule
markdownToHTML already applies to plain-text tokens, keeps the KaTeX branch's existing
"no markdown processing" behavior unchanged, and avoids putting LaTeX through the markdown lexer
at all.

Multi-line math inside a single $$…$$ is unaffected: katexRegex has no s flag, so such text
isn't detected as KaTeX and never enters this branch.

Two more unit tests cover the hard-line-break case and a math-then-hard-break case. Full unit
suite shows no regressions.

@YBJ0000 YBJ0000 changed the title fix: respect <br/> line breaks in labels containing KaTeX math fix: respect line breaks in labels containing KaTeX math Jul 24, 2026
@pbrolin47

pbrolin47 commented Jul 27, 2026 •

Copy link
Copy Markdown
Collaborator

Hi @YBJ0000
Thanks for this PR. Tested the PR branch locally and saw the problems described in #7873 and #7194 are resolved as expected.

Some input:

[sisyphos-bot]


What's working well

🎉 The fix is minimal and surgical — two lines changed, zero risk to non-math labels.

🎉 The four new unit tests cover exactly the reported failure modes:
+ text + KaTeX, KaTeX +
+ KaTeX, hard newlines with mixed content, hard newlines between KaTeX expressions. The jsdom
tests check actual HTML structure (.outerHTML, .querySelector('.katex')), not just parse output.

🎉 The comment on the inputForKatex change clearly explains why the conversion is needed — "KaTeX labels bypass the markdown/non-markdown conversion above" — which is non-obvious and exactly the
kind of thing a future reader would wonder about.

🎉 Removing the unused common default import is clean housekeeping.


Things to address

🟡 [important] — Missing Cypress visual regression test

Per repo conventions, changes to rendering-util/ require E2E visual tests (cypress/integration/rendering/ with imgSnapshotTest()). The existing cypress/integration/rendering/katex.spec.js has no
line-break test cases. The unit tests are good for structural correctness but don't cover:

  • Visual appearance across themes (the katex.spec.js tests render under all 5 themes via imgSnapshotTest)
  • Baseline alignment between text and math rows
  • Edge label rendering (isNode: false)

It would be great to add 1–2 test cases to katex.spec.js:
//
between text and math
imgSnapshotTest(
flowchart LR\n A["Input<br/>$$x$$"],
{ name: 'label with br and katex' }
)
// hard newline
imgSnapshotTest(
flowchart LR\n A["line 0\nline 1\n$$x=42$$"],
{ name: 'label with newline and katex' }
)
This follows the pattern of the existing katex.spec.js tests.


Security

No XSS or injection issues introduced. The \n →
substitution adds a hardcoded safe HTML tag — not user input — and the result flows through renderKatexSanitized which always applies
DOMPurify.sanitize() before returning. The span.html(sanitizedLabel) sink in addHtmlSpan (the D3 innerHTML equivalent) only ever receives DOMPurify-cleaned content — this is unchanged by the PR.


This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Bug / Error Something isn't working or is incorrect

Projects

None yet

Development

Successfully merging this pull request may close these issues.

<br/> line breaks are ignored in flowchart labels containing $$ math expressions Impossible to add newlines to flowchart nodes with maths in them

2 participants