From 6a06973b0d0f64a1c44719330d611d79d3d0c6a7 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 20 Apr 2026 11:12:13 -0500 Subject: [PATCH] fix(tui): route update-behind banner through theme + auto-detect light terminals (#11300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - branding.tsx: `color="yellow"` → `t.color.warn` so light-mode users get the burnt-orange warn instead of unreadable bright yellow on white bg. - theme.ts: replace HERMES_TUI_LIGHT regex with `detectLightMode(env)` that also sniffs `COLORFGBG` (XFCE Terminal, rxvt, Terminal.app, iTerm2). Bg slot 7 or 15 → LIGHT_THEME. Explicit HERMES_TUI_LIGHT (on *or* off) still wins. - tests: cover empty env, explicit on/off, COLORFGBG positions, and off-override. --- ui-tui/src/__tests__/theme.test.ts | 30 ++++++++++++++++++++++++++++-- ui-tui/src/components/branding.tsx | 8 ++++---- ui-tui/src/theme.ts | 21 +++++++++++++++++++-- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/ui-tui/src/__tests__/theme.test.ts b/ui-tui/src/__tests__/theme.test.ts index 4fe165c8d56..db2b1eac381 100644 --- a/ui-tui/src/__tests__/theme.test.ts +++ b/ui-tui/src/__tests__/theme.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import { DARK_THEME, DEFAULT_THEME, fromSkin, LIGHT_THEME } from '../theme.js' +import { DARK_THEME, DEFAULT_THEME, detectLightMode, fromSkin, LIGHT_THEME } from '../theme.js' describe('DEFAULT_THEME', () => { it('has brand defaults', () => { @@ -30,11 +30,37 @@ describe('LIGHT_THEME', () => { }) describe('DEFAULT_THEME aliasing', () => { - it('defaults to DARK_THEME when HERMES_TUI_LIGHT is unset', () => { + it('defaults to DARK_THEME when nothing signals light', () => { expect(DEFAULT_THEME).toBe(DARK_THEME) }) }) +describe('detectLightMode', () => { + it('returns false on empty env', () => { + expect(detectLightMode({})).toBe(false) + }) + + it('honors HERMES_TUI_LIGHT on/off', () => { + expect(detectLightMode({ HERMES_TUI_LIGHT: '1' })).toBe(true) + expect(detectLightMode({ HERMES_TUI_LIGHT: 'true' })).toBe(true) + expect(detectLightMode({ HERMES_TUI_LIGHT: 'on' })).toBe(true) + expect(detectLightMode({ HERMES_TUI_LIGHT: '0' })).toBe(false) + expect(detectLightMode({ HERMES_TUI_LIGHT: 'off' })).toBe(false) + }) + + it('sniffs COLORFGBG bg slots 7 and 15 as light (#11300)', () => { + expect(detectLightMode({ COLORFGBG: '0;15' })).toBe(true) + expect(detectLightMode({ COLORFGBG: '0;default;15' })).toBe(true) + expect(detectLightMode({ COLORFGBG: '0;7' })).toBe(true) + expect(detectLightMode({ COLORFGBG: '15;0' })).toBe(false) + expect(detectLightMode({ COLORFGBG: '7;default;0' })).toBe(false) + }) + + it('lets HERMES_TUI_LIGHT=0 override a light COLORFGBG', () => { + expect(detectLightMode({ COLORFGBG: '0;15', HERMES_TUI_LIGHT: '0' })).toBe(false) + }) +}) + describe('fromSkin', () => { it('overrides banner colors', () => { expect(fromSkin({ banner_title: '#FF0000' }, {}).color.gold).toBe('#FF0000') diff --git a/ui-tui/src/components/branding.tsx b/ui-tui/src/components/branding.tsx index 919c34b612f..5922e71ba71 100644 --- a/ui-tui/src/components/branding.tsx +++ b/ui-tui/src/components/branding.tsx @@ -161,16 +161,16 @@ export function SessionPanel({ info, sid, t }: SessionPanelProps) { {typeof info.update_behind === 'number' && info.update_behind > 0 && ( - + ! {info.update_behind} {info.update_behind === 1 ? 'commit' : 'commits'} behind - + {' '} - run{' '} - + {info.update_command || 'hermes update'} - + {' '} to update diff --git a/ui-tui/src/theme.ts b/ui-tui/src/theme.ts index 386e436f523..122907895bd 100644 --- a/ui-tui/src/theme.ts +++ b/ui-tui/src/theme.ts @@ -171,9 +171,26 @@ export const LIGHT_THEME: Theme = { bannerHero: '' } -const LIGHT_MODE = /^(?:1|true|yes|on)$/i.test((process.env.HERMES_TUI_LIGHT ?? '').trim()) +// Pick light vs dark. Explicit `HERMES_TUI_LIGHT` wins; otherwise sniff +// `COLORFGBG` (set by XFCE Terminal, rxvt, Terminal.app, etc.) — last field is the +// background ANSI index; 7/15 are the "white" slots most light themes emit (#11300). +export function detectLightMode(env: NodeJS.ProcessEnv = process.env): boolean { + const explicit = (env.HERMES_TUI_LIGHT ?? '').trim().toLowerCase() -export const DEFAULT_THEME: Theme = LIGHT_MODE ? LIGHT_THEME : DARK_THEME + if (/^(?:1|true|yes|on)$/.test(explicit)) { + return true + } + + if (/^(?:0|false|no|off)$/.test(explicit)) { + return false + } + + const bg = Number((env.COLORFGBG ?? '').trim().split(';').at(-1)) + + return bg === 7 || bg === 15 +} + +export const DEFAULT_THEME: Theme = detectLightMode() ? LIGHT_THEME : DARK_THEME // ── Skin → Theme ─────────────────────────────────────────────────────