stylelint-plugin-rhythmguard 3.10.1 → 3.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,16 @@ The format follows Keep a Changelog principles and semantic versioning.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [3.10.2] - 2026-10-15
10
+
11
+ ### Messages
12
+
13
+ - A value below the smallest step or above the largest named that step twice: `Use scale values (nearest: 14px or 14px).` It is now named once: `(nearest: 14px)`, and for a Tailwind class `Use "p-8" (32px).` Applies to `use-scale`, `no-offscale-transform`, `use-motion-scale` and both ESLint rules. Messages for values between two steps are unchanged.
14
+
15
+ ### Changed
16
+
17
+ - New README banner without a version number, so it no longer needs replacing each release. GitHub shows the transparent SVG; npmjs.com gets a non-transparent PNG rendered from it on the same background as before.
18
+
9
19
  ## [3.10.1] - 2026-10-15
10
20
 
11
21
  ### Fixed
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/petrilahdelma/stylelint-plugin-rhythmguard/main/assets/rhythmguard-banner.png?v=13" width="100%" alt="Rhythmguard: stable local evidence for design system drift" />
2
+ <img src="https://raw.githubusercontent.com/petrilahdelma/stylelint-plugin-rhythmguard/main/assets/rhythmguard-banner.png?v=14" width="100%" alt="Rhythmguard: stable local evidence for design system drift" />
3
3
  </p>
4
4
 
5
5
  # stylelint-plugin-rhythmguard
@@ -7,7 +7,7 @@
7
7
  Nobody chose 13px. Rhythmguard catches off-scale spacing in CSS and Tailwind class strings, tells you the nearest steps on your scale, and snaps to them or to your tokens when you ask.
8
8
 
