diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 4eca0b3c..f134ed21 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -162,8 +162,9 @@ const localRules = { }; // A px-frozen BOX utility (the no-restricted-syntax block below): an esquery regex, as a string. +// `[\d.]+`, not `\d+`: a fractional `text-[9.5px]` slipped past the integer-only pattern. const BOX_PX = - "/(^|[\\s:])(min-|max-)?(w|h|size|left|right|top|bottom|inset|gap|p[xytrbl]?|m[xytrbl]?)-\\[\\d+px\\]/"; + "/(^|[\\s:])(min-|max-)?(w|h|size|left|right|top|bottom|inset|gap|p[xytrbl]?|m[xytrbl]?)-\\[[\\d.]+px\\]/"; const BOX_PX_MESSAGE = "px-frozen box size doesn't scale with --font-scale: use the spacing scale (w-37.5, max-w-60), not w-[Npx]."; @@ -217,13 +218,21 @@ export default tseslint.config( // rem arbitraries (text-[0.75rem]) don't match (no "px"). "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. { - selector: "Literal[value=/text-\\[\\d+px\\]/]", + selector: "JSXOpeningElement[name.name='select']", + message: + "No native setProfileId(Number(e.target.value))} - className={inputCls} - > - {profiles.map((p) => ( - - ))} - + + {/* The user's own list, of any length: a menu, not a segmented bar (owner's pick, 2026-09-19). */} + v && setProfileId(Number(v))} + options={profiles.map((p) => ({ value: String(p.id), label: p.name }))} + />