mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: respect light/dark theme in agents FilesChangedPanel diff viewer (#22403)
## Problem The git diff on the `/agents` page had color issues: the editor background followed light mode but the syntax highlighting used dark mode (`github-dark-high-contrast`), and the filename header used light-colored text on a light background. The root cause was hardcoded dark theme options in the `FileDiff` component: ```tsx themeType: "dark", theme: "github-dark-high-contrast", ``` ## Fix Uses the same theme-aware pattern as every other diff/file viewer in the codebase (`WriteFileTool`, `EditFilesTool`, `ReadFileTool`, `Tool`, `response.tsx`): 1. `useTheme()` from `@emotion/react` to read `palette.mode` 2. `getDiffViewerOptions(isDark)` from the shared `utils.ts` module — returns `github-light` theme for light mode, `github-dark-high-contrast` for dark mode 3. Reuses `DIFFS_FONT_STYLE` and `diffViewerCSS` constants instead of inlining duplicates ## Storybook coverage Added four new stories with real unified diff content: - **WithDiffDark** — dark mode with a PR link - **WithDiffLight** — light mode with a PR link - **NoPullRequestDark** — dark mode, "Files Changed" header - **NoPullRequestLight** — light mode, "Files Changed" header The existing stories only covered empty and parse-error states with no rendered diff.
This commit is contained in:
@@ -5,6 +5,46 @@ import type { ChatDiffContents } from "api/typesGenerated";
|
||||
import { expect, screen, spyOn } from "storybook/test";
|
||||
import { FilesChangedPanel } from "./FilesChangedPanel";
|
||||
|
||||
const sampleUnifiedDiff = `diff --git a/site/src/pages/AgentsPage/FilesChangedPanel.tsx b/site/src/pages/AgentsPage/FilesChangedPanel.tsx
|
||||
index abc1234..def5678 100644
|
||||
--- a/site/src/pages/AgentsPage/FilesChangedPanel.tsx
|
||||
+++ b/site/src/pages/AgentsPage/FilesChangedPanel.tsx
|
||||
@@ -1,10 +1,14 @@
|
||||
+import { useTheme } from "@emotion/react";
|
||||
import { parsePatchFiles } from "@pierre/diffs";
|
||||
import { FileDiff } from "@pierre/diffs/react";
|
||||
+import {
|
||||
+ DIFFS_FONT_STYLE,
|
||||
+ getDiffViewerOptions,
|
||||
+} from "components/ai-elements/tool/utils";
|
||||
import { chatDiffContents, chatDiffStatus } from "api/queries/chats";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { ScrollArea } from "components/ScrollArea/ScrollArea";
|
||||
-import { Skeleton } from "components/Skeleton/Skeleton";
|
||||
import { type FC, useMemo } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
diff --git a/site/src/components/ai-elements/tool/utils.ts b/site/src/components/ai-elements/tool/utils.ts
|
||||
index 1234567..abcdef0 100644
|
||||
--- a/site/src/components/ai-elements/tool/utils.ts
|
||||
+++ b/site/src/components/ai-elements/tool/utils.ts
|
||||
@@ -10,6 +10,18 @@ export const diffViewerCSS =
|
||||
export function getDiffViewerOptions(isDark: boolean) {
|
||||
return {
|
||||
diffStyle: "unified" as const,
|
||||
+ diffIndicators: "bars" as const,
|
||||
+ overflow: "scroll" as const,
|
||||
themeType: (isDark ? "dark" : "light") as "dark" | "light",
|
||||
+ theme: isDark ? "github-dark-high-contrast" : "github-light",
|
||||
+ unsafeCSS: diffViewerCSS,
|
||||
};
|
||||
}
|
||||
+
|
||||
+export const DIFFS_FONT_STYLE = {
|
||||
+ "--diffs-font-size": "11px",
|
||||
+ "--diffs-line-height": "1.5",
|
||||
+} as React.CSSProperties;
|
||||
`;
|
||||
|
||||
const defaultDiffStatus: ChatDiffStatusResponse = {
|
||||
chat_id: "test-chat",
|
||||
changes_requested: false,
|
||||
@@ -23,6 +63,13 @@ const meta: Meta<typeof FilesChangedPanel> = {
|
||||
args: {
|
||||
chatId: "test-chat",
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ height: 600, width: 500 }}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
beforeEach: () => {
|
||||
spyOn(API, "getChatDiffStatus").mockResolvedValue(defaultDiffStatus);
|
||||
spyOn(API, "getChatDiffContents").mockResolvedValue(defaultDiffContents);
|
||||
@@ -65,3 +112,73 @@ export const ParseError: Story = {
|
||||
expect(screen.getByText("No file changes to display.")).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDiffDark: Story = {
|
||||
beforeEach: () => {
|
||||
spyOn(API, "getChatDiffStatus").mockResolvedValue({
|
||||
...defaultDiffStatus,
|
||||
url: "https://github.com/coder/coder/pull/456",
|
||||
additions: 14,
|
||||
deletions: 2,
|
||||
changed_files: 2,
|
||||
});
|
||||
spyOn(API, "getChatDiffContents").mockResolvedValue({
|
||||
...defaultDiffContents,
|
||||
diff: sampleUnifiedDiff,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDiffLight: Story = {
|
||||
globals: {
|
||||
theme: "light",
|
||||
},
|
||||
beforeEach: () => {
|
||||
spyOn(API, "getChatDiffStatus").mockResolvedValue({
|
||||
...defaultDiffStatus,
|
||||
url: "https://github.com/coder/coder/pull/456",
|
||||
additions: 14,
|
||||
deletions: 2,
|
||||
changed_files: 2,
|
||||
});
|
||||
spyOn(API, "getChatDiffContents").mockResolvedValue({
|
||||
...defaultDiffContents,
|
||||
diff: sampleUnifiedDiff,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const NoPullRequestDark: Story = {
|
||||
beforeEach: () => {
|
||||
spyOn(API, "getChatDiffStatus").mockResolvedValue({
|
||||
...defaultDiffStatus,
|
||||
url: "https://github.com/coder/coder/pull/456",
|
||||
additions: 14,
|
||||
deletions: 2,
|
||||
changed_files: 2,
|
||||
});
|
||||
spyOn(API, "getChatDiffContents").mockResolvedValue({
|
||||
...defaultDiffContents,
|
||||
diff: sampleUnifiedDiff,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const NoPullRequestLight: Story = {
|
||||
globals: {
|
||||
theme: "light",
|
||||
},
|
||||
beforeEach: () => {
|
||||
spyOn(API, "getChatDiffStatus").mockResolvedValue({
|
||||
...defaultDiffStatus,
|
||||
url: "https://github.com/coder/coder/pull/456",
|
||||
additions: 14,
|
||||
deletions: 2,
|
||||
changed_files: 2,
|
||||
});
|
||||
spyOn(API, "getChatDiffContents").mockResolvedValue({
|
||||
...defaultDiffContents,
|
||||
diff: sampleUnifiedDiff,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { parsePatchFiles } from "@pierre/diffs";
|
||||
import { FileDiff } from "@pierre/diffs/react";
|
||||
import { chatDiffContents, chatDiffStatus } from "api/queries/chats";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import {
|
||||
DIFFS_FONT_STYLE,
|
||||
getDiffViewerOptions,
|
||||
} from "components/ai-elements/tool/utils";
|
||||
import { ScrollArea } from "components/ScrollArea/ScrollArea";
|
||||
import { Skeleton } from "components/Skeleton/Skeleton";
|
||||
import {
|
||||
@@ -33,6 +38,10 @@ function formatPullRequestLabel(url: string): string {
|
||||
}
|
||||
|
||||
export const FilesChangedPanel: FC<FilesChangedPanelProps> = ({ chatId }) => {
|
||||
const theme = useTheme();
|
||||
const isDark = theme.palette.mode === "dark";
|
||||
const diffOptions = useMemo(() => getDiffViewerOptions(isDark), [isDark]);
|
||||
|
||||
const diffStatusQuery = useQuery(chatDiffStatus(chatId));
|
||||
const diffContentsQuery = useQuery({
|
||||
...chatDiffContents(chatId),
|
||||
@@ -134,23 +143,14 @@ export const FilesChangedPanel: FC<FilesChangedPanelProps> = ({ chatId }) => {
|
||||
key={fileDiff.name}
|
||||
fileDiff={fileDiff}
|
||||
options={{
|
||||
diffStyle: "unified",
|
||||
diffIndicators: "bars",
|
||||
overflow: "scroll",
|
||||
themeType: "dark",
|
||||
...diffOptions,
|
||||
enableLineSelection: true,
|
||||
enableHoverUtility: true,
|
||||
onLineSelected() {
|
||||
// TODO: Make this add context to the input so the user can type.
|
||||
},
|
||||
theme: "github-dark-high-contrast",
|
||||
unsafeCSS:
|
||||
"pre, [data-line], [data-diffs-header] { background-color: transparent !important; } [data-diffs-header] { border-left: 1px solid var(--border); }",
|
||||
}}
|
||||
style={{
|
||||
"--diffs-font-size": "11px",
|
||||
"--diffs-line-height": "1.5",
|
||||
}}
|
||||
style={DIFFS_FONT_STYLE}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user