9
9
  [![CI](https://img.shields.io/github/actions/workflow/status/petrilahdelma/stylelint-plugin-rhythmguard/ci.yml?branch=main&label=ci)](https://github.com/petrilahdelma/stylelint-plugin-rhythmguard/actions/workflows/ci.yml)
10
- [![npm version](https://img.shields.io/npm/v/stylelint-plugin-rhythmguard?label=npm&color=1f6feb&r=3101)](https://www.npmjs.com/package/stylelint-plugin-rhythmguard)
10
+ [![npm version](https://img.shields.io/npm/v/stylelint-plugin-rhythmguard?label=npm&color=1f6feb&r=3102)](https://www.npmjs.com/package/stylelint-plugin-rhythmguard)
11
11
  [![npm downloads](https://img.shields.io/npm/dm/stylelint-plugin-rhythmguard.svg)](https://www.npmjs.com/package/stylelint-plugin-rhythmguard)
12
12
  [![License: MIT](https://img.shields.io/badge/license-MIT-white.svg)](./LICENSE)
13
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stylelint-plugin-rhythmguard",
3
- "version": "3.10.1",
3
+ "version": "3.10.2",
4
4
  "description": "Nobody chose 13px. Catches off-scale spacing in CSS and Tailwind class strings and snaps it to your scale or tokens. Stylelint rules, an ESLint companion, and an audit CLI.",
5
5
  "bin": {
6
6
  "rhythmguard": "src/cli/index.js"
@@ -182,6 +182,14 @@ function nearestScaleValues(target, scale) {
182
182
  };
183
183
  }
184
184
 
185
+ /**
186
+ * "nearest: 12px or 16px" for a value between two steps, "nearest: 14px" for a
187
+ * value below the smallest step or above the largest, where both are the same.
188
+ */
189
+ function nearestText(lower, upper) {
190
+ return lower === upper ? `nearest: ${lower}` : `nearest: ${lower} or ${upper}`;
191
+ }
192
+
185
193
  /**
186
194
  * The replacement text for an off-scale length, or null when the value's unit
187
195
  * is not one the rule may rewrite. Keeps the sign, and with the `exact` unit
@@ -203,6 +211,7 @@ function fixedLengthValue(parsedLength, nearestPx, { baseFontSize, unitStrategy,
203
211
  }
204
212
 
205
213
  module.exports = {
214
+ nearestText,
206
215
  fixedLengthValue,
207
216
  formatLength,
208
217
  formatNumber,
@@ -4,6 +4,7 @@ const {
4
4
  formatLength,
5
5
  formatNumber,
6
6
  nearestScaleValues,
7
+ nearestText,
7
8
  normalizeScale,
8
9
  numbersEqual,
9
10
  parseLengthToken,
@@ -264,9 +265,11 @@ function offScaleClassMessage(token, analysis) {
264
265
  const upperPx = analysis.nearest ? formatLength(analysis.nearest.upper, 'px') : 'n/a';
265
266
  if (analysis.nearestUtilities) {
266
267
  const { lower, upper } = analysis.nearestUtilities;
267
- return `Unexpected Tailwind arbitrary spacing value "${token}". Use "${lower}" (${lowerPx}) or "${upper}" (${upperPx}).`;
268
+ return lower === upper
269
+ ? `Unexpected Tailwind arbitrary spacing value "${token}". Use "${lower}" (${lowerPx}).`
270
+ : `Unexpected Tailwind arbitrary spacing value "${token}". Use "${lower}" (${lowerPx}) or "${upper}" (${upperPx}).`;
268
271
  }
269
- return `Unexpected Tailwind arbitrary spacing value "${token}". Use scale values (nearest: ${lowerPx} or ${upperPx}).`;
272
+ return `Unexpected Tailwind arbitrary spacing value "${token}". Use scale values (${nearestText(lowerPx, upperPx)}).`;
270
273
  }
271
274
 
272
275
  module.exports = {
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { MAX_NOTE_LENGTH, normalizeNote, withNote } = require('../../core/options');
4
+ const { nearestText } = require('../../core/length');
4
5
  const { formatTime } = require('../../core/time');
5
6
  const { createTailwindMotionAnalyzer } = require('../../core/tailwind-motion-analysis');
6
7
 
@@ -71,7 +72,7 @@ function buildMessage(analysis, segment, lower, upper) {
71
72
  return `Unexpected Tailwind arbitrary motion duration "${segment.token}". Use non-negative duration values.`;
72
73
  }
73
74
 
74
- return `Unexpected Tailwind arbitrary motion duration "${segment.token}". Use duration scale values (nearest: ${lower} or ${upper}).`;
75
+ return `Unexpected Tailwind arbitrary motion duration "${segment.token}". Use duration scale values (${nearestText(lower, upper)}).`;
75
76
  }
76
77
 
77
78
  module.exports = {
@@ -6,6 +6,7 @@ const {
6
6
  formatLength,
7
7
  isHairlineLength,
8
8
  nearestScaleValues,
9
+ nearestText,
9
10
  numbersEqual,
10
11
  parseLengthToken,
11
12
  toPx,
@@ -37,7 +38,7 @@ const messages = stylelint.utils.ruleMessages(ruleName, {
37
38
  invalidPreset: (presetName, presetNames) =>
38
39
  `Unknown scale preset "${presetName}". Available presets: ${presetNames.join(', ')}.`,
39
40
  rejected: (value, lower, upper, note = '') =>
40
- `Unexpected transform translation value "${value}". Use scale values (nearest: ${lower} or ${upper}).${note ? ` ${note}` : ''}`,
41
+ `Unexpected transform translation value "${value}". Use scale values (${nearestText(lower, upper)}).${note ? ` ${note}` : ''}`,
41
42
  });
42
43
 
43
44
  const ruleFunction = (primary, secondaryOptions) => {
@@ -4,6 +4,7 @@ const stylelint = require('stylelint');
4
4
  const valueParser = require('postcss-value-parser');
5
5
  const {
6
6
  nearestScaleValues,
7
+ nearestText,
7
8
  numbersEqual,
8
9
  } = require('../../core/length');
9
10
  const {
@@ -43,7 +44,7 @@ const messages = stylelint.utils.ruleMessages(ruleName, {
43
44
  invalidDuration: (value, note = '') =>
44
45
  withNote(`Unexpected negative motion duration "${value}". Use non-negative duration values.`, note),
45
46
  rejectedDuration: (value, lower, upper, note = '') =>
46
- withNote(`Unexpected motion duration "${value}". Use duration scale values (nearest: ${lower} or ${upper}).`, note),
47
+ withNote(`Unexpected motion duration "${value}". Use duration scale values (${nearestText(lower, upper)}).`, note),
47
48
  rejectedEasing: (value, note = '') =>
48
49
  withNote(`Unexpected raw motion easing "${value}". Use motion tokens for easing decisions.`, note),
49
50
  });
@@ -6,6 +6,7 @@ const {
6
6
  formatLength,
7
7
  isHairlineLength,
8
8
  nearestScaleValues,
9
+ nearestText,
9
10
  numbersEqual,
10
11
  parseLengthToken,
11
12
  toPx,
@@ -44,7 +45,7 @@ const messages = stylelint.utils.ruleMessages(ruleName, {
44
45
  invalidPreset: (presetName, presetNames) =>
45
46
  `Unknown scale preset "${presetName}". Available presets: ${presetNames.join(', ')}.`,
46
47
  rejected: (value, lower, upper, note = '', position = null, aside = '', group = null) =>
47
- `Unexpected off-scale ${position ? `${position} "${value}"${aside}` : `value "${value}"`}. Use ${group ? `${group} ` : ''}scale values (nearest: ${lower} or ${upper}).${note ? ` ${note}` : ''}`,
48
+ `Unexpected off-scale ${position ? `${position} "${value}"${aside}` : `value "${value}"`}. Use ${group ? `${group} ` : ''}scale values (${nearestText(lower, upper)}).${note ? ` ${note}` : ''}`,
48
49
  });
49
50
 
50
51
  /** " (16.8px)" after a bound written in rem or em, so the reader sees what was compared. */