fix(design): the command palette's active row takes the menus' roving highlight, not an invisible bg-card; the Plex typeahead's hand copy of it goes (E28)

The palette row was ~1.07:1 on its glass (B3 names bg-card hovers a violation). ROVING_HIGHLIGHT is
exported from AnchoredPopover for the listboxes that are not PopoverItems; a contract holds that no file
paints the fill itself and every hand-built option row takes the recipe.
This commit is contained in:
2026-09-25 23:51:39 +02:00
parent ce171fa954
commit 6088e476a7
5 changed files with 65 additions and 9 deletions
@@ -0,0 +1,40 @@
import { readdirSync, readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
// E28 audit F35. The keyboard row of a listbox has one look, ui/AnchoredPopover's `ROVING_HIGHLIGHT`
// (a menu row, a combobox option, the command palette, the Plex typeahead). A listbox that paints its
// own drifted: the palette's `bg-card` row was ~1.07:1 on its glass, and the Plex typeahead carried a
// hand copy of the recipe that would not follow a change to it.
function sources(): Array<[string, string]> {
const walk = (u: URL, prefix: string): Array<[string, string]> =>
readdirSync(u, { withFileTypes: true }).flatMap((e) => {
if (e.isDirectory()) return walk(new URL(`${e.name}/`, u), `${prefix}${e.name}/`);
if (!/\.tsx?$/.test(e.name) || e.name.includes(".test.")) return [];
return [
[`${prefix}${e.name}`, readFileSync(new URL(e.name, u), "utf-8")] as [string, string],
];
});
return walk(new URL("../", import.meta.url), "");
}
describe("the roving highlight", () => {
it("is painted only by the recipe in ui/AnchoredPopover", () => {
const copies = sources()
.filter(
([name]) => name !== "components/ui/AnchoredPopover.tsx" && !name.startsWith("design-lab/"),
)
.filter(([, src]) => /(^|[\s"`'])bg-accent\/35(?![\w/-])/.test(src))
.map(([name]) => name);
expect(copies).toEqual([]);
});
it("is what every hand-built option row takes", () => {
const own = sources()
.filter(([, src]) => src.includes('role="option"'))
// A PopoverItem row takes the recipe itself (the Combobox's options).
.filter(([, src]) => !src.includes("ROVING_HIGHLIGHT") && !src.includes("<PopoverItem"))
.map(([name]) => name);
expect(own).toEqual([]);
});
});