From 53d73e99f6d25e9a65598d2d7e6ca41d054f2bdc Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Thu, 7 May 2026 13:43:05 +0530 Subject: [PATCH 1/2] feat: add vesper theme --- CHANGELOG.md | 2 ++ README.md | 2 +- docs/opentui-component.md | 26 ++++++++-------- src/opentui/HunkDiffView.test.tsx | 2 +- src/opentui/themes.ts | 2 +- src/ui/diff/pierre.test.ts | 2 +- src/ui/lib/ui-lib.test.ts | 4 +-- src/ui/themes.ts | 49 +++++++++++++++++++++++++++++++ 8 files changed, 70 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06e9263d..39ca0bc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ All notable user-visible changes to Hunk are documented in this file. ### Added +- Added the Vesper theme as a built-in diff viewer theme. + ### Changed ### Fixed diff --git a/README.md b/README.md index 401c5ce0..08083619 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ You can persist preferences to a config file: Example: ```toml -theme = "graphite" # graphite, midnight, paper, ember +theme = "graphite" # graphite, vesper, midnight, paper, ember mode = "auto" # auto, split, stack exclude_untracked = false line_numbers = true diff --git a/docs/opentui-component.md b/docs/opentui-component.md index be41ba4e..a8638b02 100644 --- a/docs/opentui-component.md +++ b/docs/opentui-component.md @@ -99,19 +99,19 @@ if (!metadata) { ## Props -| Prop | Type | Default | Notes | -| ------------------- | ------------------------------------------------ | ------------ | ------------------------------------------------------------------------- | -| `diff` | `HunkDiffFile` | `undefined` | File to render. When omitted, the component shows an empty-state message. | -| `layout` | `"split" \| "stack"` | `"split"` | Chooses side-by-side or stacked rendering. | -| `width` | `number` | — | Required content width in terminal columns. | -| `theme` | `"graphite" \| "midnight" \| "paper" \| "ember"` | `"graphite"` | Matches Hunk's built-in themes. | -| `showLineNumbers` | `boolean` | `true` | Toggles line-number columns. | -| `showHunkHeaders` | `boolean` | `true` | Toggles `@@ ... @@` hunk header rows. | -| `wrapLines` | `boolean` | `false` | Wraps long lines instead of clipping horizontally. | -| `horizontalOffset` | `number` | `0` | Scroll offset for non-wrapped code rows. | -| `highlight` | `boolean` | `true` | Enables syntax highlighting. | -| `scrollable` | `boolean` | `true` | Set to `false` if your parent view owns scrolling. | -| `selectedHunkIndex` | `number` | `0` | Highlights one hunk as the active target. | +| Prop | Type | Default | Notes | +| ------------------- | ------------------------------------------------------------ | ------------ | ------------------------------------------------------------------------- | +| `diff` | `HunkDiffFile` | `undefined` | File to render. When omitted, the component shows an empty-state message. | +| `layout` | `"split" \| "stack"` | `"split"` | Chooses side-by-side or stacked rendering. | +| `width` | `number` | — | Required content width in terminal columns. | +| `theme` | `"graphite" \| "vesper" \| "midnight" \| "paper" \| "ember"` | `"graphite"` | Matches Hunk's built-in themes. | +| `showLineNumbers` | `boolean` | `true` | Toggles line-number columns. | +| `showHunkHeaders` | `boolean` | `true` | Toggles `@@ ... @@` hunk header rows. | +| `wrapLines` | `boolean` | `false` | Wraps long lines instead of clipping horizontally. | +| `horizontalOffset` | `number` | `0` | Scroll offset for non-wrapped code rows. | +| `highlight` | `boolean` | `true` | Enables syntax highlighting. | +| `scrollable` | `boolean` | `true` | Set to `false` if your parent view owns scrolling. | +| `selectedHunkIndex` | `number` | `0` | Highlights one hunk as the active target. | ## Other exports diff --git a/src/opentui/HunkDiffView.test.tsx b/src/opentui/HunkDiffView.test.tsx index e5251684..2f308bfb 100644 --- a/src/opentui/HunkDiffView.test.tsx +++ b/src/opentui/HunkDiffView.test.tsx @@ -61,6 +61,6 @@ describe("HunkDiffView", () => { }); test("exports the documented built-in theme names", () => { - expect(HUNK_DIFF_THEME_NAMES).toEqual(["graphite", "midnight", "paper", "ember"]); + expect(HUNK_DIFF_THEME_NAMES).toEqual(["graphite", "vesper", "midnight", "paper", "ember"]); }); }); diff --git a/src/opentui/themes.ts b/src/opentui/themes.ts index 07b9599a..4c739619 100644 --- a/src/opentui/themes.ts +++ b/src/opentui/themes.ts @@ -1,3 +1,3 @@ -export const HUNK_DIFF_THEME_NAMES = ["graphite", "midnight", "paper", "ember"] as const; +export const HUNK_DIFF_THEME_NAMES = ["graphite", "vesper", "midnight", "paper", "ember"] as const; export type HunkDiffThemeName = (typeof HUNK_DIFF_THEME_NAMES)[number]; diff --git a/src/ui/diff/pierre.test.ts b/src/ui/diff/pierre.test.ts index cb64908f..809e2cc9 100644 --- a/src/ui/diff/pierre.test.ts +++ b/src/ui/diff/pierre.test.ts @@ -229,7 +229,7 @@ describe("Pierre diff rows", () => { const file = createMarkdownDiffFile(); const highlighted = await loadHighlightedDiff(file, "dark"); - for (const themeId of ["graphite", "midnight", "ember"] as const) { + for (const themeId of ["graphite", "vesper", "midnight", "ember"] as const) { const theme = resolveTheme(themeId, null); const rows = buildStackRows(file, highlighted, theme).filter( (row): row is Extract => diff --git a/src/ui/lib/ui-lib.test.ts b/src/ui/lib/ui-lib.test.ts index 2d8aa402..31ad1410 100644 --- a/src/ui/lib/ui-lib.test.ts +++ b/src/ui/lib/ui-lib.test.ts @@ -169,7 +169,7 @@ describe("ui helpers", () => { menus.theme .filter((entry): entry is Extract => entry.kind === "item") .map((entry) => entry.label), - ).toEqual(["Graphite", "Midnight", "Paper", "Ember"]); + ).toEqual(["Graphite", "Vesper", "Midnight", "Paper", "Ember"]); expect( menus.theme.some( (entry) => entry.kind === "item" && entry.label === "Graphite" && entry.checked, @@ -367,6 +367,6 @@ describe("ui helpers", () => { expect(midnight.id).toBe("midnight"); expect(missingLight.id).toBe("graphite"); expect(missingDark.id).toBe("graphite"); - expect(resolveTheme("ember", null).syntaxStyle).toBeDefined(); + expect(resolveTheme("vesper", null).syntaxStyle).toBeDefined(); }); }); diff --git a/src/ui/themes.ts b/src/ui/themes.ts index 8bf95c3d..55d49e6b 100644 --- a/src/ui/themes.ts +++ b/src/ui/themes.ts @@ -137,6 +137,55 @@ export const THEMES: AppTheme[] = [ punctuation: "#7f8b97", }, ), + withLazySyntaxStyle( + { + id: "vesper", + label: "Vesper", + appearance: "dark", + background: "#101010", + panel: "#1A1A1A", + panelAlt: "#232323", + border: "#282828", + accent: "#FFC799", + accentMuted: "#575757", + text: "#FFFFFF", + muted: "#A0A0A0", + addedBg: "#1A1A1A", + removedBg: "#1A1A1A", + contextBg: "#101010", + addedContentBg: "#232323", + removedContentBg: "#232323", + contextContentBg: "#1A1A1A", + addedSignColor: "#99FFE4", + removedSignColor: "#FF8080", + lineNumberBg: "#101010", + lineNumberFg: "#505050", + selectedHunk: "#282828", + badgeAdded: "#99FFE4", + badgeRemoved: "#FF8080", + badgeNeutral: "#B0B0B0", + fileNew: "#99FFE4", + fileDeleted: "#FF8080", + fileRenamed: "#FFD1A8", + fileModified: "#FFC799", + fileUntracked: "#B3FFE4", + noteBorder: "#FFC799", + noteBackground: "#1A1A1A", + noteTitleBackground: "#232323", + noteTitleText: "#FFFFFF", + }, + { + default: "#FFFFFF", + keyword: "#FFC799", + string: "#99FFE4", + comment: "#5C5C5C", + number: "#FFD1A8", + function: "#FFFFFF", + property: "#B0B0B0", + type: "#FFD1A8", + punctuation: "#7E7E7E", + }, + ), withLazySyntaxStyle( { id: "midnight", From 6e498f8419654a750c814466237d536c474083ae Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Thu, 7 May 2026 14:39:33 +0530 Subject: [PATCH 2/2] fix: use vesper syntax highlighting --- src/opentui/HunkDiffView.tsx | 2 +- src/ui/components/panes/DiffPane.tsx | 4 +- src/ui/diff/PierreDiffView.tsx | 2 +- src/ui/diff/pierre.test.ts | 33 ++++++++ src/ui/diff/pierre.ts | 119 +++++++++++++++++++++++---- src/ui/diff/useHighlightedDiff.ts | 53 ++++++------ src/ui/lib/ui-lib.test.ts | 5 +- src/ui/themes.ts | 20 ++--- 8 files changed, 176 insertions(+), 62 deletions(-) diff --git a/src/opentui/HunkDiffView.tsx b/src/opentui/HunkDiffView.tsx index d59fd211..0f8c9061 100644 --- a/src/opentui/HunkDiffView.tsx +++ b/src/opentui/HunkDiffView.tsx @@ -63,7 +63,7 @@ export function HunkDiffView({ const internalDiff = useMemo(() => (diff ? toInternalDiffFile(diff) : undefined), [diff]); const resolvedHighlighted = useHighlightedDiff({ file: internalDiff, - appearance: resolvedTheme.appearance, + theme: resolvedTheme, shouldLoadHighlight: highlight, }); const rows = useMemo( diff --git a/src/ui/components/panes/DiffPane.tsx b/src/ui/components/panes/DiffPane.tsx index d93f7ef5..74ab3e87 100644 --- a/src/ui/components/panes/DiffPane.tsx +++ b/src/ui/components/panes/DiffPane.tsx @@ -495,10 +495,10 @@ export function DiffPane({ void prefetchHighlightedDiff({ file, - appearance: theme.appearance, + theme, }); } - }, [files, highlightPrefetchFileIds, theme.appearance]); + }, [files, highlightPrefetchFileIds, theme]); // Read the live scroll box position during render so pinned-header ownership flips // immediately after imperative scrolls instead of waiting for the polled viewport snapshot. diff --git a/src/ui/diff/PierreDiffView.tsx b/src/ui/diff/PierreDiffView.tsx index 21c394ac..f745b948 100644 --- a/src/ui/diff/PierreDiffView.tsx +++ b/src/ui/diff/PierreDiffView.tsx @@ -48,7 +48,7 @@ export function PierreDiffView({ }) { const resolvedHighlighted = useHighlightedDiff({ file, - appearance: theme.appearance, + theme, shouldLoadHighlight, }); diff --git a/src/ui/diff/pierre.test.ts b/src/ui/diff/pierre.test.ts index 809e2cc9..acd4dfb4 100644 --- a/src/ui/diff/pierre.test.ts +++ b/src/ui/diff/pierre.test.ts @@ -225,6 +225,39 @@ describe("Pierre diff rows", () => { } }); + test("uses the Vesper syntax palette for highlighted code tokens", async () => { + const file = createDiffFile(); + const theme = resolveTheme("vesper", null); + const highlighted = await loadHighlightedDiff(file, theme); + const rows = buildStackRows(file, highlighted, theme).filter( + (row): row is Extract => + row.type === "stack-line" && row.cell.kind === "addition", + ); + + const changedRow = rows.find((row) => row.cell.spans.some((span) => span.text.includes("42"))); + expect(changedRow).toBeDefined(); + + if (!changedRow) { + throw new Error("Expected highlighted Vesper addition row"); + } + + expect( + changedRow.cell.spans.some( + (span) => span.text.includes("export const") && span.fg === theme.syntaxColors.keyword, + ), + ).toBe(true); + expect( + changedRow.cell.spans.some( + (span) => span.text.includes("answer") && span.fg === theme.syntaxColors.default, + ), + ).toBe(true); + expect( + changedRow.cell.spans.some( + (span) => span.text.includes("42") && span.fg === theme.syntaxColors.number, + ), + ).toBe(true); + }); + test("keeps reserved-color remaps isolated across dark themes", async () => { const file = createMarkdownDiffFile(); const highlighted = await loadHighlightedDiff(file, "dark"); diff --git a/src/ui/diff/pierre.ts b/src/ui/diff/pierre.ts index 7d99b3d2..370b0404 100644 --- a/src/ui/diff/pierre.ts +++ b/src/ui/diff/pierre.ts @@ -2,6 +2,7 @@ import { cleanLastNewline, getHighlighterOptions, getSharedHighlighter, + registerCustomTheme, renderDiffWithHighlighter, type FileDiffMetadata, } from "@pierre/diffs"; @@ -15,12 +16,95 @@ const PIERRE_THEME = { light: "pierre-light", dark: "pierre-dark", } as const; +const HUNK_VESPER_PIERRE_THEME = "hunk-vesper"; + +registerCustomTheme(HUNK_VESPER_PIERRE_THEME, async () => ({ + name: HUNK_VESPER_PIERRE_THEME, + type: "dark", + bg: "#101010", + fg: "#FFFFFF", + colors: { + "editor.background": "#101010", + "editor.foreground": "#FFFFFF", + "editor.lineHighlightBackground": "#101010", + "editor.selectionBackground": "#282828", + "editorCursor.foreground": "#FFC799", + "editorLineNumber.foreground": "#505050", + "gitDecoration.addedResourceForeground": "#99FFE4", + "gitDecoration.deletedResourceForeground": "#FF8080", + "gitDecoration.modifiedResourceForeground": "#FFC799", + "terminal.ansiGreen": "#99FFE4", + "terminal.ansiRed": "#FF8080", + "terminal.ansiYellow": "#FFC799", + }, + settings: [ + { settings: { background: "#101010", foreground: "#FFFFFF" } }, + { + scope: ["comment", "punctuation.definition.comment"], + settings: { foreground: "#5C5C5C", fontStyle: "italic" }, + }, + { + scope: ["string", "constant.other.symbol", "constant.other.key"], + settings: { foreground: "#99FFE4" }, + }, + { + scope: [ + "constant", + "constant.numeric", + "constant.language", + "entity.name.function", + "support.function", + "entity.name.type", + "entity.name.class", + "support.type", + "meta.type.annotation", + ], + settings: { foreground: "#FFC799" }, + }, + { + scope: [ + "keyword", + "keyword.operator", + "storage", + "storage.type", + "storage.modifier", + "punctuation", + "meta.brace", + "meta.delimiter", + ], + settings: { foreground: "#A0A0A0" }, + }, + { + scope: [ + "variable", + "variable.parameter", + "variable.other.property", + "support.variable.property", + "entity.other.attribute-name", + ], + settings: { foreground: "#FFFFFF" }, + }, + { + scope: ["invalid", "invalid.illegal"], + settings: { foreground: "#FF8080" }, + }, + ], +})); /** Resolve the single Pierre theme name needed for the current appearance. */ function pierreThemeName(appearance: AppTheme["appearance"]) { return PIERRE_THEME[appearance]; } +/** Resolve the concrete syntax-highlighting theme for one Hunk theme. */ +function pierreSyntaxThemeName(theme: AppTheme | AppTheme["appearance"]) { + if (typeof theme === "string") { + return pierreThemeName(theme); + } + + return theme.id === "vesper" ? HUNK_VESPER_PIERRE_THEME : pierreThemeName(theme.appearance); +} + const PIERRE_RENDER_OPTIONS_BY_APPEARANCE = { light: { theme: pierreThemeName("light"), @@ -38,9 +122,12 @@ const PIERRE_RENDER_OPTIONS_BY_APPEARANCE = { }, } as const; -/** Reuse the render options for one appearance so startup work avoids extra object churn. */ -function pierreRenderOptions(appearance: AppTheme["appearance"]) { - return PIERRE_RENDER_OPTIONS_BY_APPEARANCE[appearance]; +/** Resolve render options for one theme while preserving shared objects for built-in Pierre themes. */ +function pierreRenderOptions(theme: AppTheme | AppTheme["appearance"]) { + const themeName = pierreSyntaxThemeName(theme); + const appearance = typeof theme === "string" ? theme : theme.appearance; + const options = PIERRE_RENDER_OPTIONS_BY_APPEARANCE[appearance]; + return options.theme === themeName ? options : { ...options, theme: themeName }; } type HighlightOptions = ReturnType; @@ -427,16 +514,13 @@ function trailingCollapsedLines(metadata: FileDiffMetadata) { } /** Prepare syntax highlighting for one language/appearance pair using Pierre's shared highlighter. */ -async function prepareHighlighter( - language: string | undefined, - appearance: AppTheme["appearance"], -) { +async function prepareHighlighter(language: string | undefined, syntaxThemeName: string) { const resolvedLanguage = language ?? "text"; - const cacheKey = `${appearance}:${resolvedLanguage}`; + const cacheKey = `${syntaxThemeName}:${resolvedLanguage}`; const options = highlighterOptionsByKey.get(cacheKey) ?? getHighlighterOptions(resolvedLanguage, { - theme: pierreThemeName(appearance), + theme: syntaxThemeName, }); if (!highlighterOptionsByKey.has(cacheKey)) { @@ -513,28 +597,27 @@ function aliasHighlightedContextLines(file: DiffFile, highlighted: HighlightedDi /** Highlight a diff file and return just the rendered line trees the UI needs. */ export async function loadHighlightedDiff( file: DiffFile, - appearance: AppTheme["appearance"] = "dark", + theme: AppTheme | AppTheme["appearance"] = "dark", ): Promise { + const syntaxThemeName = pierreSyntaxThemeName(theme); + const renderOptions = pierreRenderOptions(theme); + try { - const highlighter = await prepareHighlighter(file.language, appearance); + const highlighter = await prepareHighlighter(file.language, syntaxThemeName); return queueHighlightedDiff(() => { - const highlighted = renderDiffWithHighlighter( - file.metadata, - highlighter, - pierreRenderOptions(appearance), - ); + const highlighted = renderDiffWithHighlighter(file.metadata, highlighter, renderOptions); return aliasHighlightedContextLines(file, { deletionLines: highlighted.code.deletionLines as Array, additionLines: highlighted.code.additionLines as Array, }); }); } catch { - const highlighter = await prepareHighlighter("text", appearance); + const highlighter = await prepareHighlighter("text", syntaxThemeName); return queueHighlightedDiff(() => { const highlighted = renderDiffWithHighlighter( { ...file.metadata, lang: "text" }, highlighter, - pierreRenderOptions(appearance), + renderOptions, ); return aliasHighlightedContextLines(file, { deletionLines: highlighted.code.deletionLines as Array, diff --git a/src/ui/diff/useHighlightedDiff.ts b/src/ui/diff/useHighlightedDiff.ts index 9cac013b..6ec72e98 100644 --- a/src/ui/diff/useHighlightedDiff.ts +++ b/src/ui/diff/useHighlightedDiff.ts @@ -1,5 +1,6 @@ import { useLayoutEffect, useState } from "react"; import type { DiffFile } from "../../core/types"; +import type { AppTheme } from "../themes"; import { loadHighlightedDiff, type HighlightedDiffCode } from "./pierre"; /** Maximum cached highlight results. Prevents unbounded growth during long watch sessions. */ @@ -72,8 +73,8 @@ function patchFingerprint(file: DiffFile) { /** Cache key that includes a content fingerprint so stale entries are never served * after reload. Unchanged files keep their cache hit across reloads. */ -function buildCacheKey(appearance: string, file: DiffFile) { - return `${appearance}:${file.id}:${patchFingerprint(file)}`; +function buildCacheKey(theme: AppTheme, file: DiffFile) { + return `${theme.id}:${theme.appearance}:${file.id}:${patchFingerprint(file)}`; } /** Only commit a highlight result if the promise is still the active one for that key. @@ -96,8 +97,8 @@ function commitHighlightResult( /** Start one shared highlight request unless the cache or an in-flight promise already has it. */ function ensureHighlightedDiffLoaded( file: DiffFile, - appearance: "light" | "dark", - cacheKey = buildCacheKey(appearance, file), + theme: AppTheme, + cacheKey = buildCacheKey(theme, file), ) { const cached = SHARED_HIGHLIGHTED_DIFF_CACHE.get(cacheKey); if (cached) { @@ -110,7 +111,7 @@ function ensureHighlightedDiffLoaded( } let pending: Promise; - pending = loadHighlightedDiff(file, appearance) + pending = loadHighlightedDiff(file, theme) .then((nextHighlighted) => { commitHighlightResult(cacheKey, pending, nextHighlighted); return nextHighlighted; @@ -129,68 +130,62 @@ function ensureHighlightedDiffLoaded( } /** Queue syntax highlighting for one file without mounting its diff rows first. */ -export function prefetchHighlightedDiff({ - file, - appearance, -}: { - file: DiffFile; - appearance: "light" | "dark"; -}) { - return ensureHighlightedDiffLoaded(file, appearance); +export function prefetchHighlightedDiff({ file, theme }: { file: DiffFile; theme: AppTheme }) { + return ensureHighlightedDiffLoaded(file, theme); } /** Read the best already-available highlight result without starting async work during render. */ function resolveHighlightedSnapshot({ - appearanceCacheKey, + highlightCacheKey, highlighted, highlightedCacheKey, }: { - appearanceCacheKey: string | null; + highlightCacheKey: string | null; highlighted: HighlightedDiffCode | null; highlightedCacheKey: string | null; }) { - if (!appearanceCacheKey) { + if (!highlightCacheKey) { return null; } - if (highlightedCacheKey === appearanceCacheKey) { + if (highlightedCacheKey === highlightCacheKey) { return highlighted; } - return SHARED_HIGHLIGHTED_DIFF_CACHE.get(appearanceCacheKey) ?? null; + return SHARED_HIGHLIGHTED_DIFF_CACHE.get(highlightCacheKey) ?? null; } /** Resolve highlighted diff content with shared caching and background prefetch support. */ export function useHighlightedDiff({ file, - appearance, + theme, shouldLoadHighlight, }: { file: DiffFile | undefined; - appearance: "light" | "dark"; + theme: AppTheme; shouldLoadHighlight?: boolean; }) { const [highlighted, setHighlighted] = useState(null); const [highlightedCacheKey, setHighlightedCacheKey] = useState(null); - const appearanceCacheKey = file ? buildCacheKey(appearance, file) : null; + const highlightCacheKey = file ? buildCacheKey(theme, file) : null; // Use a layout effect so a newly available cached result can replace the plain-text fallback // before the next diff paint whenever possible. That reduces flash/stutter as files enter view. useLayoutEffect(() => { - if (!file || !appearanceCacheKey) { + if (!file || !highlightCacheKey) { setHighlighted(null); setHighlightedCacheKey(null); return; } - if (highlightedCacheKey === appearanceCacheKey) { + if (highlightedCacheKey === highlightCacheKey) { return; } - const cached = SHARED_HIGHLIGHTED_DIFF_CACHE.get(appearanceCacheKey); + const cached = SHARED_HIGHLIGHTED_DIFF_CACHE.get(highlightCacheKey); if (cached) { setHighlighted(cached); - setHighlightedCacheKey(appearanceCacheKey); + setHighlightedCacheKey(highlightCacheKey); return; } @@ -201,23 +196,23 @@ export function useHighlightedDiff({ let cancelled = false; setHighlighted(null); - ensureHighlightedDiffLoaded(file, appearance, appearanceCacheKey).then((nextHighlighted) => { + ensureHighlightedDiffLoaded(file, theme, highlightCacheKey).then((nextHighlighted) => { if (cancelled) { return; } setHighlighted(nextHighlighted); - setHighlightedCacheKey(appearanceCacheKey); + setHighlightedCacheKey(highlightCacheKey); }); return () => { cancelled = true; }; - }, [appearance, appearanceCacheKey, file, highlightedCacheKey, shouldLoadHighlight]); + }, [file, highlightCacheKey, highlightedCacheKey, shouldLoadHighlight, theme]); // Prefer cached highlights during render so revisiting a file can paint immediately. return resolveHighlightedSnapshot({ - appearanceCacheKey, + highlightCacheKey, highlighted, highlightedCacheKey, }); diff --git a/src/ui/lib/ui-lib.test.ts b/src/ui/lib/ui-lib.test.ts index 31ad1410..ec5f0ec9 100644 --- a/src/ui/lib/ui-lib.test.ts +++ b/src/ui/lib/ui-lib.test.ts @@ -363,10 +363,13 @@ describe("ui helpers", () => { const midnight = resolveTheme("midnight", null); const missingLight = resolveTheme("missing", "light"); const missingDark = resolveTheme("missing", "dark"); + const vesper = resolveTheme("vesper", null); expect(midnight.id).toBe("midnight"); expect(missingLight.id).toBe("graphite"); expect(missingDark.id).toBe("graphite"); - expect(resolveTheme("vesper", null).syntaxStyle).toBeDefined(); + expect(vesper.addedBg).not.toBe(vesper.removedBg); + expect(vesper.addedContentBg).not.toBe(vesper.removedContentBg); + expect(vesper.syntaxStyle).toBeDefined(); }); }); diff --git a/src/ui/themes.ts b/src/ui/themes.ts index 55d49e6b..e88976cf 100644 --- a/src/ui/themes.ts +++ b/src/ui/themes.ts @@ -150,11 +150,11 @@ export const THEMES: AppTheme[] = [ accentMuted: "#575757", text: "#FFFFFF", muted: "#A0A0A0", - addedBg: "#1A1A1A", - removedBg: "#1A1A1A", + addedBg: "#1B2321", + removedBg: "#231919", contextBg: "#101010", - addedContentBg: "#232323", - removedContentBg: "#232323", + addedContentBg: "#263632", + removedContentBg: "#362222", contextContentBg: "#1A1A1A", addedSignColor: "#99FFE4", removedSignColor: "#FF8080", @@ -176,14 +176,14 @@ export const THEMES: AppTheme[] = [ }, { default: "#FFFFFF", - keyword: "#FFC799", + keyword: "#A0A0A0", string: "#99FFE4", comment: "#5C5C5C", - number: "#FFD1A8", - function: "#FFFFFF", - property: "#B0B0B0", - type: "#FFD1A8", - punctuation: "#7E7E7E", + number: "#FFC799", + function: "#FFC799", + property: "#FFFFFF", + type: "#FFC799", + punctuation: "#A0A0A0", }, ), withLazySyntaxStyle(