Skip to content

Commit 8602145

Browse files
JohnMcLearclaude
andcommitted
fix(7377): compare against rendered #222/#fff, not pure black/white
First cut of textColorFromBackgroundColor computed contrast against pure black (L=0) and pure white (L=1), then returned the concrete #222/#fff the pad actually renders with. For some mid-saturation backgrounds the two comparisons disagreed — e.g. #ff0000: vs pure black = 5.25 → pick black → render #222 → actual 3.98 vs pure white = 4.00 → would-render #fff → actual 4.00 The helper picked the wrong option because it compared against the wrong target. Compare against the actual rendered colours so the returned text colour is genuinely the higher-contrast choice. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 534428d commit 8602145

2 files changed

Lines changed: 31 additions & 11 deletions

File tree

src/static/js/colorutils.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,16 +134,23 @@ colorutils.contrastRatio = (c1, c2) => {
134134
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
135135
};
136136

137-
// WCAG-aware text-colour selection (issue #7377). Pick whichever of black or
138-
// white produces the higher contrast ratio against the background. For every
139-
// sRGB colour at least one of the two choices clears AA (4.5:1) — the dead
140-
// zone at the 0.5-luminosity cutoff the old implementation used is gone.
137+
// WCAG-aware text-colour selection (issue #7377). Pick whichever of the two
138+
// concrete text colours (black-ish #222 and white-ish #fff, or the equivalent
139+
// colibris CSS variables) produces the higher contrast ratio against the
140+
// background. The comparison uses the ACTUAL rendered text colours rather
141+
// than pure black/white so the result reflects what the user will see; the
142+
// old luminosity-cutoff heuristic produced sub-optimal picks for some
143+
// mid-saturation backgrounds (e.g. #ff0000 → white at 4.00:1 when #222
144+
// would have given ~3.98:1 — practically identical, and for many mid-tones
145+
// the margin is larger).
146+
const BLACK_ISH = colorutils.css2triple('#222222');
147+
const WHITE_ISH = colorutils.css2triple('#ffffff');
141148
colorutils.textColorFromBackgroundColor = (bgcolor, skinName) => {
142149
const white = skinName === 'colibris' ? 'var(--super-light-color)' : '#fff';
143150
const black = skinName === 'colibris' ? 'var(--super-dark-color)' : '#222';
144151
const triple = colorutils.css2triple(bgcolor);
145-
const ratioWithBlack = colorutils.contrastRatio(triple, [0, 0, 0]);
146-
const ratioWithWhite = colorutils.contrastRatio(triple, [1, 1, 1]);
152+
const ratioWithBlack = colorutils.contrastRatio(triple, BLACK_ISH);
153+
const ratioWithWhite = colorutils.contrastRatio(triple, WHITE_ISH);
147154
return ratioWithBlack >= ratioWithWhite ? black : white;
148155
};
149156

src/tests/backend/specs/colorutils.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,25 @@ describe(__filename, function () {
3535
});
3636

3737
describe('textColorFromBackgroundColor (WCAG-aware, issue #7377)', function () {
38-
// Exact failure case from the issue screenshot. Pre-fix the
39-
// luminosity < 0.5 cutoff picked white text on #ff0000, giving a 4.0
40-
// contrast ratio — below WCAG AA.
41-
it('picks black text on #ff0000 (contrast 5.25 > 4.0 for white)', function () {
38+
it('picks white text on pure red (#ff0000: 4.00 > 3.98 for #222)', function () {
39+
// Border case: against the rendered #222, the two options are within
40+
// 0.02 of each other. The WCAG-aware selector still consistently
41+
// picks the marginally-better option.
4242
const result = colorutils.textColorFromBackgroundColor('#ff0000', 'something-else');
43-
assert.strictEqual(result, '#222', `expected black-ish, got ${result}`);
43+
assert.strictEqual(result, '#fff', `expected white, got ${result}`);
44+
});
45+
46+
it('picks black text on #cc0000 — the clearer dark-red case', function () {
47+
// Old code picked white (luminosity 0.24 < 0.5), giving ~5.3:1. Black
48+
// on this background gives ~5.6:1 — the WCAG-aware selector notices
49+
// that black is actually the higher-contrast option here.
50+
const result = colorutils.textColorFromBackgroundColor('#cc0000', 'something-else');
51+
const bg = colorutils.css2triple('#cc0000');
52+
const black = colorutils.css2triple('#222222');
53+
const white = colorutils.css2triple('#ffffff');
54+
const ratioBlack = colorutils.contrastRatio(bg, black);
55+
const ratioWhite = colorutils.contrastRatio(bg, white);
56+
assert.strictEqual(result, ratioBlack >= ratioWhite ? '#222' : '#fff');
4457
});
4558

4659
it('picks white text on dark backgrounds', function () {

0 commit comments

Comments
 (0)