fix(a11y): names that carry what they show; the unused-key check stops covering whole files

- The palette launcher's name replaced its visible "Jump to…" (and the key cap's its "Ctrl K"):
  a voice-control "click Jump to" matched nothing (WCAG 2.5.3). Each name contains its text now.
- SegmentedToggle's `title` was only a tooltip on a non-focusable box — the live search's "how
  many per batch" hint reached no keyboard or screen-reader user. It is the group's accessible
  description, standalone and captioned alike.
- The unused-key contract let a template like `card.${status}` cover its whole namespace, which hid
  five dead keys (deleted). Dynamic keys are matched by shape (`editor.*.hint`); a bare namespace
  shape covers nothing, and the audit/quota namespaces are now held both ways by backendIds.
- CLAUDE.md's "FE control" row matches the lint rule it cites; each no-restricted-syntax entry has
  its reason directly above it.
This commit is contained in:
2026-09-19 19:19:43 +02:00
parent fa4f823fc8
commit cf0c55ba80
13 changed files with 128 additions and 39 deletions
+9 -7
View File
@@ -212,15 +212,10 @@ export default tseslint.config(
"error",
{ allowShortCircuit: true, allowTernary: true },
],
// R10 S2: keep the rem system from rotting. A px-frozen `text-[Npx]` font size does NOT scale
// with the Settings --font-scale slider while its rem neighbours do; use `text-2xs`/`text-3xs`
// or a `text-[Nrem]` arbitrary instead. Matches both plain strings and template-literal chunks;
// rem arbitraries (text-[0.75rem]) don't match (no "px").
// Source-shape rules, each with its reason right above it (contract-tested in
// lib/*.contract.test.ts).
"no-restricted-syntax": [
"error",
// 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
// 2026-09 debt burn replaced them, and this keeps the next one out.
// `enabled:` sees only the native attribute; a FocusSafeButton that holds the focus is
// disabled by aria-disabled, so a bare `enabled:hover:` lit it up on hover as if it worked.
{
@@ -233,11 +228,18 @@ export default tseslint.config(
message:
"`enabled:hover:` misses a focused FocusSafeButton (aria-disabled): use `enabled:not-aria-disabled:hover:`.",
},
// 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
// 2026-09 debt burn replaced them, and this keeps the next one out.
{
selector: "JSXOpeningElement[name.name='select']",
message:
"No native <select>: use ui/SegmentedToggle for a short fixed set, ui/FilterSelect for a longer one or a user's own list.",
},
// R10 S2: keep the rem system from rotting. A px-frozen `text-[Npx]` font size does NOT
// scale with the Settings --font-scale slider while its rem neighbours do; use
// `text-2xs`/`text-3xs` or a `text-[Nrem]` arbitrary instead. Matches both plain strings and
// template-literal chunks; rem arbitraries (text-[0.75rem]) don't match (no "px").
{
selector: "Literal[value=/text-\\[[\\d.]+px\\]/]",
message: