From f1a8e99942e6150d5785bdd734c4d9ff63dfa7f7 Mon Sep 17 00:00:00 2001 From: brooklyn! Date: Wed, 6 May 2026 14:01:56 -0700 Subject: [PATCH] fix(tui): honor skin highlight colors (#20895) --- hermes_cli/skin_engine.py | 1 + ui-tui/src/__tests__/theme.test.ts | 28 +++++++++++++++++++++++ ui-tui/src/components/appOverlays.tsx | 12 ++++++++-- ui-tui/src/theme.ts | 24 +++++++++++++++---- website/docs/user-guide/features/skins.md | 2 ++ 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/hermes_cli/skin_engine.py b/hermes_cli/skin_engine.py index 6ca6f8adf3..0acb41d687 100644 --- a/hermes_cli/skin_engine.py +++ b/hermes_cli/skin_engine.py @@ -42,6 +42,7 @@ All fields are optional. Missing values inherit from the ``default`` skin. session_border: "#8B8682" # Session ID dim color status_bar_bg: "#1a1a2e" # TUI status/usage bar background voice_status_bg: "#1a1a2e" # TUI voice status background + selection_bg: "#333355" # TUI mouse-selection highlight background completion_menu_bg: "#1a1a2e" # Completion menu background completion_menu_current_bg: "#333355" # Active completion row background completion_menu_meta_bg: "#1a1a2e" # Completion meta column background diff --git a/ui-tui/src/__tests__/theme.test.ts b/ui-tui/src/__tests__/theme.test.ts index 30a047df66..d45576698d 100644 --- a/ui-tui/src/__tests__/theme.test.ts +++ b/ui-tui/src/__tests__/theme.test.ts @@ -209,6 +209,34 @@ describe('fromSkin', () => { expect(theme.color.completionCurrentBg).toBe('#bfbfbf') }) + it('uses active completion color as the selection highlight fallback', async () => { + const { fromSkin } = await importThemeWithCleanEnv() + + const theme = fromSkin({ completion_menu_current_bg: '#123456' }, {}) + + expect(theme.color.selectionBg).toBe('#123456') + }) + + it('maps completion meta background colors from skins', async () => { + const { fromSkin } = await importThemeWithCleanEnv() + + const theme = fromSkin({ + completion_menu_meta_bg: '#111111', + completion_menu_meta_current_bg: '#222222' + }, {}) + + expect(theme.color.completionMetaBg).toBe('#111111') + expect(theme.color.completionMetaCurrentBg).toBe('#222222') + }) + + it('lets selection_bg override completion highlight colors', async () => { + const { fromSkin } = await importThemeWithCleanEnv() + + const theme = fromSkin({ completion_menu_current_bg: '#123456', selection_bg: '#654321' }, {}) + + expect(theme.color.selectionBg).toBe('#654321') + }) + it('overrides branding', async () => { const { fromSkin } = await importThemeWithCleanEnv() const { brand } = fromSkin({}, { agent_name: 'TestBot', prompt_symbol: '$' }) diff --git a/ui-tui/src/components/appOverlays.tsx b/ui-tui/src/components/appOverlays.tsx index e4a80ba816..c12624a4bf 100644 --- a/ui-tui/src/components/appOverlays.tsx +++ b/ui-tui/src/components/appOverlays.tsx @@ -182,7 +182,7 @@ export function FloatingOverlays({ return ( - {item.meta ? {item.meta} : null} + {item.meta ? ( + + {' '} + {item.meta} + + ) : null} ) })} diff --git a/ui-tui/src/theme.ts b/ui-tui/src/theme.ts index 2a55709036..6d7426caed 100644 --- a/ui-tui/src/theme.ts +++ b/ui-tui/src/theme.ts @@ -6,6 +6,8 @@ export interface ThemeColors { muted: string completionBg: string completionCurrentBg: string + completionMetaBg: string + completionMetaCurrentBg: string label: string ok: string @@ -264,8 +266,10 @@ export const DARK_THEME: Theme = { // new value sits ~60% luminance — readable without losing the "muted / // secondary" semantic. Field labels still use `label` (65%) which // stays brighter so hierarchy holds. - completionBg: '#FFFFFF', - completionCurrentBg: mix('#FFFFFF', '#FFBF00', 0.25), + completionBg: '#1a1a2e', + completionCurrentBg: '#333355', + completionMetaBg: '#1a1a2e', + completionMetaCurrentBg: '#333355', label: '#DAA520', ok: '#4caf50', @@ -312,6 +316,8 @@ export const LIGHT_THEME: Theme = { muted: '#7A5A0F', completionBg: '#F5F5F5', completionCurrentBg: mix('#F5F5F5', '#A0651C', 0.25), + completionMetaBg: '#F5F5F5', + completionMetaCurrentBg: mix('#F5F5F5', '#A0651C', 0.25), label: '#7A5A0F', ok: '#2E7D32', @@ -517,12 +523,20 @@ export function fromSkin( ): Theme { const d = DEFAULT_THEME const c = (k: string) => colors[k] + const hasSkinColors = Object.keys(colors).length > 0 const accent = c('ui_accent') ?? c('banner_accent') ?? d.color.accent const bannerAccent = c('banner_accent') ?? c('banner_title') ?? d.color.accent const muted = c('banner_dim') ?? d.color.muted const completionBg = c('completion_menu_bg') ?? d.color.completionBg + const completionCurrentBg = + c('completion_menu_current_bg') ?? + (hasSkinColors ? mix(completionBg, bannerAccent, 0.25) : d.color.completionCurrentBg) + + const completionMetaBg = c('completion_menu_meta_bg') ?? completionBg + const completionMetaCurrentBg = c('completion_menu_meta_current_bg') ?? completionCurrentBg + return normalizeThemeForAnsiLightTerminal({ color: { primary: c('ui_primary') ?? c('banner_title') ?? d.color.primary, @@ -531,7 +545,9 @@ export function fromSkin( text: c('ui_text') ?? c('banner_text') ?? d.color.text, muted, completionBg, - completionCurrentBg: c('completion_menu_current_bg') ?? mix(completionBg, bannerAccent, 0.25), + completionCurrentBg, + completionMetaBg, + completionMetaCurrentBg, label: c('ui_label') ?? d.color.label, ok: c('ui_ok') ?? d.color.ok, @@ -548,7 +564,7 @@ export function fromSkin( statusWarn: c('ui_warn') ?? d.color.statusWarn, statusBad: d.color.statusBad, statusCritical: d.color.statusCritical, - selectionBg: c('selection_bg') ?? d.color.selectionBg, + selectionBg: c('selection_bg') ?? c('completion_menu_current_bg') ?? (hasSkinColors ? completionCurrentBg : d.color.selectionBg), diffAdded: d.color.diffAdded, diffRemoved: d.color.diffRemoved, diff --git a/website/docs/user-guide/features/skins.md b/website/docs/user-guide/features/skins.md index 5648c46e03..def81d0e7b 100644 --- a/website/docs/user-guide/features/skins.md +++ b/website/docs/user-guide/features/skins.md @@ -67,6 +67,7 @@ Controls all color values throughout the CLI. Values are hex color strings. | `session_border` | Session ID dim border color | `#8B8682` | | `status_bar_bg` | Background color for the TUI status / usage bar | `#1a1a2e` | | `voice_status_bg` | Background color for the voice-mode status badge | `#1a1a2e` | +| `selection_bg` | Background color for the TUI mouse-selection highlighter. Falls back to `completion_menu_current_bg` when unset. | `#333355` | | `completion_menu_bg` | Background color for the completion menu list | `#1a1a2e` | | `completion_menu_current_bg` | Background color for the active completion row | `#333355` | | `completion_menu_meta_bg` | Background color for the completion meta column | `#1a1a2e` | @@ -139,6 +140,7 @@ colors: session_border: "#8B8682" status_bar_bg: "#1a1a2e" voice_status_bg: "#1a1a2e" + selection_bg: "#333355" completion_menu_bg: "#1a1a2e" completion_menu_current_bg: "#333355" completion_menu_meta_bg: "#1a1a2e"