From fbbf343286dd24fa34d112f36bdf755d25f09a46 Mon Sep 17 00:00:00 2001
From: Marco Beretta <81851188+berry-13@users.noreply.github.com>
Date: Tue, 10 Nov 2026 13:21:24 +0100
Subject: [PATCH] fix: Keep Untagged Chat Code Fences Plain (#17986)
---
.../Content/MarkdownErrorBoundary.tsx | 2 +-
.../Chat/Messages/Content/MarkdownLite.tsx | 2 +-
.../__tests__/MarkdownHighlight.test.tsx | 108 ++++++++++++++++++
.../Chat/Messages/Content/markdownConfig.ts | 2 +-
4 files changed, 111 insertions(+), 3 deletions(-)
create mode 100644 client/src/components/Chat/Messages/Content/__tests__/MarkdownHighlight.test.tsx
diff --git a/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx b/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx
index 3d65a2ae14..02d9626e0d 100644
--- a/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx
+++ b/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx
@@ -68,7 +68,7 @@ class MarkdownErrorBoundary extends React.Component<
[
rehypeHighlight,
{
- detect: true,
+ detect: false,
ignoreMissing: true,
subset: langSubset,
},
diff --git a/client/src/components/Chat/Messages/Content/MarkdownLite.tsx b/client/src/components/Chat/Messages/Content/MarkdownLite.tsx
index 54bd0eab31..4ff28dcd63 100644
--- a/client/src/components/Chat/Messages/Content/MarkdownLite.tsx
+++ b/client/src/components/Chat/Messages/Content/MarkdownLite.tsx
@@ -34,7 +34,7 @@ const MarkdownLite = memo(
[
rehypeHighlight,
{
- detect: true,
+ detect: false,
ignoreMissing: true,
subset: langSubset,
},
diff --git a/client/src/components/Chat/Messages/Content/__tests__/MarkdownHighlight.test.tsx b/client/src/components/Chat/Messages/Content/__tests__/MarkdownHighlight.test.tsx
new file mode 100644
index 0000000000..6405b8f106
--- /dev/null
+++ b/client/src/components/Chat/Messages/Content/__tests__/MarkdownHighlight.test.tsx
@@ -0,0 +1,108 @@
+import React from 'react';
+import { RecoilRoot } from 'recoil';
+import ReactMarkdown from 'react-markdown';
+import { render } from '@testing-library/react';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { getRehypePlugins, getMarkdownComponents } from '../markdownConfig';
+import { ArtifactProvider, CodeBlockProvider } from '~/Providers';
+import MarkdownErrorBoundary from '../MarkdownErrorBoundary';
+import MarkdownLite from '../MarkdownLite';
+import Markdown from '../Markdown';
+
+jest.mock('~/components/Messages/Content/CodeBlock', () => ({
+ __esModule: true,
+ default: ({ lang, codeChildren }: { lang: string; codeChildren: React.ReactNode }) => (
+ {codeChildren}
+ ),
+}));
+
+const WholeMessage = ({ content }: { content: string }) => (
+
+
+ ['rehypePlugins']
+ }
+ components={getMarkdownComponents()}
+ >
+ {content}
+
+
+
+);
+
+const Throw = (): never => {
+ throw new Error('Exercise markdown fallback');
+};
+
+const Fallback = ({
+ content,
+ codeExecution = true,
+}: {
+ content: string;
+ codeExecution?: boolean;
+}) => (
+
+
+
+);
+
+const renderers = [
+ ['whole message', WholeMessage],
+ ['blocks', (props: { content: string }) => ],
+ ['lite', MarkdownLite],
+ [
+ 'lite without execution',
+ (props: { content: string }) => ,
+ ],
+ ['fallback', Fallback],
+ [
+ 'fallback without execution',
+ (props: { content: string }) => ,
+ ],
+] as const;
+
+const prose = 'Pick up milk, eggs and bread on the way home. Call the dentist: Tuesday at 3.';
+
+describe.each(renderers)('%s highlighting', (_name, Renderer) => {
+ beforeEach(() => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+ });
+
+ const renderContent = (content: string) => {
+ const { container } = render(
+
+
+
+
+ ,
+ );
+ return container.querySelector('code');
+ };
+
+ it.each([
+ ['', prose, true],
+ ['', prose, false],
+ ['', 'const answer = 42;', true],
+ ['text', prose, true],
+ ['plaintext', prose, true],
+ ['unknownlang', prose, true],
+ ])('keeps %s fences plain for %s (closed: %s)', (language, content, closed) => {
+ const code = renderContent(`\`\`\`${language}\n${content}\n${closed ? '```' : ''}`);
+ expect(code).toHaveAttribute('data-language', language || 'text');
+ expect(code?.textContent).toBe(`${content}\n`);
+ expect(code?.querySelector('[class*="hljs-"]')).toBeNull();
+ });
+
+ it('retains explicit Python highlighting', () => {
+ const code = renderContent('```python\nprint("hello")\n```');
+ expect(code).toHaveAttribute('data-language', 'python');
+ expect(code?.querySelector('[class*="hljs-"]')).not.toBeNull();
+ });
+
+ it('keeps inline code inline', () => {
+ const code = renderContent('Use `const answer = 42;` here.');
+ expect(code).not.toHaveAttribute('data-language');
+ expect(code?.textContent).toBe('const answer = 42;');
+ });
+});
diff --git a/client/src/components/Chat/Messages/Content/markdownConfig.ts b/client/src/components/Chat/Messages/Content/markdownConfig.ts
index dbf0fa9700..db082d0207 100644
--- a/client/src/components/Chat/Messages/Content/markdownConfig.ts
+++ b/client/src/components/Chat/Messages/Content/markdownConfig.ts
@@ -65,7 +65,7 @@ export const getRemarkPlugins = (latexParsing = true): PluggableList => [
export const getRehypePlugins = (): PluggableList => [
[rehypeKatex],
- [rehypeHighlight, { detect: true, ignoreMissing: true, subset: langSubset }],
+ [rehypeHighlight, { detect: false, ignoreMissing: true, subset: langSubset }],
];
export const getMarkdownComponents = (): { [nodeType: string]: ElementType } => ({