Adds a local ESLint plugin (web/eslint-local/) with three custom rules
implementing the preventive guardrails from docs/ui/UI_AUDIT.md §5:
- bytelyst-trading/truncate-needs-title
flags JSX elements using Tailwind 'truncate' / 'line-clamp-*' /
'text-ellipsis' without a paired title= or aria-label= (Pattern E)
- bytelyst-trading/grid-needs-minmax
flags gridTemplateColumns string values with bare Nfr tracks not
wrapped in minmax(0, ...). Catches both literal and template-string
forms; verifies *every* fr is wrapped, not just one (Pattern F)
- bytelyst-trading/no-button-with-stacked-children
flags <Button> from @bytelyst/ui wrapping 2+ block children. The
Button primitive applies whitespace-nowrap + fixed h-{size} which
collapses stacked content; recommends native <button class="card-button">
(Pattern A)
All wired into eslint.config.js as 'warn' (not error) so existing code
isn't broken; new violations show up immediately.
Also fixes the two bare-Nfr grids the new rule caught:
- components/strategy/CodeStrategyEditor.tsx :270 — repeat(5, 1fr)
- views/ScreenerView.tsx :142 — '100px 1fr 90px ...'
eslint src/ now reports zero bytelyst-trading/* warnings.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
51 lines
1.7 KiB
JavaScript
51 lines
1.7 KiB
JavaScript
// @ts-check
|
|
/**
|
|
* Warn when a CSS `gridTemplateColumns` value uses a bare `Nfr` track
|
|
* without `minmax(0, ...)`. Default `min-width: auto` makes grid items
|
|
* refuse to shrink below their content, causing overflow.
|
|
*
|
|
* Pattern F in docs/ui/UI_AUDIT.md.
|
|
*/
|
|
const HAS_FR_RE = /\b\d*\.?\d*fr\b/;
|
|
const HAS_MINMAX_RE = /minmax\s*\(/;
|
|
|
|
function checkValue(context, node, raw) {
|
|
if (typeof raw !== 'string' || !HAS_FR_RE.test(raw)) return;
|
|
if (!HAS_MINMAX_RE.test(raw)) {
|
|
context.report({ node, messageId: 'bare', data: { value: raw } });
|
|
return;
|
|
}
|
|
// Has minmax somewhere — check whether *every* fr is wrapped.
|
|
const stripped = raw.replace(/minmax\s*\([^)]*\)/g, 'MM');
|
|
if (HAS_FR_RE.test(stripped)) {
|
|
context.report({ node, messageId: 'bare', data: { value: raw } });
|
|
}
|
|
}
|
|
|
|
export default {
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: { description: 'grid 1fr tracks should be wrapped in minmax(0, ...) to allow shrinking' },
|
|
schema: [],
|
|
messages: {
|
|
bare: 'gridTemplateColumns "{{ value }}" has a bare Nfr track. Wrap each in minmax(0, Nfr) so cell content can shrink below min-content.',
|
|
},
|
|
},
|
|
create(context) {
|
|
function visitProperty(node) {
|
|
if (!node.key) return;
|
|
const keyName = node.key.name || (node.key.type === 'Literal' && node.key.value);
|
|
if (keyName !== 'gridTemplateColumns') return;
|
|
const v = node.value;
|
|
if (!v) return;
|
|
if (v.type === 'Literal' && typeof v.value === 'string') {
|
|
checkValue(context, v, v.value);
|
|
} else if (v.type === 'TemplateLiteral') {
|
|
const raw = v.quasis.map(q => q.value.cooked).join('${...}');
|
|
checkValue(context, v, raw);
|
|
}
|
|
}
|
|
return { Property: visitProperty };
|
|
},
|
|
};
|