From 0df6aed6ca1b544a05cfb966eaf7e47a1d88982a Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 7 Aug 2026 22:43:58 -0400 Subject: [PATCH] fix(merman): derive neutral diagram palette (#41181) --- packages/merman/src/palette.test.ts | 51 +++++++++++++++++++++++++++++ packages/merman/src/palette.ts | 20 +++++++++++ packages/merman/src/plugin.ts | 13 ++++---- 3 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 packages/merman/src/palette.test.ts create mode 100644 packages/merman/src/palette.ts diff --git a/packages/merman/src/palette.test.ts b/packages/merman/src/palette.test.ts new file mode 100644 index 00000000000..07ed2f81bcf --- /dev/null +++ b/packages/merman/src/palette.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, test } from "bun:test" +import { RGBA } from "@opentui/core" +import { createOpenCodeDiagramPalette } from "./palette.js" + +type Rgb = readonly [number, number, number] + +const rgb = (value: Rgb) => RGBA.fromInts(...value) + +describe("OpenCode diagram palette", () => { + test.each( + [ + { + name: "dark theme", + text: [230, 232, 240], + subdued: [114, 120, 138], + secondary: [172, 176, 189], + muted: [149, 154, 169], + }, + { + name: "light theme", + text: [32, 35, 43], + subdued: [119, 125, 138], + secondary: [76, 80, 91], + muted: [93, 98, 110], + }, + ] satisfies ReadonlyArray<{ + name: string + text: Rgb + subdued: Rgb + secondary: Rgb + muted: Rgb + }>, + )("derives a controlled neutral ladder for a $name", ({ text, subdued, secondary, muted }) => { + const primary = rgb(text) + const info = RGBA.fromInts(40, 120, 220) + const background = RGBA.fromInts(10, 20, 30) + const palette = createOpenCodeDiagramPalette({ + text: primary, + subdued: rgb(subdued), + info, + background, + }) + + expect(palette.text).toBe(primary) + expect(palette.primary).toBe(primary) + expect(palette.secondary.equals(rgb(secondary))).toBe(true) + expect(palette.muted.equals(rgb(muted))).toBe(true) + expect(palette.warning).toBe(info) + expect(palette.background).toBe(background) + }) +}) diff --git a/packages/merman/src/palette.ts b/packages/merman/src/palette.ts new file mode 100644 index 00000000000..ee1fc1a1410 --- /dev/null +++ b/packages/merman/src/palette.ts @@ -0,0 +1,20 @@ +import type { RGBA } from "@opentui/core" +import { blendColor } from "./core/color/style.js" + +export interface OpenCodeDiagramPaletteInput { + readonly text: RGBA + readonly subdued: RGBA + readonly info: RGBA + readonly background: RGBA +} + +export function createOpenCodeDiagramPalette(input: OpenCodeDiagramPaletteInput) { + return { + text: input.text, + primary: input.text, + secondary: blendColor(input.text, input.subdued, 0.5), + muted: blendColor(input.text, input.subdued, 0.7), + warning: input.info, + background: input.background, + } +} diff --git a/packages/merman/src/plugin.ts b/packages/merman/src/plugin.ts index 966aa7a5152..2f4c7d02f6b 100644 --- a/packages/merman/src/plugin.ts +++ b/packages/merman/src/plugin.ts @@ -1,5 +1,6 @@ import { Plugin } from "@opencode-ai/plugin/tui" import { createMermaidCodeBlockRenderer } from "./markdown.js" +import { createOpenCodeDiagramPalette } from "./palette.js" export default Plugin.define({ id: "opencode.merman", @@ -7,14 +8,12 @@ export default Plugin.define({ context.markdown.registerCodeBlockRenderer( "mermaid", createMermaidCodeBlockRenderer(context.renderer, () => ({ - colors: { - text: context.theme.markdown.text, - primary: context.theme.text.default, - secondary: context.theme.text.subdued, - muted: context.theme.border.default, - warning: context.theme.text.feedback.info.default, + colors: createOpenCodeDiagramPalette({ + text: context.theme.text.default, + subdued: context.theme.text.subdued, + info: context.theme.text.feedback.info.default, background: context.theme.background.default, - }, + }), })), ) },