fix(design): the button and chip hover colours were never generated — an act: variant instead of a runtime prefix (E28)
buttonStyles/chipStyles glued the hover guard on at runtime (`${ACT}hover:text-fg`). Tailwind emits CSS
only for a class it finds whole in the source, so all 22 guarded hovers shipped with no rule: no text or
edge colour on hover for ghost/outline/danger buttons, no white layer on the player's control row, no
underline on a link button, no accent hover on token/suggest/ghost chips. The stylesheet had 0 rules
for `not-aria-disabled`.
- `@custom-variant act` (index.css): the same guard as the state layers; `:disabled` never matches an
<a>, so one variant serves buttons and links and the per-element variant maps go.
- ui/tailwindScan.contract.test.ts: every class the primitives can emit stands whole in their source.
- a lint rule refuses a variant right after an interpolation; the `enabled:hover:` rule now points at
`act:hover:`.
This commit is contained in:
@@ -372,6 +372,11 @@ const PALETTE =
|
||||
const PALETTE_MESSAGE =
|
||||
"a palette colour: use a semantic token (text-rating, bg-live, bg-media-chrome, text-danger, …; DESIGN.md Tokens), not text-amber-500.";
|
||||
|
||||
// A class name split by an interpolation: the text right after `${…}` starts with a variant (`hover:`).
|
||||
const SPLIT_CLASS = "/^[a-z][\\w-]*:[\\w\\[!-]/";
|
||||
const SPLIT_CLASS_MESSAGE =
|
||||
"a class split by an interpolation (`${x}hover:…`): Tailwind never sees it whole and emits no CSS; write the class literally (a shared guard is the `act:` variant).";
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ["dist", "node_modules", "playwright-report", "test-results", "e2e/.auth"] },
|
||||
{
|
||||
@@ -451,12 +456,19 @@ export default tseslint.config(
|
||||
{
|
||||
selector: "Literal[value=/(^|\\s)enabled:hover:/]",
|
||||
message:
|
||||
"`enabled:hover:` misses a focused FocusSafeButton (aria-disabled): use `enabled:not-aria-disabled:hover:`.",
|
||||
"`enabled:hover:` misses a focused FocusSafeButton (aria-disabled): use `act:hover:` (index.css).",
|
||||
},
|
||||
{
|
||||
selector: "TemplateElement[value.raw=/(^|\\s)enabled:hover:/]",
|
||||
message:
|
||||
"`enabled:hover:` misses a focused FocusSafeButton (aria-disabled): use `enabled:not-aria-disabled:hover:`.",
|
||||
"`enabled:hover:` misses a focused FocusSafeButton (aria-disabled): use `act:hover:` (index.css).",
|
||||
},
|
||||
// Tailwind emits CSS only for a class it finds WHOLE in the source. A variant glued onto an
|
||||
// interpolated prefix (`${ACT}hover:text-fg`) is never found, so it ships with no rule: every
|
||||
// hover colour of the button and chip primitives was dead that way (E28 audit).
|
||||
{
|
||||
selector: `TemplateLiteral > TemplateElement:not(:first-child)[value.raw=${SPLIT_CLASS}]`,
|
||||
message: SPLIT_CLASS_MESSAGE,
|
||||
},
|
||||
// The canonical "FE control" row (CLAUDE.md): a short fixed set is a SegmentedToggle, a
|
||||
// longer one or a user's own list a FilterSelect. Six native selects had drifted in; the
|
||||
|
||||
Reference in New Issue
Block a user