mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-27 17:58:07 +00:00
fix(tui): keep the Apple Terminal dim fallback inside the active palette
Terminal.app ignores SGR 2, so dim is substituted with a literal color. That color was hardcoded to #6B7280 — a cold slate that belongs to no theme. Next to themed text on the same line it reads as a second, foreign foreground: on a light profile, gray words beside near-black ones. Make the tone theme-supplied via setDimFallbackColor, fed the active muted tone from the same effect that already publishes selectionBg. #6B7280 stays as the pre-theme boot default so the first frame is unchanged, and terminals that honor SGR 2 are untouched.
This commit is contained in:
parent
b8bf368b02
commit
4c03d5bff2
5 changed files with 50 additions and 4 deletions
|
|
@ -10,7 +10,7 @@ export { NoSelect } from './ink/components/NoSelect.js'
|
|||
export { RawAnsi } from './ink/components/RawAnsi.js'
|
||||
export { default as ScrollBox } from './ink/components/ScrollBox.js'
|
||||
export { default as Spacer } from './ink/components/Spacer.js'
|
||||
export { default as Text } from './ink/components/Text.js'
|
||||
export { setDimFallbackColor, default as Text } from './ink/components/Text.js'
|
||||
export { default as useApp } from './ink/hooks/use-app.js'
|
||||
export { useCursorAdvance } from './ink/hooks/use-cursor-advance.js'
|
||||
export { useDeclaredCursor } from './ink/hooks/use-declared-cursor.js'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { dimColorFallback, shouldUseAnsiDim } from './Text.js'
|
||||
import { dimColorFallback, setDimFallbackColor, shouldUseAnsiDim } from './Text.js'
|
||||
|
||||
describe('shouldUseAnsiDim', () => {
|
||||
it('disables ANSI dim on VTE terminals by default', () => {
|
||||
|
|
@ -23,6 +23,10 @@ describe('shouldUseAnsiDim', () => {
|
|||
})
|
||||
|
||||
describe('dimColorFallback', () => {
|
||||
afterEach(() => {
|
||||
setDimFallbackColor(undefined)
|
||||
})
|
||||
|
||||
it('renders Apple Terminal dim as muted gray by default', () => {
|
||||
expect(dimColorFallback({ TERM_PROGRAM: 'Apple_Terminal' } as NodeJS.ProcessEnv)).toBe('#6B7280')
|
||||
})
|
||||
|
|
@ -39,4 +43,23 @@ describe('dimColorFallback', () => {
|
|||
dimColorFallback({ HERMES_TUI_DIM: '0', TERM_PROGRAM: 'Apple_Terminal' } as NodeJS.ProcessEnv)
|
||||
).toBeUndefined()
|
||||
})
|
||||
|
||||
it('uses the theme tone once one is supplied, so dim stays in-palette', () => {
|
||||
setDimFallbackColor('#936e06')
|
||||
|
||||
expect(dimColorFallback({ TERM_PROGRAM: 'Apple_Terminal' } as NodeJS.ProcessEnv)).toBe('#936e06')
|
||||
})
|
||||
|
||||
it('falls back to the boot default when the theme tone is cleared', () => {
|
||||
setDimFallbackColor('#936e06')
|
||||
setDimFallbackColor(undefined)
|
||||
|
||||
expect(dimColorFallback({ TERM_PROGRAM: 'Apple_Terminal' } as NodeJS.ProcessEnv)).toBe('#6B7280')
|
||||
})
|
||||
|
||||
it('stays inert on terminals that honor SGR 2, whatever the theme tone', () => {
|
||||
setDimFallbackColor('#936e06')
|
||||
|
||||
expect(dimColorFallback({ TERM: 'xterm-256color' } as NodeJS.ProcessEnv)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -84,6 +84,19 @@ export function shouldUseAnsiDim(env: NodeJS.ProcessEnv = process.env): boolean
|
|||
return !env.VTE_VERSION
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminals that ignore SGR 2 need a literal color instead. The tone is
|
||||
* theme-supplied (setDimFallbackColor, called from the theme effect) so it
|
||||
* stays inside the active palette; the slate below is only the pre-theme
|
||||
* boot default. A hardcoded value here reads as an off-palette foreground
|
||||
* next to themed spans on the same line — cold gray beside warm ink.
|
||||
*/
|
||||
let dimFallbackColor: Color = LEGACY_APPLE_DIM_COLOR
|
||||
|
||||
export function setDimFallbackColor(color: Color | undefined): void {
|
||||
dimFallbackColor = color || LEGACY_APPLE_DIM_COLOR
|
||||
}
|
||||
|
||||
export function dimColorFallback(env: NodeJS.ProcessEnv = process.env): Color | undefined {
|
||||
const override = (env.HERMES_TUI_DIM ?? '').trim()
|
||||
|
||||
|
|
@ -91,7 +104,7 @@ export function dimColorFallback(env: NodeJS.ProcessEnv = process.env): Color |
|
|||
return undefined
|
||||
}
|
||||
|
||||
return (env.TERM_PROGRAM ?? '').trim() === 'Apple_Terminal' ? LEGACY_APPLE_DIM_COLOR : undefined
|
||||
return (env.TERM_PROGRAM ?? '').trim() === 'Apple_Terminal' ? dimFallbackColor : undefined
|
||||
}
|
||||
|
||||
const memoizedStylesForWrap: Record<NonNullable<Styles['textWrap']>, Styles> = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
forceRedraw,
|
||||
type ScrollBoxHandle,
|
||||
setDimFallbackColor,
|
||||
useApp,
|
||||
useHasSelection,
|
||||
useSelection,
|
||||
|
|
@ -245,6 +246,14 @@ export function useMainApp(gw: GatewayClient) {
|
|||
selection.setSelectionBgColor(ui.theme.color.selectionBg)
|
||||
}, [selection, ui.theme.color.selectionBg])
|
||||
|
||||
// Terminals that ignore SGR 2 (Apple_Terminal) get a literal color for
|
||||
// `dim` instead. Feed it the theme's muted tone so dimmed spans stay in
|
||||
// the palette — a hardcoded gray renders as a foreign foreground next to
|
||||
// themed text on the same line.
|
||||
useEffect(() => {
|
||||
setDimFallbackColor(ui.theme.color.muted)
|
||||
}, [ui.theme.color.muted])
|
||||
|
||||
// macOS Terminal.app does not forward Cmd+C to fullscreen TUIs that enable
|
||||
// mouse tracking, so the only reliable native-feeling path is iTerm-style
|
||||
// copy-on-select: once a drag creates a stable TUI selection, write it to
|
||||
|
|
|
|||
1
ui-tui/src/types/hermes-ink.d.ts
vendored
1
ui-tui/src/types/hermes-ink.d.ts
vendored
|
|
@ -104,6 +104,7 @@ declare module '@hermes/ink' {
|
|||
export const NoSelect: React.ComponentType<any>
|
||||
export const ScrollBox: React.ComponentType<any>
|
||||
export const Text: React.ComponentType<any>
|
||||
export function setDimFallbackColor(color: string | undefined): void
|
||||
export const TextInput: React.ComponentType<any>
|
||||
export const stringWidth: (s: string) => number
|
||||
export function isXtermJs(): boolean
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue