fix(frontend): raise dark contrast, expand hit areas, and surface input limits
Dark muted surfaces missed WCAG AA; action and tertiary tokens plus a 32-pair contract close that. Hit targets, remaining-count, and Enter-to-send follow the interaction audit without changing visual sizes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -64,15 +64,43 @@ test("dark ink reads lighter than dark canvas, and the action stays legible", ()
|
||||
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
|
||||
return (hi + 0.05) / (lo + 0.05);
|
||||
};
|
||||
const canvas = pinned.get("color-canvas")!;
|
||||
for (const token of ["color-ink", "color-action", "color-danger", "color-success", "color-warning"]) {
|
||||
const value = pinned.get(token)!;
|
||||
assert.match(value, /^#[0-9a-f]{6}$/i, `--${token} must be a hex so contrast is checkable`);
|
||||
assert.ok(
|
||||
contrast(value, canvas) >= 4.5,
|
||||
`--${token} (${value}) on --color-canvas (${canvas}) is ${contrast(value, canvas).toFixed(2)}:1, below AA`,
|
||||
);
|
||||
// Checking only the reading surface missed ink-tertiary and action on the
|
||||
// raised card (--color-canvas-muted). New text or surface tokens must join
|
||||
// this matrix; do not drop a pair or lower 4.5 to make it pass.
|
||||
const inks = [
|
||||
"color-ink",
|
||||
"color-ink-strong",
|
||||
"color-ink-secondary",
|
||||
"color-ink-tertiary",
|
||||
"color-action",
|
||||
"color-danger",
|
||||
"color-success",
|
||||
"color-warning",
|
||||
];
|
||||
const surfaces = [
|
||||
"color-canvas",
|
||||
"color-canvas-soft",
|
||||
"color-canvas-muted",
|
||||
"color-sidebar-solid",
|
||||
];
|
||||
const failures: string[] = [];
|
||||
for (const ink of inks) {
|
||||
const inkValue = pinned.get(ink)!;
|
||||
assert.match(inkValue, /^#[0-9a-f]{6}$/i, `--${ink} must be a hex so contrast is checkable`);
|
||||
for (const surface of surfaces) {
|
||||
const surfaceValue = pinned.get(surface)!;
|
||||
assert.match(
|
||||
surfaceValue,
|
||||
/^#[0-9a-f]{6}$/i,
|
||||
`--${surface} must be a hex so contrast is checkable`,
|
||||
);
|
||||
const ratio = contrast(inkValue, surfaceValue);
|
||||
if (ratio < 4.5) {
|
||||
failures.push(`--${ink} (${inkValue}) on --${surface} (${surfaceValue}) is ${ratio.toFixed(2)}:1`);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.equal(failures.length, 0, failures.join("; "));
|
||||
// Elevation reads through lightness on dark: floor is darkest, raised steps up.
|
||||
const steps = ["color-canvas-soft", "color-canvas", "color-canvas-muted", "color-canvas-strong"]
|
||||
.map((token) => luminance(pinned.get(token)!));
|
||||
@@ -80,3 +108,22 @@ test("dark ink reads lighter than dark canvas, and the action stays legible", ()
|
||||
assert.ok(steps[i] > steps[i - 1], "dark surfaces must get lighter as they rise");
|
||||
}
|
||||
});
|
||||
|
||||
test("the previous dark action and tertiary hexes are gone from product sources", () => {
|
||||
const sources = [
|
||||
globalStyles,
|
||||
readFileSync(new URL("../src/app/error.tsx", import.meta.url), "utf8"),
|
||||
readFileSync(new URL("../src/app/not-found.tsx", import.meta.url), "utf8"),
|
||||
readFileSync(new URL("../src/app/forbidden.tsx", import.meta.url), "utf8"),
|
||||
readFileSync(new URL("../src/app/global-error.tsx", import.meta.url), "utf8"),
|
||||
readFileSync(new URL("../DESIGN.md", import.meta.url), "utf8"),
|
||||
];
|
||||
for (const source of sources) {
|
||||
assert.doesNotMatch(source, /#d4785a/i);
|
||||
}
|
||||
assert.equal(pinned.get("color-action"), "#d78064");
|
||||
assert.equal(pinned.get("color-focus"), "#d78064");
|
||||
assert.equal(pinned.get("color-ink-tertiary"), "#9c988e");
|
||||
assert.equal(preferred.get("color-action"), "#d78064");
|
||||
assert.equal(preferred.get("color-ink-tertiary"), "#9c988e");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user