feat(ui): terminal, charts and low-contrast text follow the selected theme
- The lane terminal (xterm) takes its palette from the selected DARK theme — it stays dark in light mode, as a terminal should — and repaints live when the theme changes (`client/src/lib/terminalTheme.ts`). - Charts on the Dashboard and Workflows pages draw their grid, axes, labels and tooltips from theme tokens instead of hardcoded near-black/white, and the activity heatmap ramps from the page surface to the accent, so it reads on every theme instead of going near-black on light ones. - Status/brand text that only worked on dark backgrounds (`text-*-300/400`) gets a darker light-mode shade (`text-*-700 dark:text-*-300`) across sessions, conversations, workflows, config and settings. Categorical chart colours are kept.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @file terminalTheme.test.ts
|
||||
* @description Tests for the xterm theme builder: CSS-variable RGB triplets
|
||||
* become hex, status/accent map to the ANSI hues, bright twins are lighter,
|
||||
* and the probe resolves a named dark theme's variables.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { buildTerminalTheme, terminalThemeFor } from "../terminalTheme";
|
||||
|
||||
const vars: Record<string, string> = {
|
||||
"--surface-0": " 17 17 19",
|
||||
"--surface-3": "39 42 45",
|
||||
"--fg-primary": "237 238 240",
|
||||
"--fg-secondary": "176 180 186",
|
||||
"--fg-muted": "105 110 119",
|
||||
"--border-light": "67 72 78",
|
||||
"--accent": "0 144 255",
|
||||
"--status-success": "61 214 140",
|
||||
"--status-danger": "255 149 146",
|
||||
"--status-warning": "255 202 22",
|
||||
};
|
||||
|
||||
describe("buildTerminalTheme", () => {
|
||||
it("maps theme tokens onto the xterm palette", () => {
|
||||
const t = buildTerminalTheme((n) => vars[n] ?? "");
|
||||
expect(t.background).toBe("#111113");
|
||||
expect(t.foreground).toBe("#edeef0");
|
||||
expect(t.cursor).toBe("#edeef0");
|
||||
expect(t.selectionBackground).toBe("#43484e");
|
||||
expect(t.red).toBe("#ff9592");
|
||||
expect(t.green).toBe("#3dd68c");
|
||||
expect(t.yellow).toBe("#ffca16");
|
||||
expect(t.blue).toBe("#0090ff");
|
||||
expect(t.black).toBe("#43484e");
|
||||
expect(t.brightBlack).toBe("#696e77");
|
||||
expect(t.brightWhite).toBe("#edeef0");
|
||||
});
|
||||
|
||||
it("makes each bright twin lighter than its base colour", () => {
|
||||
const t = buildTerminalTheme((n) => vars[n] ?? "");
|
||||
const sum = (hex: string) =>
|
||||
[1, 3, 5].reduce((a, i) => a + parseInt(hex.slice(i, i + 2), 16), 0);
|
||||
for (const c of ["red", "green", "yellow", "blue", "magenta", "cyan"] as const) {
|
||||
const base = t[c]!;
|
||||
const bright = t[`bright${c[0]!.toUpperCase()}${c.slice(1)}` as keyof typeof t] as string;
|
||||
expect(sum(bright)).toBeGreaterThan(sum(base));
|
||||
}
|
||||
});
|
||||
|
||||
it("leaves a colour unset when its variable is missing, so xterm keeps its default", () => {
|
||||
const t = buildTerminalTheme(() => "");
|
||||
expect(t.background).toBeUndefined();
|
||||
expect(t.red).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("terminalThemeFor", () => {
|
||||
it("resolves variables on a dark probe scoped to the theme id, then removes it", () => {
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `.dark { --surface-0: 1 2 3; } .dark[data-theme="darcula"] { --surface-0: 30 31 32; }`;
|
||||
document.head.appendChild(style);
|
||||
try {
|
||||
expect(terminalThemeFor("default").background).toBe("#010203");
|
||||
expect(terminalThemeFor("darcula").background).toBe("#1e1f20");
|
||||
expect(document.body.children).toHaveLength(0);
|
||||
} finally {
|
||||
style.remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user