fix(tui): normalize legacy Terminal.app colors (#17695)

Keep light Terminal.app TUI colors readable by normalizing non-banner theme tokens into ANSI256-safe buckets while preserving truecolor terminals.
This commit is contained in:
brooklyn! 2026-04-29 20:13:49 -07:00 committed by GitHub
parent 31f70d1f2a
commit 4cc6da84a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 487 additions and 50 deletions

View file

@ -16,12 +16,13 @@ const RELEVANT_ENV = [
'HERMES_TUI_THEME',
'HERMES_TUI_BACKGROUND',
'COLORFGBG',
'COLORTERM',
'TERM_PROGRAM'
] as const
async function importThemeWithCleanEnv() {
async function importThemeWithEnv(env: Partial<Record<(typeof RELEVANT_ENV)[number], string>> = {}) {
for (const key of RELEVANT_ENV) {
vi.stubEnv(key, '')
vi.stubEnv(key, env[key] ?? '')
}
vi.resetModules()
@ -29,6 +30,10 @@ async function importThemeWithCleanEnv() {
return import('../theme.js')
}
async function importThemeWithCleanEnv() {
return importThemeWithEnv()
}
afterEach(() => {
vi.unstubAllEnvs()
vi.resetModules()
@ -84,6 +89,12 @@ describe('detectLightMode', () => {
expect(detectLightMode({})).toBe(false)
})
it('defaults Apple Terminal to light when no stronger signal is present', async () => {
const { detectLightMode } = await importThemeWithCleanEnv()
expect(detectLightMode({ TERM_PROGRAM: 'Apple_Terminal' })).toBe(true)
})
it('honors HERMES_TUI_LIGHT on/off', async () => {
const { detectLightMode } = await importThemeWithCleanEnv()
@ -159,8 +170,8 @@ describe('detectLightMode', () => {
it('treats COLORFGBG as authoritative when present so it dominates the TERM_PROGRAM allow-list', async () => {
const { detectLightMode } = await importThemeWithCleanEnv()
// Inject a light-default allow-list so the precedence test is
// meaningful even though the production allow-list is empty.
// Injecting the allow-list keeps this precedence rule explicit even if
// production defaults change.
const allowList = new Set(['Apple_Terminal'])
// Sanity: the allow-list alone WOULD turn this terminal light.
@ -221,6 +232,40 @@ describe('fromSkin', () => {
expect(fromSkin({}, {}).brand.icon).toBe(DEFAULT_THEME.brand.icon)
})
it('normalizes non-banner foregrounds on light Apple Terminal', async () => {
const { fromSkin } = await importThemeWithEnv({ TERM_PROGRAM: 'Apple_Terminal' })
const theme = fromSkin({
banner_accent: '#FFBF00',
banner_border: '#CD7F32',
banner_dim: '#B8860B',
banner_text: '#FFF8DC',
banner_title: '#FFD700',
prompt: '#FFF8DC'
}, {})
expect(theme.color.primary).toBe('#FFD700')
expect(theme.color.accent).toBe('#FFBF00')
expect(theme.color.border).toBe('#CD7F32')
expect(theme.color.muted).toBe('ansi256(245)')
expect(theme.color.text).toBe('ansi256(136)')
expect(theme.color.prompt).toBe('ansi256(136)')
})
it('does not normalize light Apple Terminal when truecolor is advertised', async () => {
const { fromSkin } = await importThemeWithEnv({ COLORTERM: 'truecolor', TERM_PROGRAM: 'Apple_Terminal' })
const theme = fromSkin({ banner_text: '#FFF8DC' }, {})
expect(theme.color.text).toBe('#FFF8DC')
})
it('normalizes Apple Terminal names before matching', async () => {
const { fromSkin } = await importThemeWithEnv({ TERM_PROGRAM: ' Apple_Terminal ' })
const theme = fromSkin({ banner_text: '#FFF8DC' }, {})
expect(theme.color.text).toBe('ansi256(136)')
})
it('passes banner logo/hero', async () => {
const { fromSkin } = await importThemeWithCleanEnv()