mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 16:03:45 +00:00
fix(terminal): apply mild contrast floor for dark backgrounds
Raise the dark-background contrast floor from 1 (disabled) to 3 (WCAG-AA large-text) to rescue near-background text like Antigravity's body text (#262b30 on #1e242a ~1.1:1). The floor stays well below saturated ANSI colors (~3-3.4:1), avoiding the over-brightening regression #7934.
This commit is contained in:
@@ -417,13 +417,15 @@ describe('applyTerminalAppearance theme assignment', () => {
|
||||
expect(pane.terminal.options.minimumContrastRatio).toBe(4.5)
|
||||
})
|
||||
|
||||
it('disables xterm contrast correction on dark themes', () => {
|
||||
it('applies the mild dark-background contrast floor on dark themes', () => {
|
||||
// #10104: a floor of 3 rescues near-background body text (e.g. Antigravity's #262b30 on #1e242a)
|
||||
// without the 4.5-floor over-brightening of vibrant ANSI colors that #7934 fixed.
|
||||
const pane = makePane(1)
|
||||
const settings = getDefaultSettings('/tmp')
|
||||
|
||||
apply(pane, { ...settings, theme: 'dark' })
|
||||
|
||||
expect(pane.terminal.options.minimumContrastRatio).toBe(1)
|
||||
expect(pane.terminal.options.minimumContrastRatio).toBe(3)
|
||||
})
|
||||
|
||||
it('re-gates contrast correction when the theme flips live', () => {
|
||||
@@ -434,17 +436,17 @@ describe('applyTerminalAppearance theme assignment', () => {
|
||||
expect(pane.terminal.options.minimumContrastRatio).toBe(4.5)
|
||||
|
||||
apply(pane, { ...settings, theme: 'dark' })
|
||||
expect(pane.terminal.options.minimumContrastRatio).toBe(1)
|
||||
expect(pane.terminal.options.minimumContrastRatio).toBe(3)
|
||||
})
|
||||
|
||||
it('disables contrast correction in light mode when the terminal matches dark mode', () => {
|
||||
it('applies the dark-background floor in light mode when the terminal matches dark mode', () => {
|
||||
// terminalUseSeparateLightTheme=false keeps the dark terminal theme in light app mode; the gate must follow the background.
|
||||
const pane = makePane(1)
|
||||
const settings = getDefaultSettings('/tmp')
|
||||
|
||||
apply(pane, { ...settings, theme: 'light', terminalUseSeparateLightTheme: false })
|
||||
|
||||
expect(pane.terminal.options.minimumContrastRatio).toBe(1)
|
||||
expect(pane.terminal.options.minimumContrastRatio).toBe(3)
|
||||
})
|
||||
|
||||
it('keeps contrast correction in dark mode when a light theme fills the dark slot', () => {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
DARK_BG_MIN_CONTRAST,
|
||||
LIGHT_BG_MIN_CONTRAST,
|
||||
resolveTerminalMinimumContrastRatio
|
||||
} from './terminal-contrast-correction'
|
||||
|
||||
// WCAG relative-luminance contrast ratio, matching xterm's minimumContrastRatio gate.
|
||||
function contrastRatio(a: string, b: string): number {
|
||||
const lum = (hex: string): number => {
|
||||
const n = Number.parseInt(hex.replace('#', ''), 16)
|
||||
const toLinear = (channel: number): number => {
|
||||
const c = channel / 255
|
||||
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
|
||||
}
|
||||
const r = toLinear((n >> 16) & 0xff)
|
||||
const g = toLinear((n >> 8) & 0xff)
|
||||
const bl = toLinear(n & 0xff)
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * bl
|
||||
}
|
||||
const la = lum(a)
|
||||
const lb = lum(b)
|
||||
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05)
|
||||
}
|
||||
|
||||
describe('resolveTerminalMinimumContrastRatio', () => {
|
||||
it('returns the light-background floor for a light terminal background', () => {
|
||||
expect(resolveTerminalMinimumContrastRatio('#ffffff', 'light')).toBe(LIGHT_BG_MIN_CONTRAST)
|
||||
})
|
||||
|
||||
it('returns the dark-background floor for a dark terminal background', () => {
|
||||
expect(resolveTerminalMinimumContrastRatio('#1e242a', 'dark')).toBe(DARK_BG_MIN_CONTRAST)
|
||||
})
|
||||
|
||||
it('follows the composed background, not the app surface (light theme in the dark slot)', () => {
|
||||
expect(resolveTerminalMinimumContrastRatio('#fbf1c7', 'dark')).toBe(LIGHT_BG_MIN_CONTRAST)
|
||||
})
|
||||
|
||||
it('treats an undefined/transparent background as dark', () => {
|
||||
expect(resolveTerminalMinimumContrastRatio(undefined, 'dark')).toBe(DARK_BG_MIN_CONTRAST)
|
||||
})
|
||||
})
|
||||
|
||||
// #10104: the dark-background floor must sit in the window that rescues near-background body text
|
||||
// without over-brightening vibrant ANSI colors (the #7934 regression). Guarding both edges keeps a
|
||||
// future tweak from silently sliding out of that window.
|
||||
describe('DARK_BG_MIN_CONTRAST rescue window', () => {
|
||||
const DARK_BG = '#1e242a'
|
||||
|
||||
it('is high enough to lift Antigravity-style near-background body text', () => {
|
||||
// #262b30 on #1e242a is ~1.1:1 — invisible at floor 1. The floor must exceed it so xterm corrects it.
|
||||
expect(contrastRatio(DARK_BG, '#262b30')).toBeLessThan(DARK_BG_MIN_CONTRAST)
|
||||
})
|
||||
|
||||
it('stays below the contrast that saturated ANSI colors naturally reach on a dark background', () => {
|
||||
// Normal red/blue/magenta sit at ~3.0-3.4:1 here; the floor must not exceed them or xterm would
|
||||
// wash them toward white — exactly the over-brightening #7934 disabled the 4.5 floor to avoid.
|
||||
for (const ansi of ['#cd3131', '#2472c8', '#bc3fbc']) {
|
||||
expect(contrastRatio(DARK_BG, ansi)).toBeGreaterThanOrEqual(DARK_BG_MIN_CONTRAST)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,10 +1,15 @@
|
||||
import { isTerminalBackgroundLight } from '@/lib/terminal-title-contrast'
|
||||
|
||||
// xterm minimumContrastRatio tuning (#7934). Light backgrounds keep WCAG-AA correction so invisible
|
||||
// white/bright-white ANSI body text stays readable; dark backgrounds disable it (ratio 1) because
|
||||
// correction over-brightens vibrant ANSI colors.
|
||||
// xterm minimumContrastRatio tuning (#7934, #10104). Light backgrounds keep WCAG-AA correction so
|
||||
// invisible white/bright-white ANSI body text stays readable. Dark backgrounds use a mild floor of 3
|
||||
// (WCAG-AA large-text): high enough to rescue near-background body text — e.g. Antigravity's #262b30
|
||||
// on #1e242a (~1.1:1) — while staying far milder than the light-background 4.5 that badly
|
||||
// over-brightened vibrant colors (#7934). On most dark themes saturated ANSI colors already clear 3:1
|
||||
// and are untouched; a few (e.g. Homebrew red/blue on pure black, Gruvbox Dark red) sit below 3:1 and
|
||||
// get mildly lifted — accepted because those were already near-illegible, so the nudge helps rather
|
||||
// than harms (see the builtin-catalog exceptions pinned in terminal-contrast-correction.test.ts).
|
||||
export const LIGHT_BG_MIN_CONTRAST = 4.5
|
||||
export const DARK_BG_MIN_CONTRAST = 1
|
||||
export const DARK_BG_MIN_CONTRAST = 3
|
||||
|
||||
// Why gate by background luminance, not app mode (#7934): either theme slot can hold either kind of
|
||||
// theme (match-dark-mode, or a light theme in the dark slot), so follow the composed background.
|
||||
|
||||
Reference in New Issue
Block a user