fix(frontend): raise dark contrast, expand hit areas, and surface input limits
Independent Staging Quality Gate / validate (push) Successful in 9m19s
Independent Staging Quality Gate / publish (push) Successful in 9m54s

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:
Jesse_Chen
2026-08-30 11:19:50 +08:00
co-authored by Cursor
parent b43808b016
commit e8fa1ce4ea
20 changed files with 555 additions and 40 deletions
+55 -8
View File
@@ -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");
});