Problem
Three kinds of block that SimpleChat already produces are rendered as raw text in the V2 React chat (application/v2_ui).
1. Mermaid diagrams
This is the least obvious and arguably the most impactful. Azure Content Understanding already extracts document figures as Mermaid and writes them into page content as fenced blocks:
# application/single_app/functions_content_understanding.py:330
if figure_kind == 'mermaid' and isinstance(figure_content, str) and figure_content.strip():
diagram_block = f"```mermaid\n{figure_content.strip()}\n```"
So a user who uploads a document containing a flowchart and asks about it gets diagram source pasted into the middle of the answer. Nothing renders it in either interface.
2. TeX
Models emit \( ... \), \[ ... \] and $$ ... $$. None of it renders, so any answer involving mathematics arrives as unreadable markup.
3. SimpleChart
The built-in chart action emits ```simplechart fences (functions_chart_operations.py:191). The classic interface renders these via static/js/chat/chat-inline-charts.js; V2 shows the raw JSON payload — often several kilobytes of it — in the middle of the reply.
Scope
V2 React UI only. The classic interface is unchanged.
Constraints
- No new remote browser code. Third-party browser libraries must be downloaded and committed to the repository so the bytes the browser executes are pinned and any change to them is a reviewable commit rather than a silent substitution. This rules out resolving them from npm at build time as well as loading them from a CDN at run time.
- The
Content-Security-Policy in config.py (default-src 'self', script-src 'self', font-src 'self') must not be relaxed.
- Diagram and equation source is untrusted model output, so anything rendered from it needs an explicit sanitizer boundary — V2 currently uses
dangerouslySetInnerHTML nowhere.
- A single
$...$ must not be treated as inline maths, or ordinary prose about money ("costs $5 to $10 per user") renders as an equation.
Acceptance criteria
Resolution
Implemented in v0.261.019.
- Libraries vendored under
application/v2_ui/public/vendor/<library>-<version>/: KaTeX 0.18.4, Mermaid 11.17.2, Chart.js 4.5.1, DOMPurify 3.4.14. All files verified against the registry's published SHA-256, licences preserved, and loaded on demand so a conversation with no maths, diagram or chart downloads none of them. No npm dependencies were added.
remark-math was deliberately not used: CommonMark treats a backslash before ASCII punctuation as an escape, so \(x\) is already the literal text (x) by the time an mdast node exists and a post-parse plugin cannot see the delimiters. Maths instead reuses the placeholder mechanism the renderer already has for citations and masks.
- KaTeX runs with
trust: false; Mermaid with securityLevel: 'strict', htmlLabels: false, no autostart and no icon packs. Both pass through DOMPurify before injection — the only two dangerouslySetInnerHTML sites in the SPA, enforced by test.
- The markdown renderer moved from
MessageList.tsx into its own AssistantMarkdown.tsx.
Verified in a real browser across light and dark themes. Covered by functional_tests/test_v2_rich_rendering.py; the full V2 suite (18 files) passes.
Documentation: docs/explanation/features/REACT_V2_UI.md, docs/explanation/release_notes.md.
Problem
Three kinds of block that SimpleChat already produces are rendered as raw text in the V2 React chat (
application/v2_ui).1. Mermaid diagrams
This is the least obvious and arguably the most impactful. Azure Content Understanding already extracts document figures as Mermaid and writes them into page content as fenced blocks:
So a user who uploads a document containing a flowchart and asks about it gets diagram source pasted into the middle of the answer. Nothing renders it in either interface.
2. TeX
Models emit
\( ... \),\[ ... \]and$$ ... $$. None of it renders, so any answer involving mathematics arrives as unreadable markup.3. SimpleChart
The built-in chart action emits
```simplechartfences (functions_chart_operations.py:191). The classic interface renders these viastatic/js/chat/chat-inline-charts.js; V2 shows the raw JSON payload — often several kilobytes of it — in the middle of the reply.Scope
V2 React UI only. The classic interface is unchanged.
Constraints
Content-Security-Policyinconfig.py(default-src 'self',script-src 'self',font-src 'self') must not be relaxed.dangerouslySetInnerHTMLnowhere.$...$must not be treated as inline maths, or ordinary prose about money ("costs $5 to $10 per user") renders as an equation.Acceptance criteria
$$...$$,\[...\]and\(...\)render as maths; a lone$does not```mermaidfences render as diagrams```simplechartfences render as charts, with the underlying data reachable and an image downloadResolution
Implemented in v0.261.019.
application/v2_ui/public/vendor/<library>-<version>/: KaTeX 0.18.4, Mermaid 11.17.2, Chart.js 4.5.1, DOMPurify 3.4.14. All files verified against the registry's published SHA-256, licences preserved, and loaded on demand so a conversation with no maths, diagram or chart downloads none of them. No npm dependencies were added.remark-mathwas deliberately not used: CommonMark treats a backslash before ASCII punctuation as an escape, so\(x\)is already the literal text(x)by the time an mdast node exists and a post-parse plugin cannot see the delimiters. Maths instead reuses the placeholder mechanism the renderer already has for citations and masks.trust: false; Mermaid withsecurityLevel: 'strict',htmlLabels: false, no autostart and no icon packs. Both pass through DOMPurify before injection — the only twodangerouslySetInnerHTMLsites in the SPA, enforced by test.MessageList.tsxinto its ownAssistantMarkdown.tsx.Verified in a real browser across light and dark themes. Covered by
functional_tests/test_v2_rich_rendering.py; the full V2 suite (18 files) passes.Documentation:
docs/explanation/features/REACT_V2_UI.md,docs/explanation/release_notes.md.