Improve editable control contrast across themes

This commit is contained in:
2026-07-17 10:50:28 -07:00
parent 418f01235d
commit 78673e718d
2 changed files with 38 additions and 6 deletions
+6 -6
View File
File diff suppressed because one or more lines are too long
+32
View File
@@ -38,6 +38,38 @@ test("implements the control and privacy contracts", async () => {
assert.match(css, /\.ideation-layout\{height:100%;min-height:0/);
});
function contrastRatio(foreground, background) {
const luminance = (hex) => {
const channels = hex.slice(1).match(/.{2}/g).map((value) => Number.parseInt(value, 16) / 255);
const linear = channels.map((value) => value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4);
return 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2];
};
const values = [luminance(foreground), luminance(background)].sort((a, b) => b - a);
return (values[0] + 0.05) / (values[1] + 0.05);
}
function themeToken(theme, name) {
return theme.match(new RegExp(`--${name}:(#[0-9a-f]{6})`, "i"))?.[1];
}
test("editable surfaces maintain high contrast in light and dark themes", async () => {
const css = await readFile(new URL("../src/globals.css", import.meta.url), "utf8");
const lightTheme = css.match(/^:root\{([^}]*)\}/)?.[1];
const darkTheme = css.match(/@media\(prefers-color-scheme:dark\)\{:root\{([^}]*)\}/)?.[1];
assert.ok(lightTheme && darkTheme, "Both system themes must define color tokens");
for (const [name, theme] of [["light", lightTheme], ["dark", darkTheme]]) {
const background = themeToken(theme, "field-bg");
const foreground = themeToken(theme, "field-ink");
const placeholder = themeToken(theme, "field-muted");
assert.ok(background && foreground && placeholder, `${name} field tokens must be complete`);
assert.ok(contrastRatio(foreground, background) >= 7, `${name} editable text must meet WCAG AAA contrast`);
assert.ok(contrastRatio(placeholder, background) >= 7, `${name} placeholder text must meet WCAG AAA contrast`);
}
assert.match(css, /\.edit-title,.idea-card textarea\{[^}]*background:var\(--field-bg\)[^}]*color:var\(--field-ink\)/);
assert.match(css, /\.format-toolbar button\{[^}]*background:var\(--field-bg\)[^}]*color:var\(--field-ink\)/);
});
test("never ships retained audio assets", async () => {
async function walk(url) {
const entries = await readdir(url, { withFileTypes: true });