mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-23 16:36:23 +00:00
Merge pull request #69671 from NousResearch/bb/skin-owns-polarity
fix(ui-tui): a skin that authors a background owns its polarity
This commit is contained in:
commit
8d6e045b8f
3 changed files with 70 additions and 8 deletions
|
|
@ -302,6 +302,42 @@ describe('fromSkin', () => {
|
|||
expect(theme.color.prompt).toBe('ansi256(136)')
|
||||
})
|
||||
|
||||
// ── A skin that authors a background OWNS its polarity ──────────────
|
||||
// The TUI paints the terminal with the skin's background (OSC-11), so
|
||||
// every adaptation pass must run against the skin's canvas, not the host
|
||||
// profile the skin just covered. The real-world failure: a pure-black
|
||||
// skin on light-mode Apple Terminal got its text ansi256-bucketed for a
|
||||
// light background that no longer exists — invisible on the painted black.
|
||||
|
||||
it('a dark-background skin on light Apple Terminal keeps its truecolor text (no light-mode bucketing)', async () => {
|
||||
const { fromSkin } = await importThemeWithEnv({ TERM_PROGRAM: 'Apple_Terminal' })
|
||||
|
||||
const theme = fromSkin({ background: '#000000', ui_accent: '#ff9e18', ui_text: '#ffa726' }, {})
|
||||
|
||||
expect(theme.color.text).toBe('#ffa726')
|
||||
expect(theme.color.prompt).not.toMatch(/^ansi256/)
|
||||
})
|
||||
|
||||
it('a skin background outranks the cached host background for adaptation and tone derivation', async () => {
|
||||
const { fromSkin } = await importThemeWithEnv({ HERMES_TUI_BACKGROUND: '#ffffff' })
|
||||
|
||||
const theme = fromSkin({ background: '#000000', ui_text: '#ffa726' }, {})
|
||||
|
||||
// Text is not contrast-lifted toward a white host it painted over…
|
||||
expect(theme.color.text).toBe('#ffa726')
|
||||
// …and derived fills mix against the skin's black, not the host's white.
|
||||
expect(luminance(theme.color.completionBg)).toBeLessThanOrEqual(0.35)
|
||||
expect(luminance(theme.color.statusBg)).toBeLessThanOrEqual(0.35)
|
||||
})
|
||||
|
||||
it('skinIsLight: the authored background decides; host detection only when absent', async () => {
|
||||
const { skinIsLight } = await importThemeWithEnv({ HERMES_TUI_BACKGROUND: '#ffffff' })
|
||||
|
||||
expect(skinIsLight({ background: '#000000' })).toBe(false)
|
||||
expect(skinIsLight({ background: '#f5f5f5' })).toBe(true)
|
||||
expect(skinIsLight({})).toBe(true) // no canvas of its own → host polarity
|
||||
})
|
||||
|
||||
it('keeps truecolor light Apple Terminal in truecolor (adapting, not ansi256-bucketing)', async () => {
|
||||
const { contrastRatio, fromSkin } = await importThemeWithEnv({
|
||||
COLORTERM: 'truecolor',
|
||||
|
|
|
|||
|
|
@ -21,7 +21,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, detectLightMode, fromSkin, type Theme } from '../theme.js'
|
||||
import { defaultThemeForCurrentBackground, fromSkin, skinIsLight, type Theme } from '../theme.js'
|
||||
import type { Msg, SubagentProgress, SubagentStatus } from '../types.js'
|
||||
|
||||
import { applyDelegationStatus, getDelegationState } from './delegationStore.js'
|
||||
|
|
@ -45,8 +45,10 @@ const themeForSkin = (s: GatewaySkin) => {
|
|||
// can ship a fills-only `light_colors` (flip the dark navy menu/status fills
|
||||
// to light on a light terminal) while its vivid foreground golds keep coming
|
||||
// from `colors` and render raw through fromSkin's shim. A full paired block
|
||||
// still works — it just overrides every key it lists.
|
||||
const paired = detectLightMode() ? s.light_colors : s.dark_colors
|
||||
// still works — it just overrides every key it lists. Polarity follows the
|
||||
// skin's authored background when it has one (the skin paints the terminal
|
||||
// with it), else the host's.
|
||||
const paired = skinIsLight(s.colors ?? {}) ? s.light_colors : s.dark_colors
|
||||
|
||||
const colors = paired && Object.keys(paired).length ? { ...(s.colors ?? {}), ...paired } : (s.colors ?? {})
|
||||
|
||||
|
|
|
|||
|
|
@ -788,6 +788,28 @@ export function defaultThemeForCurrentBackground(env: NodeJS.ProcessEnv = proces
|
|||
|
||||
// ── Skin → Theme ─────────────────────────────────────────────────────
|
||||
|
||||
/** The skin's authored canvas as a #-prefixed hex, or null when absent/junk. */
|
||||
const authoredBackground = (raw: string | undefined): null | string => {
|
||||
const v = (raw ?? '').trim()
|
||||
|
||||
return backgroundLuminance(v) === null ? null : v.startsWith('#') ? v : `#${v}`
|
||||
}
|
||||
|
||||
/**
|
||||
* A skin that authors a background OWNS its polarity: the TUI paints the
|
||||
* terminal with it (applySkin → OSC-11), so contrast adaptation, the derived
|
||||
* tone ladder, and ANSI light-terminal normalization must all run against the
|
||||
* skin's canvas — not the host profile the skin just covered. (A pure-black
|
||||
* skin on a light Apple Terminal otherwise gets its text remapped for a light
|
||||
* background it painted over: invisible.) Host detection still governs skins
|
||||
* without a background — they render on the terminal's own surface.
|
||||
*/
|
||||
export function skinIsLight(colors: SkinColors, env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
const authored = backgroundLuminance(colors['background'] ?? '')
|
||||
|
||||
return authored === null ? detectLightMode(env) : authored >= LUMA_LIGHT_THRESHOLD
|
||||
}
|
||||
|
||||
export function fromSkin(
|
||||
colors: SkinColors,
|
||||
branding: SkinBranding,
|
||||
|
|
@ -796,11 +818,13 @@ export function fromSkin(
|
|||
toolPrefix = '',
|
||||
helpHeader = ''
|
||||
): Theme {
|
||||
// Live detection (not the module-load snapshot): by the time the gateway
|
||||
// skin arrives, the OSC-11 background probe has usually answered and cached
|
||||
// itself into HERMES_TUI_BACKGROUND. See #applySkin / syncThemeToTerminalBackground.
|
||||
const isLight = detectLightMode()
|
||||
const bg = referenceBackground(isLight)
|
||||
// Polarity: the skin's own canvas when it authors one (see skinIsLight);
|
||||
// otherwise live host detection (not the module-load snapshot — by the time
|
||||
// the gateway skin arrives, the OSC-11 probe has usually answered and cached
|
||||
// itself into HERMES_TUI_BACKGROUND. See #applySkin / syncThemeToTerminalBackground).
|
||||
const skinBg = authoredBackground(colors['background'])
|
||||
const isLight = skinIsLight(colors)
|
||||
const bg = skinBg ?? referenceBackground(isLight)
|
||||
const base = isLight ? LIGHT_SEEDS : DARK_SEEDS
|
||||
const d = isLight ? LIGHT_THEME : DARK_THEME
|
||||
const c = (k: string) => colors[k]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue