mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-27 17:58:07 +00:00
fix(tui): paint the OSC-10 default foreground on quantizing terminals
A skin that authors a background paints both terminal defaults: OSC-11 for the backdrop, OSC-10 to re-base every default-fg token (markdown body, borders, anything rendered without an explicit color) onto the theme's text tone. The OSC-10 half never fired on a limited-palette terminal. `normalizeThemeForAnsiLightTerminal` rewrites the foreground tones to `ansi256(N)`, and `setTerminalForeground` only accepts `#rrggbb` — so the argument failed the hex test and the write was silently skipped. The background moved to the skin while default-fg text stayed on the host profile's foreground. That split is the reported symptom: prose renders in the terminal's own near-black while every themed token beside it renders the skin's gray, so the base text color appears to change between adjacent words. A resize repaints the affected cells from the screen buffer, which is why the text "goes black" on resize and why the mix looks scattered rather than uniform. Resolve the tone through a new `themeToneHex` before handing it to OSC-10: `ansi256(N)` maps through the xterm grayscale ramp and 6x6x6 cube, an authored hex passes through, and anything with no paintable color yields '' (which correctly clears back to the terminal default). Verified on Terminal.app + the `brooklyn` skin: `theme.color.text` is `ansi256(238)`, previously dropped, now emitted as `ESC]10;#444444 BEL` alongside the existing `ESC]11;#f6f9fd BEL`.
This commit is contained in:
parent
98fe6d0a8e
commit
0b1ee22b9e
3 changed files with 57 additions and 2 deletions
|
|
@ -653,3 +653,31 @@ describe('background-aware adaptation (OSC-11 light terminals)', () => {
|
|||
expect(color.statusCritical).toBe(fromSkin({ ui_error: '#dd2222' }, {}).color.error)
|
||||
})
|
||||
})
|
||||
|
||||
describe('themeToneHex', () => {
|
||||
it('resolves a tone to the literal color it paints as', async () => {
|
||||
const { themeToneHex } = await importThemeWithCleanEnv()
|
||||
|
||||
// 232+ is the grayscale ramp (8 + (n-232)*10); 16-231 is the 6x6x6 cube.
|
||||
expect(themeToneHex('ansi256(238)')).toBe('#444444')
|
||||
expect(themeToneHex('ansi256(161)')).toBe('#d7005f')
|
||||
// An authored hex is already literal.
|
||||
expect(themeToneHex('#e77fa3')).toBe('#e77fa3')
|
||||
// No paintable color ⇒ '', which releases the terminal default.
|
||||
expect(themeToneHex('')).toBe('')
|
||||
expect(themeToneHex('ansi256(999)')).toBe('')
|
||||
expect(themeToneHex('inherit')).toBe('')
|
||||
})
|
||||
|
||||
it('makes every tone paintable on a quantizing terminal', async () => {
|
||||
// The contract OSC-10 depends on: whatever the palette normalizer does to
|
||||
// a tone, themeToneHex still yields a literal `#rrggbb`. Asserted over the
|
||||
// whole palette so a new tone can't silently regress the default paint.
|
||||
const { fromSkin, themeToneHex } = await importThemeWithEnv({ TERM_PROGRAM: 'Apple_Terminal' })
|
||||
const { color } = fromSkin({ background: '#f6f9fd', ui_text: '#4a4550' }, {})
|
||||
|
||||
for (const tone of Object.values(color)) {
|
||||
expect(themeToneHex(tone)).toMatch(/^#[0-9a-f]{6}$/i)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { topLevelSubagents } from '../lib/subagentTree.js'
|
|||
import { isPaintableHex, setTerminalBackground, setTerminalForeground } from '../lib/terminalModes.js'
|
||||
import { formatAbandonedClarify, formatToolCall, stripAnsi } from '../lib/text.js'
|
||||
import { bootSeededPin, invalidateBootBackground, writeBootTheme } from '../lib/themeBoot.js'
|
||||
import { defaultThemeForCurrentBackground, fromSkin, skinIsLight, type Theme } from '../theme.js'
|
||||
import { defaultThemeForCurrentBackground, fromSkin, skinIsLight, type Theme, themeToneHex } from '../theme.js'
|
||||
import type { Msg, SubagentProgress, SubagentStatus } from '../types.js'
|
||||
|
||||
import { applyDelegationStatus, getDelegationState } from './delegationStore.js'
|
||||
|
|
@ -126,11 +126,13 @@ const themesEqual = (a: Theme, b: Theme) => {
|
|||
// the theme's text color. Without the pair, a dark skin on a light terminal
|
||||
// leaves default-fg text at the HOST's near-black: invisible. Opt-in stays
|
||||
// intact: no `background` ⇒ both defaults restore to the terminal's own.
|
||||
// The text tone resolves through themeToneHex because a limited-palette
|
||||
// terminal quantizes it to `ansi256(N)`, which OSC-10 cannot speak.
|
||||
const paintTerminalDefaults = (theme: Theme) => {
|
||||
const background = lastSkin?.colors?.background ?? ''
|
||||
|
||||
setTerminalBackground(background)
|
||||
setTerminalForeground(isPaintableHex(background) ? theme.color.text : '')
|
||||
setTerminalForeground(isPaintableHex(background) ? themeToneHex(theme.color.text) : '')
|
||||
}
|
||||
|
||||
const applySkin = (s: GatewaySkin) => {
|
||||
|
|
|
|||
|
|
@ -222,6 +222,31 @@ function normalizeAnsiForeground(color: string): string {
|
|||
return `ansi256(${ansi})`
|
||||
}
|
||||
|
||||
const ANSI256_RE = /^ansi256\((\d{1,3})\)$/
|
||||
|
||||
/**
|
||||
* The literal `#rrggbb` a theme tone paints as, or '' when it has none.
|
||||
*
|
||||
* The inverse of `normalizeAnsiForeground`: tones are not uniformly hex, since
|
||||
* a limited-palette terminal rewrites the foregrounds to `ansi256(N)`.
|
||||
* Consumers needing a real color — OSC 10/11, which only speak `#rrggbb` —
|
||||
* resolve through here rather than hex-testing the tone, which would silently
|
||||
* skip exactly the terminals that did the quantizing.
|
||||
*/
|
||||
export function themeToneHex(tone: string): string {
|
||||
const ansi = ANSI256_RE.exec(tone.trim())
|
||||
|
||||
if (ansi) {
|
||||
const n = Number(ansi[1])
|
||||
|
||||
return n <= 255 ? toHex(xtermEightBitRgb(n)) : ''
|
||||
}
|
||||
|
||||
const rgb = parseColor(tone)
|
||||
|
||||
return rgb ? toHex(rgb) : ''
|
||||
}
|
||||
|
||||
// ── Defaults ─────────────────────────────────────────────────────────
|
||||
|
||||
const BRAND: ThemeBrand = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue