Render Mermaid diagrams instead of describing them in ASCII - #1385
Merged
Paul Lizer (paullizer) merged 3 commits intoSep 2, 2026
Merged
Conversation
Asking for a diagram came back as box art in a ```text fence. Nothing told the model that SimpleChat renders ```mermaid, so it drew with keyboard characters, and the chart guidance flatly banned Mermaid on top of that. Add intent-detected diagram guidance alongside the existing chart and image guidance, wired into all three generation paths, and scope the chart guidance so it only rules out Mermaid as a substitute for a data chart. The classic client also never rendered a mermaid fence in chat, only for export. Add inline rendering through the same pipeline as inline charts, with a pending placeholder while streaming and a source fallback when parsing fails. mermaid.initialize is global and render reads it back, so inline chat rendering would have decided the theme and sizing for export PNGs. Both now go through one runtime that applies its preset immediately before each render and serialises them. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The V2 chat notices work landed on the base branch and claimed 0.261.028, the same version this branch used. Git merged config.py cleanly because both sides wrote the identical line, so the collision was only visible in release_notes.md. Their version stands, since it landed first. This work moves to 0.261.029, in config.py and in the test headers, version assertions and documentation that name it. Their release notes section is kept intact under 0.261.028 with a new 0.261.029 section above it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The inline image proposal work landed on the base branch and took 0.261.029, the version this branch had moved to after the previous merge. As before, git merged config.py cleanly because both sides wrote the identical line, so the collision only surfaced in release_notes.md. This work moves to 0.261.031, coordinated with the inline image proposal and conversation id sessions so 0.261.029 and 0.261.030 stay with them. The base sections for 0.261.029 and 0.261.028 are preserved untouched, with a new 0.261.031 section above them. Their changes to richBlocks.ts and AssistantMarkdown.tsx are additive: mermaid is still in RICH_FENCE_LANGUAGES and PENDING_LANGUAGES, so markPendingFences still guards a half-streamed fence, and the code renderer still routes mermaid to MermaidDiagram before the chart and image proposal branches. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer)
added a commit
that referenced
this pull request
Sep 2, 2026
PR #1385 (Mermaid diagram rendering) landed first and took 0.261.031, so this branch's 0.261.030 was no longer ahead of the base and both sides claimed a changelog section at the top. The version moves to 0.261.032 in all four places that carry it -- config.py, the release note section heading, the test's docstring header and its assert_app_version_at_least floor -- rather than adopting the base's 031 and leaving this feature documented under a number that never shipped containing it. The base's VERSION must strictly increase on every merge into it; keeping 031 would have left two different base commits indistinguishable by version, which is the one thing a running deployment can report about itself. The gap where 030 was costs nothing. Note that 032 is on neither side of the conflict: this is not a pick-one resolution, which is the shape most easily resolved by reflex. Changelog reads 032 -> 031 -> 029 -> 028, with the diagram sections intact and this branch's delta being additions only. Verified: no leftover markers, no lingering 030 reference anywhere, the V2 suite (24 Python files plus the inline image proposal runtime checks) passes, the two diagram tests arriving with the base pass, both docs tests pass, and the bundle builds. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Asking for a diagram came back as ASCII box art in a ```text fence: unreadable, not exportable as a picture, meaningless to a screen reader.
This is not a Mermaid rendering bug. The renderer never saw a mermaid fence.
Two causes
1. Nothing told the model that ```mermaid renders. SimpleChat already attaches capability guidance for inline charts and image proposals when a request calls for one. Diagrams had none, so the model fell back on drawing with keyboard characters. The chart guidance made it worse — it ended with "Do not output Mermaid, matplotlib/Python, Vega, or other chart code blocks", steering the model away from diagrams on any analytical request.
2. The classic UI never rendered mermaid in chat. Mermaid was wired only into the export rasterizer, so a diagram became a picture in an exported Word file or PDF while the same diagram stayed a code block in the conversation it came from. V2 (
MermaidDiagram.tsx) was already fine.Changes
Backend
New
functions_diagram_operations.py, mirroringfunctions_chart_operations.py:user_requested_diagram()— matches diagram, flowchart, sequence, architecture, data flow, ERD, state machine, mind map, and phrasings like "turn this into a diagram" or "map out the architecture".Detection is deliberately narrow so it does not compete with chart detection:
draw the request flowgive me an ERD for the orders tablevisualize revenue by monthplot the sales trendmaybe_append_diagram_system_message()is wired into all three generation paths (agent, model fallback, streaming), inserted after existing system messages and never duplicated.The chart guidance is rescoped: Mermaid is barred only as a substitute for a data chart, and structural diagrams are explicitly still expected to use a ```mermaid fence.
Classic UI
chat-inline-diagrams.jsfollows the same extract → tokenize → sanitize → inject → hydrate pipeline aschat-inline-charts.js. Fences are lifted out beforemarkedparses, which also protects diagram source from the table-conversion passes.securityLevel: 'strict';bindFunctionsis never called.The non-obvious trap
mermaid.initialize()sets global config andmermaid.render()reads it back. Inline chat wants a theme-aware,useMaxWidthdiagram; export wants neutral and fixed-size, because a dark, width-constrained diagram does not survive being painted onto a canvas. Whichever configured last would have decided for both — meaning inline rendering could silently corrupt export PNGs.chat-mermaid-runtime.jsnow owns the library: it loads the 3.4 MB bundle once (lazily), applies the caller's preset immediately before each render, and serialises renders so config always stays paired with the diagram that asked for it. The rasterizer requestsexport, chat requestsinline. V2 already solves this the same way internally.Also fixed
Reply previews and text-to-speech were reading internal chart/diagram/image placeholder tokens aloud. Now stripped.
Coverage
textfencemermaidfenceV2 needed no rendering change — it picks up the guidance and starts receiving diagrams it could already draw.
Testing
10 suites pass:
test_mermaid_diagram_prompt_guidance.py(new) — intent detection including chart requests that must not trigger it, guidance content, idempotent insertion, wiring on all three paths, and that chart guidance no longer contradicts diagram guidance.test_chat_inline_diagram_rendering.py(new) — headless Chromium renders a real diagram and asserts its labels survive; pending placeholder, source fallback, render cache, teardown, fence-detection parity with the rasterizer, and no request leaves the origin.test_export_mermaid_browser_rasterizer.py,test_export_mermaid_server_render.py,test_conversation_export_mermaid_tex_images.py— exports unchanged.test_chart_tool_prompt_handoff.py,test_v2_rich_rendering.py,test_browser_alert_replacement.py,test_docs_app_surface_coverage.py,test_docs_site_quality.py.Two export tests pinned Mermaid config strings to the rasterizer file. Those assertions moved to the runtime, preserving their intent (browser and server renderers must still agree on
htmlLabels: false,securityLevel: 'strict',suppressErrorRendering: true, neutral theme).Notes
0.261.027→0.261.028.enable_*setting and no new chat control, so the generated docs inventory is untouched.MERMAID_DIAGRAM_RENDERING.md, corrected the now-stale "the classic chat interface does not render Mermaid" claim in the export doc, plus release notes.