fix(ui): make the logs JSON viewer follow the theme in dark mode

The request and response tree passed the library's light palette in every
theme, so in dark mode the string values rendered dark green and the
punctuation rendered black on a near black surface. Pick the palette from
the resolved theme instead, and let the tree inherit the themed surface
rather than painting the library's own background.
This commit is contained in:
Yuneng Jiang 2026-08-29 10:54:48 -07:00
parent 352789257d
commit 4c0bb1226c
No known key found for this signature in database
2 changed files with 35 additions and 6 deletions

View file

@ -1,17 +1,26 @@
import { render, screen } from "@testing-library/react";
import { ThemeProvider } from "next-themes";
import { darkStyles, defaultStyles } from "react-json-view-lite";
import { describe, expect, it } from "vitest";
import { JsonViewer } from "./JsonViewer";
const renderWithTheme = (theme: "light" | "dark", data: unknown) =>
render(
<ThemeProvider attribute="class" defaultTheme={theme} enableSystem={false}>
<JsonViewer data={data} mode="formatted" />
</ThemeProvider>,
);
describe("JsonViewer", () => {
it("should render a placeholder and no tree when the log entry carries no payload", () => {
render(<JsonViewer data={null} mode="formatted" />);
renderWithTheme("light", null);
expect(screen.getByText("No data")).toBeInTheDocument();
expect(screen.queryByRole("tree")).not.toBeInTheDocument();
});
it("should render the payload as a tree exposing its keys", () => {
render(<JsonViewer data={{ model: "claude-opus-4-5", stream: true }} mode="formatted" />);
renderWithTheme("light", { model: "claude-opus-4-5", stream: true });
expect(screen.getByRole("tree")).toBeInTheDocument();
expect(screen.getByText(/model/)).toBeInTheDocument();
@ -20,9 +29,26 @@ describe("JsonViewer", () => {
});
it("should treat an empty payload as data rather than showing the placeholder", () => {
render(<JsonViewer data={{}} mode="formatted" />);
renderWithTheme("light", {});
expect(screen.getByRole("tree")).toBeInTheDocument();
expect(screen.queryByText("No data")).not.toBeInTheDocument();
});
it("should style the tree with the light palette when the dashboard theme is light", () => {
renderWithTheme("light", { model: "claude-opus-4-5" });
expect(screen.getByRole("tree")).toHaveClass(...defaultStyles.container.split(" "));
});
it("should style the tree with the dark palette when the dashboard theme is dark", () => {
renderWithTheme("dark", { model: "claude-opus-4-5" });
const tree = screen.getByRole("tree");
expect(tree).toHaveClass(...darkStyles.container.split(" "));
defaultStyles.container
.split(" ")
.filter((className) => !darkStyles.container.split(" ").includes(className))
.forEach((lightOnlyClassName) => expect(tree).not.toHaveClass(lightOnlyClassName));
});
});

View file

@ -1,4 +1,5 @@
import { JsonView, defaultStyles } from "react-json-view-lite";
import { useTheme } from "next-themes";
import { JsonView, darkStyles, defaultStyles } from "react-json-view-lite";
import "react-json-view-lite/dist/index.css";
import { JSON_MAX_HEIGHT, SPACING_LARGE } from "./constants";
@ -12,6 +13,8 @@ interface JsonViewerProps {
* Uses an interactive tree component for easy navigation.
*/
export function JsonViewer({ data }: JsonViewerProps) {
const { resolvedTheme } = useTheme();
if (!data) return <span className="text-muted-foreground">No data</span>;
return (
@ -24,8 +27,8 @@ export function JsonViewer({ data }: JsonViewerProps) {
borderRadius: 4,
}}
>
<div className="**:[[role='tree']]:bg-background! **:[[role='tree']]:text-foreground">
<JsonView data={data} style={defaultStyles} clickToExpandNode={true} />
<div className="**:[[role='tree']]:bg-transparent!">
<JsonView data={data} style={resolvedTheme === "dark" ? darkStyles : defaultStyles} clickToExpandNode={true} />
</div>
</div>
);