Hmbown/CodeWhale · error

grayscale colors are all resolvable RGB

Error message

grayscale colors are all resolvable RGB

What it means

This is a Rust `Option::expect` panic reading 'grayscale colors are all resolvable RGB': `contrast_ratio(fg, bg)` returned None for a grayscale theme surface, aborting the high-contrast-theme test. The library throws it when contrast_ratio cannot parse one of the theme's colors into RGB — e.g. an unresolved named color, an unparseable hex string, or a non-RGB color representation. Because a grayscale theme is by definition pure RGB values, None here signals the theme builder produced a malformed color.

Solutions

  1. Log/inspect which color failed resolution; fix the theme definition so every surface_bg and foreground is a concrete, parseable RGB value.
  2. Make contrast_ratio return Result<Rgb-parse error> so failures name the offending color instead of panicking on Option.
  3. Validate theme colors at construction time (parse once in the theme builder) so invalid values are rejected where they are created, not in contrast tests.

Example fix

// before
let ratio = contrast_ratio(fg, bg).expect("grayscale colors are all resolvable RGB");
// after
let ratio = contrast_ratio(fg, bg)
    .unwrap_or_else(|| panic!("unresolvable RGB in grayscale theme: fg={fg:?} bg={bg:?}"));
Defensive patterns

Strategy: validation

Validate before calling

fn all_colors_resolvable(theme: &Theme) -> Result<(), String> {
    [theme.surface_bg, theme.panel_bg, theme.composer_bg, theme.elevated_bg]
        .iter()
        .chain(std::iter::once(&theme.text_default))
        .map(|c| c.to_rgb().ok_or_else(|| format!("unresolvable RGB: {c:?}")))
        .collect::<Result<Vec<_>, _>>()
        .map(|_| ())
}

Type guard

fn is_resolvable_rgb(color: &Color) -> bool {
    color.to_rgb().is_some()
}

Try / catch

let ratio = contrast_ratio(fg, bg).unwrap_or_else(|| {
    panic!("contrast_ratio returned None: fg={fg:?} bg={bg:?} is not a resolvable RGB")
});

Prevention

When it happens

Trigger: Calling `contrast_ratio(fg, bg)` where either argument is a Color that fails RGB resolution: a hex string with the wrong length, a color still stored as a symbolic name, or a theme field populated with None/placeholder before resolution. Any grayscale-theme surface/foreground pair that hits that path panics.

Common situations: Theme definitions referencing named colors that require a terminal-query step to resolve; a refactor changing surface_bg fields from parsed Rgb to raw strings; copy-paste typos in grayscale hex values like '#1111' instead of '#111111'.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/0552e8a523ae7467. Report an issue: GitHub.

Appendix: source

Thrown at crates/palette/src/tests.rs:1142

    }
    // A painted community theme never gets the exemption.
    assert!(!theme_uses_terminal_owned_surfaces(&TOKYO_NIGHT_UI_THEME));
}

#[test]
fn high_contrast_grayscale_theme_clears_body_floor_on_every_surface() {
    // The picker tagline claims "Color-minimal high contrast" — hold the
    // grayscale theme to the full body-text floor on every surface, not the
    // 3:1 secondary-chrome floor.
    let theme = GRAYSCALE_UI_THEME;
    for fg in [theme.text_body, theme.text_soft, theme.text_muted] {
        for bg in [
            theme.surface_bg,
            theme.panel_bg,
            theme.composer_bg,
            theme.elevated_bg,
        ] {
            let ratio = contrast_ratio(fg, bg).expect("grayscale colors are all resolvable RGB");
            assert!(
                ratio >= AA_BODY_CONTRAST,
                "grayscale {fg:?} on {bg:?} is {ratio}:1, below the {AA_BODY_CONTRAST}:1 floor",
            );
        }
    }
}

#[test]
fn violation_report_names_pair_and_ratio() {
    let mut bad = TOKYO_NIGHT_UI_THEME;
    bad.text_muted = bad.surface_bg;
    let violations = theme_contrast_violations(&bad);
    // The muted-on-surface color fails against all four text surfaces.
    let muted_pairs: Vec<_> = violations
        .iter()
        .filter(|violation| violation.pair.starts_with("text_muted on "))
        .collect();

View on GitHub (pinned to 433685b202)