diff --git a/site/src/components/ai-elements/response.stories.tsx b/site/src/components/ai-elements/response.stories.tsx index cef2259010..5a2392567c 100644 --- a/site/src/components/ai-elements/response.stories.tsx +++ b/site/src/components/ai-elements/response.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; +import { expect, within } from "storybook/test"; import { Response } from "./response"; const sampleMarkdown = ` @@ -66,3 +67,39 @@ export const MarkdownAndLinksLight: Story = { theme: "light", }, }; + +// Verifies that JSX-like syntax in LLM output is preserved as +// escaped text rather than being swallowed by the HTML pipeline. +const jsxProseMarkdown = ` +\`getLineAnnotations\` depends on \`activeCommentBox\` which could shift. + + + +The props that might change on every \`RemoteDiffPanel\` re-render: +- \`isLoading\` only during refetch +- \`getLineAnnotations\` only when \`activeCommentBox\` changes +`; + +export const JsxInProse: Story = { + args: { + children: jsxProseMarkdown, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + // These strings live inside the JSX block. + // Without the rehype-raw fix they are silently eaten by the + // HTML sanitizer and never reach the DOM. + // The tag name itself is the token most likely to be consumed + // by HTML parsing, so assert it explicitly. + const tagName = await canvas.findByText(/, "children"> { @@ -13,6 +18,16 @@ interface ResponseProps extends Omit, "children"> { urlTransform?: UrlTransform; } +// Omit rehype-raw so HTML-like syntax in LLM output is rendered as +// escaped text instead of being parsed by the HTML5 engine. Without +// this, JSX fragments such as are +// consumed by rehype-raw and then stripped by rehype-sanitize, +// silently destroying content mid-stream. +const chatRehypePlugins = [ + defaultRehypePlugins.sanitize, + defaultRehypePlugins.harden, +]; + const fileViewerCSS = "pre, [data-line], [data-diffs-header] { background-color: transparent !important; }"; @@ -241,6 +256,7 @@ export const Response = ({ controls={false} components={components} urlTransform={urlTransform} + rehypePlugins={chatRehypePlugins} > {children}