mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 00:02:23 +00:00
fix(theme): finish the sweep — plain-button states and midtone grounds
Two gaps in the interaction-state change. `Theme::input` reaches the *outline* button path, which reads the field live, but not the plain one: gpui-component derives `tokens.button_hover` and `tokens.button_active` from `input` once, inside the `apply_config` that `Theme::change` runs — from the stock `#2f2f2f`, before any of `apply_theme`'s overrides exist. A snapshot never sees the fix, so a plain `Button` still hovered and pressed in that grey and `Button::selected` (the terminal search bar's `Aa` / `.*` toggles, the last two left in the app) filled from `tokens.secondary_active` the same way — ~1.03:1 on Dracula, i.e. issue #197 again, one snapshot removed from the field that fixed it. Point those state rungs at the window ladder. `tokens.button`, the resting fill, is deliberately left alone. `legible_ink` picked its extreme with `is_dark`, whose 0.5 luminance threshold is the wrong question: on a midtone ground (luminance 0.18…0.5) it says "dark" while black outreaches white, so a status seed was driven toward white and clamped there *below* the floor with its hue gone — precisely when a user most needs to tell an error from a warning. Take the reachable extreme instead, as `legible_foreground` already does. Every built-in sits far enough from the midpoint that this picks what `is_dark` did, so the derived palettes are unchanged; only an imported scheme could reach the divergent range. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCb8ZDmvdA5xbVtvs647tD
This commit is contained in:
+34
-1
@@ -591,7 +591,19 @@ fn legible_ink(bg: u32, seed: u32, floor: f32) -> u32 {
|
||||
if contrast(seed, bg) >= floor {
|
||||
return seed;
|
||||
}
|
||||
let away = if is_dark(bg) { 0xffffff } else { 0x000000 };
|
||||
// Whichever extreme the background is *further* from, exactly as
|
||||
// [`legible_foreground`] picks it — not `is_dark`, whose 0.5 luminance
|
||||
// threshold is the wrong question here. The two answers only diverge on a
|
||||
// midtone background (luminance 0.18…0.5), where `is_dark` still says "dark"
|
||||
// but black outreaches white: an imported scheme on a mid-grey ground would
|
||||
// have been driven to pure white and clamped there *below* the floor, losing
|
||||
// the hue and failing the job in one go. Every built-in is far enough from
|
||||
// the midpoint that this picks what `is_dark` did.
|
||||
let away = if contrast(0xffffff, bg) >= contrast(0x000000, bg) {
|
||||
0xffffff
|
||||
} else {
|
||||
0x000000
|
||||
};
|
||||
bisect_contrast(seed, away, bg, floor)
|
||||
}
|
||||
|
||||
@@ -1666,6 +1678,27 @@ mod tests {
|
||||
assert_eq!(raise(0x000000, 0x808080, 21.0), 0x808080);
|
||||
}
|
||||
|
||||
/// Conditioning has to take the extreme it can actually *reach*, which on a
|
||||
/// midtone ground is not the one `is_dark`'s 0.5 luminance threshold names.
|
||||
/// A mid-grey background is "dark" by that test, yet white tops out at
|
||||
/// 3.95:1 on it while black manages 5.32:1 — so driving toward white would
|
||||
/// clamp at pure white, below the floor and with the hue thrown away, in the
|
||||
/// one case where a status colour most needs both. Reachable only for an
|
||||
/// imported scheme; every built-in sits far enough from the midpoint that
|
||||
/// this picks the same extreme `is_dark` did.
|
||||
#[test]
|
||||
fn semantic_conditioning_survives_a_midtone_background() {
|
||||
let bg = 0x808080;
|
||||
for seed in [0xff5555u32, 0x50fa7b, 0xf1fa8c, 0x8be9fd] {
|
||||
let ink = legible_ink(bg, seed, TEXT_FLOOR);
|
||||
assert!(
|
||||
contrast(ink, bg) >= TEXT_FLOOR - 0.01,
|
||||
"{seed:#08x} conditioned to {ink:#08x}, only {:.2}:1 on a midtone ground",
|
||||
contrast(ink, bg)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A bad foreground is swapped for a legible black/white; a good one is kept.
|
||||
#[test]
|
||||
fn legible_foreground_rescues_unreadable_text() {
|
||||
|
||||
@@ -709,6 +709,28 @@ pub(crate) fn apply_theme(mut window: Option<&mut Window>, cx: &mut App) {
|
||||
t.input = rgb(surfaces.window.selected).into();
|
||||
t.tokens.input = Hsla::from(rgb(surfaces.window.selected)).into();
|
||||
|
||||
// …but `input` only reaches the *outline* path, which reads the field live.
|
||||
// The plain (non-outline) button family is derived from `input` **once**,
|
||||
// inside the `apply_config` that `Theme::change` ran above — i.e. from the
|
||||
// stock `#2f2f2f`, before any of this function's overrides exist — and a
|
||||
// snapshot never sees the fix. So a plain `Button` still hovered and pressed
|
||||
// in that grey, and `Button::selected` (the terminal search bar's `Aa` / `.*`
|
||||
// toggles, the last two in the app) filled from `tokens.secondary_active` the
|
||||
// same way: on Dracula, ~1.03:1 against the surface behind it. That is issue
|
||||
// #197 again, one snapshot removed from the field that fixed it.
|
||||
//
|
||||
// Only the *state* rungs move — `tokens.button` (the resting fill) is left
|
||||
// alone, so a plain button keeps the flat look it has today and only its
|
||||
// hover/pressed/selected join the ladder.
|
||||
let button_hover: Hsla = rgb(surfaces.window.hover).into();
|
||||
let button_active: Hsla = rgb(surfaces.window.selected).into();
|
||||
t.tokens.button_hover = button_hover.into();
|
||||
t.tokens.button_active = button_active.into();
|
||||
t.tokens.secondary_hover = button_hover.into();
|
||||
t.tokens.secondary_active = button_active.into();
|
||||
t.tokens.button_secondary_hover = button_hover.into();
|
||||
t.tokens.button_secondary_active = button_active.into();
|
||||
|
||||
// Focus rings: the one place the theme's *real* accent belongs. A ring is ink
|
||||
// on the background at 1–2px, which is exactly the job `legible_accent`
|
||||
// conditions the seed for; the stock `neutral-300` was both off-theme and
|
||||
|
||||
Reference in New Issue
Block a user