Files
small-packages/luci-theme-footstrap/.stylelintrc.json
T
2026-09-20 01:22:33 +08:00

140 lines
7.3 KiB
JSON

{
"//": [
"Correctness rules only — deliberately NOT `extends: stylelint-config-standard`.",
"That preset is largely a formatter: it would demand its own indentation, quoting",
"and case conventions across the whole styles/ tree and bury the two or three",
"findings that actually matter under a thousand cosmetic ones. The house style",
"here is tabs + one-liners for short rules, and it is deliberate.",
"",
"What is switched on below is the set that can catch a real bug: a typo'd",
"property or pseudo-class, a broken hex, the same property twice in one block,",
"an empty block, a duplicate selector. Plus the project's own invariants from",
"docs/conventions.md — no stray !important, and no colour literal outside the palettes.",
"",
"audit.py stays: it checks things stylelint cannot see (var() defined-vs-used",
"across files, declarations shadowed within a cascade layer).",
"",
"TWO formatting rules are the deliberate exception, and they are @stylistic/* because",
"stylelint 16 removed every stylistic rule from core. The objection above is to the",
"PRESET, not to the idea: a preset imposes a hundred opinions nobody chose, while these",
"two each close a measured drift the house style already had a majority on — quotes were",
"309 double vs 47 single in attribute selectors, leading zeros 97 bare vs 5. Picking the",
"majority cost 77 autofixed characters and changed the built cascade.css by 0 bytes",
"(116548 both ways; the awk squeeze is string-aware and eats either quote identically).",
"string-quotes leaves a string that CONTAINS the preferred quote alone rather than escape",
"it — which is why the four <svg xmlns=\"…\"> data URIs in 03-palettes.css stay single.",
"That is the rule's own definition, not an exemption we wrote.",
"",
"What still CANNOT be gated, so it is on review alone: attribute-quote PRESENCE",
"(input[type=file]) and `0px` — stylelint 16 removed selector-attribute-quotes and",
"length-zero-no-unit and @stylistic never ported either.",
"",
"The second block of rules below is the same argument one layer deeper: all of them",
"are correctness, none is formatting, and every one found ZERO violations in the",
"current tree — so they cost nothing today and catch the next mistake for free.",
"declaration-property-value-no-unknown is the one that matters most: it is the only",
"check that reads a declaration's VALUE against the property's grammar, i.e. the only",
"thing that can catch a value which is invalid at computed-value time and therefore",
"vanishes in SILENCE — the exact failure mode this project tore the --*-rgb component",
"bridges out for. custom-property-no-missing-var-function catches `color: --fs-accent`",
"(a bare custom-property name where var() was meant), which parses and does nothing."
],
"plugins": ["@stylistic/stylelint-plugin"],
"rules": {
"@stylistic/string-quotes": "double",
"@stylistic/number-leading-zero": "never",
"color-no-invalid-hex": true,
"function-calc-no-unspaced-operator": true,
"string-no-newline": true,
"unit-no-unknown": true,
"property-no-unknown": true,
"declaration-block-no-duplicate-properties": [
true,
{ "ignore": ["consecutive-duplicates-with-different-values"] }
],
"declaration-block-no-shorthand-property-overrides": true,
"block-no-empty": true,
"selector-pseudo-class-no-unknown": true,
"selector-pseudo-element-no-unknown": [
true,
{
"ignorePseudoElements": [
"picker",
"picker-icon",
"checkmark",
"-webkit-slider-thumb",
"-moz-range-thumb",
"-webkit-search-decoration",
"file-selector-button"
]
}
],
"selector-type-no-unknown": true,
"media-feature-name-no-unknown": true,
"at-rule-no-unknown": true,
"comment-no-empty": true,
"no-duplicate-at-import-rules": true,
"no-duplicate-selectors": true,
"no-empty-source": true,
"no-invalid-double-slash-comments": true,
"no-invalid-position-at-import-rule": true,
"no-irregular-whitespace": true,
"declaration-property-value-no-unknown": true,
"custom-property-no-missing-var-function": true,
"declaration-block-no-duplicate-custom-properties": true,
"function-no-unknown": true,
"media-query-no-invalid": true,
"selector-anb-no-unmatchable": true,
"keyframe-declaration-no-important": true,
"no-invalid-position-declaration": true,
"font-family-no-missing-generic-family-keyword": true,
"syntax-string-no-invalid": true,
"no-descending-specificity": null,
"declaration-no-important": true
},
"overrides": [
{
"//": [
"The only files allowed an !important, and each for a documented reason.",
"theme/90-responsive + pages/20-overview outrank an inline style= attribute",
"written by 29_ports.js / ui.js — no cascade layer can do that, only an author",
"!important can. theme/45-misc is the same case: the realtime graphs write",
"style='width:100%' on the box they draw into, and the theme has to widen it or",
"the drawing (sized from #view) is clipped by the card's gutter. theme/65-dropdown",
"carries the widget's ul-margin flag (ui.js writes margin inline on an open list),",
"moved in with the machinery absorbed from base/80-dropdown.css. styles/base",
"keeps the .left/.right/.center forcing utilities and the two inline-style fighters",
"in 95-luci. theme/95-a11y-media is the reduced-motion block: !important INVERTS",
"the layer order, so it is the only way one rule can stop animations declared in",
"base as well as in theme.",
"theme/30-tables carries THREE flags, one per adversary a layer cannot reach.",
"`word-break: normal` fights two of them: luci-mod-status's processes.js writes",
"style='word-break: break-word' on its Command span, the deprecated alias for",
"`overflow-wrap: anywhere`, erasing the column's min-content from an inline declaration",
"— measured on the router, the column sat at 126px against a 353px token and no layered",
"rule could move it; and luci-mod-dashboard ships `word-break: break-all` UNLAYERED,",
"which splits a token that would have fitted. The other two hold a carded cell's width",
"against that same unlayered sheet's `flex: 2 2 10%`, two because a meter cell needs the",
"whole line where a label/value pair needs half.",
"This list is the contract. Adding a file to it needs a reason in the file.",
"The same list lives in tools/audit.py (BANG_OK); tools/bang-ok.mjs (npm run bang-ok)",
"fails if the two drift, so a file added here but not there (or vice versa) is caught."
],
"files": [
"luci-theme-footstrap/styles/base/*.css",
"luci-theme-footstrap/styles/theme/30-tables.css",
"luci-theme-footstrap/styles/theme/45-misc.css",
"luci-theme-footstrap/styles/theme/65-dropdown.css",
"luci-theme-footstrap/styles/theme/90-responsive.css",
"luci-theme-footstrap/styles/theme/95-a11y-media.css",
"luci-theme-footstrap/styles/pages/20-overview.css"
],
"rules": { "declaration-no-important": null }
}
]
}