diff --git a/ui/src/components/MarkdownBody.tsx b/ui/src/components/MarkdownBody.tsx index b1f2212685..a26cafac79 100644 --- a/ui/src/components/MarkdownBody.tsx +++ b/ui/src/components/MarkdownBody.tsx @@ -231,7 +231,7 @@ const codeBlockActionsStyle: React.CSSProperties = { gap: "0.25rem", }; -const codeBlockActionStyle: React.CSSProperties = { +export const codeBlockActionStyle: React.CSSProperties = { position: "static", opacity: 1, display: "inline-flex", @@ -242,7 +242,7 @@ const codeBlockActionStyle: React.CSSProperties = { padding: "0.2rem 0.4rem", borderRadius: "calc(var(--radius) - 4px)", border: "1px solid color-mix(in oklab, var(--foreground) 14%, transparent)", - backgroundColor: "color-mix(in oklab, var(--muted) 92%, var(--background) 8%)", + backgroundColor: "var(--background)", color: "var(--muted-foreground)", fontSize: "var(--text-micro)", lineHeight: 1, diff --git a/ui/src/components/MarkdownCodeBlockStyles.test.ts b/ui/src/components/MarkdownCodeBlockStyles.test.ts new file mode 100644 index 0000000000..f269e90bf8 --- /dev/null +++ b/ui/src/components/MarkdownCodeBlockStyles.test.ts @@ -0,0 +1,70 @@ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +import { codeBlockActionStyle } from "./MarkdownBody"; + +const stylesheet = readFileSync(fileURLToPath(new URL("../index.css", import.meta.url)), "utf8"); + +function cssBlock(selector: string): string { + const start = stylesheet.indexOf(`${selector} {`); + expect(start, `Missing CSS selector: ${selector}`).toBeGreaterThanOrEqual(0); + + const bodyStart = stylesheet.indexOf("{", start); + const bodyEnd = stylesheet.indexOf("\n}", bodyStart); + expect(bodyStart, `Missing CSS block start: ${selector}`).toBeGreaterThanOrEqual(0); + expect(bodyEnd, `Missing CSS block end: ${selector}`).toBeGreaterThan(bodyStart); + + return stylesheet.slice(bodyStart + 1, bodyEnd); +} + +/* The rendered code block used to be pinned to the Catppuccin literals + #1e1e2e / #cdd6f4, in the normal AND the prose-invert variables. A code + block therefore stayed dark in light mode. These tests fail if any of + those surfaces is pinned to a literal again, rather than riding a token + that carries a `.dark` override. */ + +describe("markdown code block styles", () => { + it("rides theme tokens for the rendered code block surface", () => { + const block = cssBlock(".paperclip-markdown pre"); + + expect(block).toContain("background-color: var(--muted)"); + expect(block).toContain("color: var(--foreground)"); + expect(block).toContain("border: 1px solid var(--border)"); + expect(block).toContain("border-radius: var(--radius-lg)"); + }); + + it("rides theme tokens for the editor content code block surface", () => { + const block = cssBlock(".paperclip-mdxeditor-content pre"); + + expect(block).toContain("background: var(--muted)"); + expect(block).toContain("color: var(--foreground)"); + expect(block).toContain("border: 1px solid var(--border)"); + expect(block).toContain("border-radius: var(--radius-lg)"); + }); + + it("points the normal and inverted prose variables at the same tokens", () => { + const block = cssBlock(".paperclip-markdown"); + + expect(block).toContain("--tw-prose-pre-bg: var(--muted)"); + expect(block).toContain("--tw-prose-pre-code: var(--foreground)"); + expect(block).toContain("--tw-prose-invert-pre-bg: var(--muted)"); + expect(block).toContain("--tw-prose-invert-pre-code: var(--foreground)"); + }); + + it("keeps every themed code surface free of hardcoded colors", () => { + for (const selector of [".paperclip-markdown pre", ".paperclip-mdxeditor-content pre", ".paperclip-markdown"]) { + // Any hex literal, or an rgb()/hsl() literal, re-pins the surface to one mode. + expect(cssBlock(selector), `${selector} must not hardcode a color`).not.toMatch( + /#[0-9a-f]{3,8}\b|\brgba?\(|\bhsla?\(/i, + ); + } + }); + + it("separates the copy and wrap controls from the code block surface", () => { + // The controls sit on top of the block. --muted is now the block's own + // fill, so the controls must not use it or they disappear against it. + expect(codeBlockActionStyle.backgroundColor).toBe("var(--background)"); + expect(codeBlockActionStyle.backgroundColor).not.toContain("--muted"); + }); +}); diff --git a/ui/src/index.css b/ui/src/index.css index f8792195e3..2bc7bc81de 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -1406,10 +1406,10 @@ a.paperclip-mention-chip[data-mention-kind="agent"]::before { .paperclip-mdxeditor-content pre { margin: 0.4rem 0; padding: 0; - border: 1px solid color-mix(in oklab, var(--foreground) 12%, transparent); - border-radius: calc(var(--radius) - 3px); - background: #1e1e2e; - color: #cdd6f4; + border: 1px solid var(--border); + border-radius: var(--radius-lg); + background: var(--muted); + color: var(--foreground); overflow-x: auto; } @@ -1482,20 +1482,22 @@ a.paperclip-mention-chip[data-mention-kind="agent"]::before { } /* Rendered markdown code blocks & inline code (prose/MarkdownBody context). - Dark theme code blocks with compact sizing. - Override prose CSS variables so prose-invert can't revert to defaults. */ + Theme-aware: the block rides --muted/--foreground/--border so it follows + light and dark instead of pinning the old Catppuccin #1e1e2e/#cdd6f4 in + both modes. Override prose CSS variables so prose-invert can't revert to + defaults. */ .paperclip-markdown { - --tw-prose-pre-bg: #1e1e2e; - --tw-prose-pre-code: #cdd6f4; - --tw-prose-invert-pre-bg: #1e1e2e; - --tw-prose-invert-pre-code: #cdd6f4; + --tw-prose-pre-bg: var(--muted); + --tw-prose-pre-code: var(--foreground); + --tw-prose-invert-pre-bg: var(--muted); + --tw-prose-invert-pre-code: var(--foreground); } .paperclip-markdown pre { - border: 1px solid color-mix(in oklab, var(--foreground) 12%, transparent) !important; - border-radius: calc(var(--radius) - 3px) !important; - background-color: #1e1e2e !important; - color: #cdd6f4 !important; + border: 1px solid var(--border) !important; + border-radius: var(--radius-lg) !important; + background-color: var(--muted) !important; + color: var(--foreground) !important; padding: 0.5rem 0.65rem !important; margin: 0.4rem 0 !important; font-size: 1em !important;