Skip to content

Commit 3f08da0

Browse files
committed
refactor(web): keep existing diff rendering in chat
1 parent c17ef01 commit 3f08da0

5 files changed

Lines changed: 27 additions & 138 deletions

File tree

‎apps/web/src/components/ChatMarkdown.test.tsx‎

Lines changed: 24 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { describe, expect, it, vi } from "vite-plus/test";
66

77
import { getSyntaxHighlighterPromise } from "../lib/syntaxHighlighting";
88
import * as mermaidRendering from "../lib/mermaidRendering";
9-
import { MarkdownDiffBlock } from "./MarkdownDiffBlock";
109
import { MarkdownMermaidBlock } from "./MarkdownMermaidBlock";
1110
import { GitHubIcon } from "./Icons";
1211
import { Button } from "./ui/button";
@@ -75,91 +74,52 @@ function codeButton(renderer: ReactTestRenderer, label: string) {
7574
return button.props as ComponentProps<typeof Button>;
7675
}
7776

78-
describe("ChatMarkdown code previews", () => {
79-
it.each(["diff", "PATCH"])(
80-
"switches %s snippets between preview and source without changing copied code",
81-
async (language) => {
82-
const source = " unchanged\n-old\n+new\n";
83-
const writeText = vi.fn(async (_text: string) => {});
84-
vi.stubGlobal("navigator", { clipboard: { writeText } });
85-
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
86-
let renderer: ReactTestRenderer | undefined;
87-
try {
88-
await act(async () => {
89-
renderer = create(
90-
<ChatMarkdown
91-
cwd="/tmp/project"
92-
text={`\`\`\`${language}\n${source}\`\`\``}
93-
parseRawHtml={false}
94-
/>,
95-
);
96-
});
97-
const preview = renderer!.root.findByType(MarkdownDiffBlock);
98-
expect(
99-
preview
100-
.findAllByType("span")
101-
.flatMap((line) => line.children)
102-
.join(""),
103-
).toBe(source);
104-
const showSource = codeButton(renderer!, "Show source");
105-
await act(async () => {
106-
showSource.onClick?.({} as Parameters<NonNullable<typeof showSource.onClick>>[0]);
107-
});
108-
expect(renderer!.root.findAllByType(MarkdownDiffBlock)).toHaveLength(0);
109-
const copy = codeButton(renderer!, "Copy code");
110-
await act(async () => {
111-
copy.onClick?.({} as Parameters<NonNullable<typeof copy.onClick>>[0]);
112-
});
113-
expect(writeText).toHaveBeenCalledWith(source);
114-
const showPreview = codeButton(renderer!, "Show preview");
115-
await act(async () => {
116-
showPreview.onClick?.({} as Parameters<NonNullable<typeof showPreview.onClick>>[0]);
117-
});
118-
expect(
119-
renderer!.root
120-
.findByType(MarkdownDiffBlock)
121-
.findAllByType("span")
122-
.flatMap((line) => line.children)
123-
.join(""),
124-
).toBe(source);
125-
} finally {
126-
await act(async () => renderer?.unmount());
127-
vi.unstubAllGlobals();
128-
vi.restoreAllMocks();
129-
}
130-
},
131-
);
132-
77+
describe("ChatMarkdown Mermaid previews", () => {
13378
it("renders Mermaid only after streaming ends and lets the user return to its source", async () => {
13479
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
80+
const writeText = vi.fn(async (_text: string) => {});
81+
vi.stubGlobal("navigator", { clipboard: { writeText } });
13582
const renderDiagram = vi
13683
.spyOn(mermaidRendering, "renderMermaidDiagram")
13784
.mockResolvedValue("<svg><text>Draft</text></svg>");
13885
const text = "```mermaid\nflowchart LR\nDraft-->Review\n```";
13986
let renderer: ReactTestRenderer | undefined;
14087
try {
14188
await act(async () => {
142-
renderer = create(<ChatMarkdown cwd="/tmp/project" text={text} isStreaming />, {
143-
createNodeMock: (element) =>
144-
element.type === "div" &&
145-
(element.props as ComponentProps<"div">).className === "chat-markdown-mermaid"
146-
? {}
147-
: null,
148-
});
89+
renderer = create(
90+
<ChatMarkdown cwd="/tmp/project" text={text} isStreaming parseRawHtml={false} />,
91+
{
92+
createNodeMock: (element) =>
93+
element.type === "div" &&
94+
(element.props as ComponentProps<"div">).className === "chat-markdown-mermaid"
95+
? {}
96+
: null,
97+
},
98+
);
14999
});
150100
expect(renderDiagram).not.toHaveBeenCalled();
151101
expect(renderer!.root.findAllByType(MarkdownMermaidBlock)).toHaveLength(0);
152102
await act(async () => {
153-
renderer!.update(<ChatMarkdown cwd="/tmp/project" text={text} />);
103+
renderer!.update(<ChatMarkdown cwd="/tmp/project" text={text} parseRawHtml={false} />);
154104
});
155105
expect(
156106
renderer!.root.findByProps({ role: "img" }).props.dangerouslySetInnerHTML.__html,
157107
).toContain("Draft");
108+
const copyPreview = codeButton(renderer!, "Copy code");
109+
await act(async () => {
110+
copyPreview.onClick?.({} as Parameters<NonNullable<typeof copyPreview.onClick>>[0]);
111+
});
112+
expect(writeText).toHaveBeenLastCalledWith("flowchart LR\nDraft-->Review\n");
158113
const showSource = codeButton(renderer!, "Show source");
159114
await act(async () => {
160115
showSource.onClick?.({} as Parameters<NonNullable<typeof showSource.onClick>>[0]);
161116
});
162117
expect(renderer!.root.findAllByProps({ role: "img" })).toHaveLength(0);
118+
const copySource = codeButton(renderer!, "Copied");
119+
await act(async () => {
120+
copySource.onClick?.({} as Parameters<NonNullable<typeof copySource.onClick>>[0]);
121+
});
122+
expect(writeText).toHaveBeenLastCalledWith("flowchart LR\nDraft-->Review\n");
163123
const showPreview = codeButton(renderer!, "Show preview");
164124
await act(async () => {
165125
showPreview.onClick?.({} as Parameters<NonNullable<typeof showPreview.onClick>>[0]);

‎apps/web/src/components/ChatMarkdown.tsx‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ import { LRUCache } from "../lib/lruCache";
122122
import { getSyntaxHighlighterPromise } from "../lib/syntaxHighlighting";
123123
import { GitHubIcon } from "./Icons";
124124
import { RenderErrorBoundary } from "./RenderErrorBoundary";
125-
import { MarkdownDiffBlock } from "./MarkdownDiffBlock";
126125
import { MarkdownMermaidBlock } from "./MarkdownMermaidBlock";
127126
import { useTheme } from "../hooks/useTheme";
128127
import { getClientSettings, useClientSettings } from "../hooks/useSettings";
@@ -523,7 +522,6 @@ const GITHUB_ALERT_PRESENTATIONS: Record<
523522
function extractFenceLanguage(className: string | undefined): string {
524523
const match = className?.match(CODE_FENCE_LANGUAGE_REGEX);
525524
const raw = match?.[1] ?? "text";
526-
if (raw.toLowerCase() === "patch") return "diff";
527525
// Shiki doesn't bundle a gitignore grammar; ini is a close match (#685)
528526
return raw === "gitignore" ? "ini" : raw;
529527
}
@@ -3113,9 +3111,7 @@ const CHAT_MARKDOWN_COMPONENTS = {
31133111
fenceTitle={fenceTitle}
31143112
theme={resolvedTheme}
31153113
preview={
3116-
language.toLowerCase() === "diff" || language.toLowerCase() === "patch" ? (
3117-
<MarkdownDiffBlock code={codeBlock.code} />
3118-
) : language.toLowerCase() === "mermaid" && !isStreaming ? (
3114+
language.toLowerCase() === "mermaid" && !isStreaming ? (
31193115
<MarkdownMermaidBlock code={codeBlock.code} theme={resolvedTheme} />
31203116
) : undefined
31213117
}

‎apps/web/src/components/MarkdownDiffBlock.tsx‎

Lines changed: 0 additions & 31 deletions
This file was deleted.

‎apps/web/src/index.css‎

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1875,32 +1875,6 @@ code {
18751875
background: transparent !important;
18761876
}
18771877

1878-
.chat-markdown-diff code {
1879-
display: block;
1880-
min-width: fit-content;
1881-
}
1882-
1883-
.chat-markdown-diff [data-diff-line] {
1884-
display: block;
1885-
min-height: 1lh;
1886-
}
1887-
1888-
.chat-markdown-diff [data-diff-line="added"] {
1889-
background: color-mix(in srgb, var(--color-green-500) 15%, transparent);
1890-
color: light-dark(var(--color-green-800), var(--color-green-300));
1891-
}
1892-
1893-
.chat-markdown-diff [data-diff-line="removed"] {
1894-
background: color-mix(in srgb, var(--color-red-500) 15%, transparent);
1895-
color: light-dark(var(--color-red-800), var(--color-red-300));
1896-
}
1897-
1898-
.chat-markdown-diff [data-diff-line="header"],
1899-
.chat-markdown-diff [data-diff-line="hunk"] {
1900-
color: var(--muted-foreground);
1901-
background: color-mix(in srgb, var(--foreground) 5%, transparent);
1902-
}
1903-
19041878
/* Diagnostics-style tables: row separators only, uppercase headers, and a
19051879
scroll-fade container for horizontal overflow. The root chat-markdown
19061880
wrapping rules (overflow-wrap: anywhere) would let columns shrink to single

‎docs/user/composer.md‎

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,9 @@ include a skill when the task needs more context.
66
Messages can contain up to 120,000 characters. Longer drafts stay in the composer
77
so you can shorten them or split them into several messages.
88

9-
## Diffs and diagrams
9+
## Mermaid diagrams
1010

11-
On web and desktop, use a fenced `diff` or `patch` block to display additions and
12-
removals. Short snippets work without file headers:
13-
14-
````markdown
15-
```diff
16-
-const retries = 1;
17-
+const retries = 3;
18-
```
19-
````
20-
21-
Use a `mermaid` fence to display a diagram:
11+
On web and desktop, use a `mermaid` fence to display a diagram:
2212

2313
````markdown
2414
```mermaid

0 commit comments

Comments
 (0)