Skip to content

fix(c4): allow hash characters in titles - #8031

Open
cyphercodes wants to merge 2 commits into
mermaid-js:developfrom
cyphercodes:fix-c4-title-hash-8030
Open

cyphercodes wants to merge 2 commits into
mermaid-js:developfrom
cyphercodes:fix-c4-title-hash-8030

Conversation

@cyphercodes

@cyphercodes cyphercodes commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

📑 Summary

Fixes #8030.

Allows C4 diagram titles to include # characters. Previously, the C4 lexer stopped title tokens at #, so titles like Sprint #3 architecture caused a parse error.

📏 Design Decisions

Only the title lexer rule is relaxed; comment handling and other metadata lexer rules are unchanged.

📋 Tasks

  • Add regression coverage for C4 titles containing #
  • Add a patch changeset

🧪 How to test

pnpm exec vitest run packages/mermaid/src/diagrams/c4/parser/c4Diagram.spec.ts
pnpm lint:jison
pnpm exec prettier --check packages/mermaid/src/diagrams/c4/parser/c4Diagram.spec.ts .changeset/c4-title-hash.md
git diff --check

What's working well

  • 🎉 [praise] The C4 lexer accepts # in titles and accumulated descriptions.
  • 🎉 [praise] The lexer still stops these tokens at newlines and semicolons.
  • 🎉 [praise] Regression tests cover multiple title formats, including Boundary (C4 diagram not possible to create relation from boundaries to container  #4864) and issue#4864.
  • 🎉 [praise] Comment-sensitive lexer rules remain unchanged.
  • 🎉 [praise] The patch changeset uses the requested fix(c4): prefix.

Security

  • No XSS or injection issues identified.

Review verdict

APPROVE

@changeset-bot

changeset-bot Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 31cc59d

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 Aug 6, 2026 •

Copy link
Copy Markdown

❌ Deploy Preview for mermaid-js failed.

Name Link
🔨 Latest commit 31cc59d
🔍 Latest deploy log https://app.netlify.com/projects/mermaid-js/deploys/6a75927bf7f4c30008745fcc

@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b7cdeedd-dbdd-493d-913c-21ec66db50b6

📥 Commits

Reviewing files that changed from the base of the PR and between 29122e4 and 31cc59d.

📒 Files selected for processing (3)
  • .changeset/c4-title-hash.md
  • packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison
  • packages/mermaid/src/diagrams/c4/parser/c4Diagram.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison
  • .changeset/c4-title-hash.md

📝 Walkthrough

Walkthrough

The C4 lexer now accepts # characters in title and accessibility-description text. Parser tests verify that the values remain unchanged. A changeset documents the patch release.

Changes

C4 title parsing

Layer / File(s) Summary
Title lexer and parser validation
packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison, packages/mermaid/src/diagrams/c4/parser/c4Diagram.spec.ts, .changeset/c4-title-hash.md
The C4 lexer accepts # characters in titles and accessibility descriptions. Parser tests verify both values. The changeset documents the patch release.

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

Suggested labels: Graph: C4, Type: Bug / Error

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main C4 parser fix.
Description check ✅ Passed The description explains the fix, design, tests, and changeset, while omitting only some non-critical template checklist items.
Linked Issues check ✅ Passed The changes fix C4 title parsing for '#' characters and add regression tests for the linked issue [#8030].
Out of Scope Changes check ✅ Passed The lexer updates and regression tests remain within the C4 hash-character parsing scope described by [#8030].
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 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.

@knsv-bot knsv-bot 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.

[sisyphus-bot]

Thanks for this — and for the issue that came with it. #8030 is one of the better bug reports we've had: root-caused to the exact line, with a cross-diagram comparison table and a check that # isn't actually reserved in C4 (comments are %%). The PR then does exactly what that analysis pointed at, and nothing more. 🎉

What's working well

I verified the change locally rather than taking it on faith:

  • 🎉 The test genuinely captures the bug. Applying the spec change without the grammar fix fails with precisely the error from the issue (Lexical error on line 2. Unrecognized text. ...ontexttitle Sprint #3 architecturePers). With the grammar fix applied, the full C4 spec passes 4/4.
  • 🎉 All three failing forms from the issue table now parse: title Sprint #3 architecture → Sprint #3 architecture, title Boundary (#4864) → Boundary (#4864), title issue#4864 → issue#4864. Statement separation is intact — \n and ; are still excluded, so the following Person(...) line lexes normally.
  • 🎉 No security impact. # isn't an HTML/SVG-special character, and the title still flows through sanitizeText() (c4Db.ts:771) and is written with D3's .text() (c4Renderer.ts:611), which sets textContent rather than parsing HTML. No DOMPurify config touched, no new sinks. No XSS or injection concerns.

No e2e test needed here — this is parser-only, and repo convention is that unit tests suffice for parser/DB changes.

Things to address

🟡 [important] — accDescription on the next line has the identical bug

packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison:88 still reads:

"accDescription"\s[^#\n;]+                return 'accDescription';

Same [^#\n;] exclusion, same hard failure. Verified on your branch:

C4Context
accDescription Sprint #3 notes
Person(a, "A")

→ Lexical error on line 2. Unrecognized text.

What makes this worth raising rather than leaving alone: the colon forms already accept # (I checked — accTitle: Sprint #3 and accDescr: Sprint #3 notes both parse fine). So after this PR, C4 metadata ends up inconsistent:

syntax # allowed?
title ✅ (this PR)
accTitle: ✅ (already)
accDescr: ✅ (already)
accDescription ❌

It's the same one-character change on the adjacent line, and it's an accessibility field, so the failure mode is a bit worse than cosmetic. I can see from your Design Decisions section that scoping to title only was deliberate — so treat this as a request to reconsider, not a correction. Happy either way: fold it in here, or say the word and it becomes a follow-up. Mainly I'd rather it not sit around and resurface as a duplicate issue.

🟢 [nit] — changeset description prefix

.changeset/c4-title-hash.md — CLAUDE.md asks for a fix:/feat:/chore: prefix, and the sibling changesets all follow it (fix(architecture): allow non-ASCII characters..., fix(c4): named attributes...). Suggest:

fix(c4): allow `#` characters in C4 diagram titles

🟢 [nit] — lock in the other two forms from the issue

The test covers Sprint #3 architecture. The issue tabulated two more broken forms — (#4864) and issue#4864 (no space before the #). Both pass now, so this is purely about keeping them from regressing. Cheap to add as extra assertions in the same test.

Out of scope, just noting

Pre-existing and unchanged by this PR: title Foo %% note swallows the comment into the title (yielding "Foo %% note"), since % was never in the exclusion set either. Worth a separate issue if anyone cares to file one — definitely not for this PR.


Nice, tight fix. Once the accDescription question is settled this is good to go, (assuming the CI goes through). Thanks again for the unusually thorough issue writeup. 🙏

@cyphercodes

Copy link
Copy Markdown
Contributor Author

Updated this with the review follow-up:

  • allowed # in accDescription to match the adjacent C4 metadata forms
  • expanded title coverage for Boundary (#4864) and issue#4864
  • added the requested fix(c4): changeset prefix

Verification run locally:

  • pnpm install --frozen-lockfile
  • pnpm exec vitest run packages/mermaid/src/diagrams/c4/parser/c4Diagram.spec.ts
  • pnpm exec prettier --check packages/mermaid/src/diagrams/c4/parser/c4Diagram.spec.ts .changeset/c4-title-hash.md
  • pnpm lint:jison
  • git diff --check

@pkg-pr-new

pkg-pr-new Bot commented Aug 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

@mermaid-js/examples

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

mermaid

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

@mermaid-js/layout-elk

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

@mermaid-js/layout-tidy-tree

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

@mermaid-js/mermaid-zenuml

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

@mermaid-js/parser

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

@mermaid-js/tiny

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

commit: 31cc59d

@codecov

codecov Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.66%. Comparing base (3d521b1) to head (31cc59d).
⚠️ Report is 493 commits behind head on develop.

Files with missing lines Patch % Lines
...ges/mermaid/src/diagrams/c4/parser/c4Diagram.jison 0.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #8031      +/-   ##
===========================================
+ Coverage    77.61%   77.66%   +0.05%     
===========================================
  Files          568      568              
  Lines        74992    74992              
  Branches     12690    14655    +1965     
===========================================
+ Hits         58202    58246      +44     
+ Misses       15796    15753      -43     
+ Partials       994      993       -1     
Flag Coverage Δ
e2e 70.68% <0.00%> (+0.12%) ⬆️
unit 75.05% <ø> (ø)

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

Files with missing lines Coverage Δ
...ges/mermaid/src/diagrams/c4/parser/c4Diagram.jison 41.58% <0.00%> (ø)

... and 6 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 Aug 7, 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 - Aug 7, 2026, 8:28 AM

@knsv-bot knsv-bot 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.

[sisyphus-bot]

Thanks for this, @cyphercodes — and for the unusually thorough issue in #8030 that preceded it. The table you built there comparing # handling across sequenceDiagram / journey / gantt / pie / requirementDiagram is exactly the kind of scoping that makes a grammar change easy to trust, and it saved a lot of review time here. Sorry it sat for a few days.

The code is in good shape. I have one coordination item that needs a decision before this can land, and a couple of cosmetic notes.

What's working well

🎉 [praise] The fix is correctly minimal. # is not a comment character in C4 (comments are %%, c4Diagram.jison:98-99) and I confirmed it plays no other role in the grammar — after this change grep '#' c4Diagram.jison returns only lines 90 and 92, which are unrelated acc_title/acc_descr rules. Widening the character class really was the whole fix, and you resisted the temptation to restructure anything around it.

🎉 [praise] Keeping ; excluded ([^\n;]+ rather than [^\n]+) is the right conservative call. For reference, gantt.jison:90 uses the fully permissive "title"\s[^\n]+ — so this change moves C4 toward the established precedent without going all the way there in one step.

🎉 [praise] The tests genuinely capture the bug. I checked out the branch and ran the new spec against the unpatched develop grammar — all four cases fail with precisely the Lexical error on line 2. Unrecognized text. from the issue, and all pass with the grammar change applied. That's the TDD cycle done properly, not tests written to fit a fix.

🎉 [praise] The it.each covering three distinct # placements (mid-sentence Sprint #3 architecture, parenthesized Boundary (#4864), suffix issue#4864) maps directly onto the failing cases from the issue table, and extending coverage to accDescription was a good catch.

🎉 [praise] Changeset present, correct patch bump, correct fix(c4): prefix.

Regression check

I ran a before/after probe over eight edge cases to confirm the relaxed class doesn't change anything beyond the intended fix:

input before after
title System Context diagram OK OK (unchanged)
title Foo %% a comment OK, "Foo %% a comment" OK, identical (pre-existing; % was always allowed)
title Foo; bar lexical error lexical error (unchanged — ; still excluded)
title Foo direction TB parse error parse error (unchanged)
title Sprint #lt;3#gt; lexical error lexical error (unchanged)
title #<script>alert(1)</script> lexical error parses, title sanitized to "#"
title Foo # lexical error parses ✅

#-bearing titles are the only behavior change. Nothing else moved.

Security

No XSS or injection issues. The path was traced end to end: c4Diagram.jison:87 → production at :241 → c4Db.ts:770-773 setTitle → common.ts:82-94 sanitizeText (DOMPurify on every security level) → c4Renderer.ts:608-614 .append('text').text(title), a textContent sink. accDescription follows the equivalent path into accessibility.ts:57 svg.insert('desc').text(...).

Worth noting explicitly, since it's the natural worry with this change: encodeEntities (Diagram.ts:20) rewrites #word; sequences before the lexer runs, so the old [^#\n;]+ class never gated the entity path — it only blocked a bare #, which is inert in a textContent sink. Entity-based payloads behave identically before and after.

Things to address

🟡 [important] — PR #8039 is a duplicate of this one. @filipsajdak opened it a day later with a byte-identical c4Diagram.jison change and, awkwardly, the same changeset filename (.changeset/c4-title-hash.md). If both were to merge there'd be a collision on that file. Someone needs to pick one.

My read is that this PR should be the one that lands: it was opened first, and its test coverage is broader (three title placements via it.each plus accDescription, versus two single cases). Nothing in #8039 is missing here. This isn't something you did wrong — just flagging it so a maintainer can close the other one rather than leaving two identical PRs in the queue. A quick maintainer call on this would unblock the merge.

🟢 [nit] — The PR description says "Only the title lexer rule is relaxed", but commit 31cc59d also relaxes accDescription (c4Diagram.jison:88). Extending it there is the right thing to do — the bug is identical — the description just hasn't caught up. Worth a quick edit so the merge commit reflects the real scope.

🟢 [nit] — Same gap in .changeset/c4-title-hash.md: it mentions only titles. Since this text ends up in the published changelog, something like allow C4 titles and accDescription to include `#` characters would be more accurate.

💡 [suggestion] — Since the issue originated from a #4864 reference in a C4 rendering test in #7874, a single imgSnapshotTest case with a # in the title would close the loop nicely in cypress/integration/rendering/c4/c4.spec.js. Genuinely optional — this is a parser-only change and unit tests are the right level of coverage per our test strategy, so please don't feel obliged.

Out of scope (not for this PR)

c4Diagram.jison:90 and :92 contain (?!\n|;|#)* — a zero-width negative lookahead quantified with *, which matches empty and filters nothing. accTitle: and accDescr: consequently already accept # and ;. It's harmless (same sanitized .text() sink) but it's dead syntax that misleads anyone reading the grammar into thinking # is filtered there. Untouched by your change and best left to a separate cleanup — noting it only so it doesn't get lost.

CI

The four failing Netlify checks are not caused by this PR — the deploy preview is failing identically on #8047, #8045, #8041 and #8038. Everything else is green, including all 8 e2e shards, unit tests, lint, and Argos.

Once the #8039 overlap is sorted and the description/changeset wording is updated, this is ready to go. Nice, tight fix — let's get it across the finish line. 🚀


Review self-check
  • Severity tally: 0 🔴 / 1 🟡 / 2 🟢 / 1 💡 / 5 🎉
  • Verdict: COMMENT (1 🟡, no 🔴)
  • Not a draft; no prior bot review rounds
  • No inline comments used — all findings carry file:line references in the body

@knsv-bot

Copy link
Copy Markdown
Collaborator

[sisyphus-bot]

Correction to my review above — I wrote it without properly accounting for the earlier review round on this PR, and that was unfair to you.

Checking the history: every item from the 7 Aug review was addressed.

  • accDescription — asked you to reconsider folding it in; you did, in 31cc59d. So my nit about the PR description "not having caught up" has it backwards: the description was accurate when written, and the scope grew because of review feedback. Nothing for you to explain there — just worth updating the text at merge time so the commit reflects where it landed.
  • Changeset prefix — asked for fix(c4):; done. My remaining note is only about also naming accDescription in the changelog line, which is a much smaller point than I made it sound.
  • Extra test forms — asked to lock in (#4864) and issue#4864; you covered both, and went further by using it.each rather than bolting on assertions.

That's a clean round trip on all three. The substantive item in my review stands on its own and is not about your code: the duplicate PR #8039 still needs a maintainer to pick one. Everything else here is cosmetic.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

C4: a '#' in 'title' breaks parsing with a lexical error

2 participants